- Extract device action execution into artemis/mcp action layer: typed ActionResult, capability manifest with startup validation, AdbActuator as the single physical implementation, in-memory ActionSession (replaces stdio subprocess MCP in validator), and McpActionExecutor preserving the legacy executor contract. adb_server keeps its external stdio/SSE surface unchanged and now reuses the actuator implementation; import-time side effects moved into configure_stdio_mode(). - Capability-gated prompt assembly (prompt_assembly.py): tool enums and teaching segments render per available backend tools; full-set output is byte-identical to the previous static text (locked by tests). - Admin console task queue: serial worker replaced by a per-device concurrent dispatcher (ARTEMIS_CONCURRENCY_MODE=global restores serial semantics), per-session cancellation, strict device binding on enqueue, unified stdio piping with tee to trace stdout.log, and shutdown cleanup of all active runs. Fix _pid_exists exception order so PermissionError is treated as alive. - mcp_server reliability: spawn watchdog kills runners stuck before interpreter startup and releases device reservations; task state reconcile uses psutil liveness, prefers runner-reported pids, and adds a 45s startup grace to stop false failure reports on Windows; conversation_id is now optional end-to-end; explicit device_serial is validated at submission. - CLI install now writes ~/.claude/rules/artemis.md for Claude Code and migrates/removes the legacy CLAUDE.md injection block; ~/.claude.json is never rewritten when it fails to parse. - Tests: Windows-portable stdio lifecycle and path assertions; new suites for action layer, prompt assembly, spawn watchdog, and task reconcile (895 passed, 3 skipped).
72 lines
2.2 KiB
Python
72 lines
2.2 KiB
Python
# 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.
|
|
|
|
"""Compatibility prompt builder backed by the canonical Operator templates."""
|
|
|
|
import json
|
|
from functools import lru_cache
|
|
from pathlib import Path
|
|
|
|
from jinja2 import Template
|
|
|
|
OPERATOR_HUMAN_TEMPLATE = """Goal: {{ goal }}
|
|
Active Sub-Goal: {{ sub_goal }}
|
|
Current Turn: {{ current_turn }}
|
|
Execution History:
|
|
{{ history }}
|
|
"""
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
def load_operator_prompts() -> dict[str, str]:
|
|
"""Load the single canonical prompt source shared by all Operator entry points."""
|
|
prompts_path = Path(__file__).with_name("operator.json")
|
|
return json.loads(prompts_path.read_text(encoding="utf-8"))
|
|
|
|
|
|
class OperatorPromptBuilder:
|
|
"""Builds prompt messages and multimodal contents for the Operator Agent."""
|
|
|
|
@classmethod
|
|
def build_system_message(
|
|
cls,
|
|
template_name: str = "main_template",
|
|
available_tools: frozenset[str] | None = None,
|
|
) -> str:
|
|
from artemis.agents.operator.prompts import apply_operator_prompt_contract
|
|
|
|
prompts = load_operator_prompts()
|
|
try:
|
|
return apply_operator_prompt_contract(
|
|
prompts[template_name], available_tools=available_tools
|
|
)
|
|
except KeyError as exc:
|
|
raise ValueError(f"Unknown Operator template: {template_name}") from exc
|
|
|
|
@classmethod
|
|
def build_human_message(
|
|
cls,
|
|
goal: str,
|
|
sub_goal: str = "",
|
|
current_turn: int = 1,
|
|
history: str = "",
|
|
) -> str:
|
|
template = Template(OPERATOR_HUMAN_TEMPLATE)
|
|
return template.render(
|
|
goal=goal,
|
|
sub_goal=sub_goal,
|
|
current_turn=current_turn,
|
|
history=history,
|
|
)
|