feat: implement robust multi-tier text input architecture and ADB diagnostics

- Android Text Input & IME Normalization:
  - Implement progressive 3-tier input architecture in AndroidAdbDriver and AndroidDeviceController:
    - Tier 1 (Primary): Zero-dependency clipboard injection + KEYCODE_PASTE (immune to IME Pinyin interception, full Unicode/Emoji support, preserves multiline formatting).
    - Tier 2 (Broadcast): Base64 broadcast injection when ADBKeyboard is active.
    - Tier 3 (Fallback): Native ADB physical keyevent fallback.
  - Add literal newline normalization (\r\n, \n, \r) to prevent JSON escape string corruption (e.g. '10086\n' -> '10086、n').
  - Add set_clipboard method to UIAutomatorClient without auxiliary APK requirements.

- Diagnostics & System Probes:
  - Add adb_keys diagnostics probe for ADB key auth checks.
  - Integrate diagnostics into CLI doctor command.

- Admin Console & Showcase UI:
  - Improve model service and system router in admin console.
  - Enhance workspace stream view and session state in showcase UI.
This commit is contained in:
somew1nd committed 2026-08-24 16:48:45 -07:00
1 parent 42b8988661
commit ea3708a98f
19 files changed
+785 -89

No files matched your search

+3 -3
View File
@@ -241,10 +241,10 @@
<div style="margin-bottom: 20px;">
<textarea id="task-goal" style="width: 100%; height: 60px; border-radius: 4px; border: 1px solid #e2e8f0; padding: 8px; box-sizing: border-box; font-family: inherit;" placeholder="Enter task goal..."></textarea>
<div style="margin-top: 8px; margin-bottom: 8px; display: flex; align-items: center; justify-content: space-between; font-size: 0.85rem; color: #475569;">
<label for="task-profile-select" style="font-weight: 600;">Model Profile:</label>
<label for="task-profile-select" style="font-weight: 600;">Agent Architecture:</label>
<select id="task-profile-select" style="padding: 4px 8px; border-radius: 4px; border: 1px solid #cbd5e0; font-size: 0.85rem; background-color: #f8fafc; color: #334155; outline: none; cursor: pointer; flex: 1; margin-left: 10px;">
<option value="flash">⚡ Flash (Fast Response)</option>
<option value="pro">🚀 Pro (Deep Planning)</option>
<option value="flash">⚡ Flash (Reactive Fast Loop)</option>
<option value="pro">🚀 Pro (Multi-Agent Cognitive Graph)</option>
</select>
</div>
<div style="display: flex; gap: 10px; margin-top: 5px;">
+11
View File
@@ -63,6 +63,17 @@ async def restart_adb_server():
}
@router.post("/adb/heal-keys")
async def heal_adb_keys():
"""Auto-heal corrupted ADB authentication RSA keys and return updated readiness."""
heal_result = await readiness_engine.heal_adb_keys()
updated_report = await readiness_engine.run_all()
return {
"heal_result": heal_result,
"report": updated_report,
}
class ConnectAdbRequest(BaseModel):
"""Payload to connect to an Android device over Wi-Fi."""
+27 -37
View File
@@ -17,26 +17,12 @@ from typing import Any
class ModelService:
"""Service handling model profile detection and metadata formatting."""
"""Service handling agent architecture profile detection and metadata formatting."""
@staticmethod
def get_active_model_info(profile: str | None = None) -> dict[str, str]:
if profile:
p_lower = str(profile).lower()
if "pro" in p_lower:
return {
"name": "Pro",
"id": "gemini-3.7-pro",
"provider": "google",
}
elif "flash" in p_lower:
return {
"name": "Flash",
"id": "gemini-3.7-flash",
"provider": "google",
}
model_name = "Flash"
"""Return active architecture and underlying LLM model configuration."""
# 1. Determine underlying LLM model and provider from config
model_id = "gemini-3.7-flash"
provider = "google"
try:
@@ -44,18 +30,31 @@ class ModelService:
llm_cfg = parse_llm_config()
if llm_cfg and llm_cfg.operator:
op_model = str(llm_cfg.operator.model).lower()
provider = str(llm_cfg.operator.provider)
model_id = llm_cfg.operator.model
if "pro" in op_model:
model_name = "Pro"
elif "flash" in op_model:
model_name = "Flash"
else:
model_name = llm_cfg.operator.model.capitalize()
model_id = str(llm_cfg.operator.model)
elif llm_cfg and llm_cfg.default:
provider = str(llm_cfg.default.provider)
model_id = str(llm_cfg.default.model)
except Exception:
pass
return {"name": model_name, "id": model_id, "provider": provider}
# 2. Determine agent architecture name (Flash vs Pro)
arch_name = "Flash"
if profile:
p_lower = str(profile).lower()
if "pro" in p_lower:
arch_name = "Pro"
elif "flash" in p_lower:
arch_name = "Flash"
else:
arch_name = str(profile).capitalize()
return {
"name": arch_name,
"id": model_id,
"provider": provider,
"architecture": f"ARTEMIS {arch_name}",
}
@staticmethod
def resolve_session_profile(
@@ -64,6 +63,7 @@ class ModelService:
running_profile: str | None = None,
agent_names: list[str] | None = None,
) -> str | None:
"""Resolve the Artemis agent architecture profile ('flash' or 'pro') for a session."""
sess_profile = None
d_info_raw = row_dict.get("device_info")
if d_info_raw:
@@ -98,7 +98,7 @@ class ModelService:
if any("flashrunner" in name for name in agent_names_lower):
return "flash"
# Check LLM traces for agent name or model overrides
# Check LLM traces for agent name
if llm_trace_payloads:
for tr_payload in llm_trace_payloads:
if tr_payload:
@@ -115,16 +115,6 @@ class ModelService:
for x in ("planner", "validator", "operator", "checker")
):
return "pro"
m_str = str(p_obj.get("model") or p_obj.get("model_name") or "").lower()
if (
"gemini-3.7-pro" in m_str
or "gemini-3.6-pro" in m_str
or "gemini-1.5-pro" in m_str
or "gpt-4" in m_str
or "claude" in m_str
):
return "pro"
except Exception:
pass