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:
1 parent
42b8988661
commit
ea3708a98f
19 files changed
+785
-89
No files matched your search
@@ -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;">
|
||||
|
||||
@@ -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."""
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -244,15 +244,15 @@
|
||||
|
||||
<!-- Floating Top-Right Badges & Toolbar (Pinned overlay, hidden in empty/initial state) -->
|
||||
<div class="stream-floating-toolbar" *ngIf="consolidatedBlocks().length > 0 || showPlanningLoader()">
|
||||
<!-- Active Model Info Chip (Informational Metadata, Not a Button) -->
|
||||
<!-- Active Architecture Info Chip (Informational Metadata, Not a Button) -->
|
||||
<div
|
||||
class="model-info-chip"
|
||||
[ngClass]="getModelClass(model.name)"
|
||||
*ngIf="agentService.activeModel() as model"
|
||||
[title]="'Active Model: ' + model.id + ' (' + model.provider + ')'"
|
||||
[title]="getArchitectureTooltip(model)"
|
||||
>
|
||||
<span class="material-symbols-outlined model-icon">{{ getModelIcon(model.name) }}</span>
|
||||
<span class="model-label">Model:</span>
|
||||
<span class="model-label">Architecture:</span>
|
||||
<span class="model-name">{{ getModelDisplayName(model.name) }}</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import { FormsModule } from '@angular/forms';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { AgentService, StartupProgressEvent } from '../../services/agent.service';
|
||||
import { Session } from '../../core/models/session.model';
|
||||
import { Session, ModelInfo } from '../../core/models/session.model';
|
||||
import { MarkdownSegment, MarkdownLine, NoteMilestone, ParsedNote } from '../../core/models/markdown.model';
|
||||
import { StepBlock, PhaseBlock, StepEvent, ActionParam, CheckerResult } from '../../core/models/stream.model';
|
||||
|
||||
@@ -1262,6 +1262,19 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
return '';
|
||||
}
|
||||
|
||||
public getArchitectureTooltip(model?: ModelInfo | null): string {
|
||||
if (!model) return 'Agent Architecture: ARTEMIS Flash (Reactive Fast Loop)';
|
||||
const name = this.getModelDisplayName(model.name);
|
||||
const isPro = name.toLowerCase().includes('pro');
|
||||
const archDesc = isPro
|
||||
? 'ARTEMIS Pro (Multi-Agent Cognitive State Graph)'
|
||||
: 'ARTEMIS Flash (Reactive Fast Loop)';
|
||||
if (model.id) {
|
||||
return `Agent Architecture: ${archDesc} · LLM: ${model.id} (${model.provider || 'google'})`;
|
||||
}
|
||||
return `Agent Architecture: ${archDesc}`;
|
||||
}
|
||||
|
||||
public formatTokenCount(tokens?: number): string {
|
||||
return formatTokenCount(tokens);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface ModelInfo {
|
||||
name: string;
|
||||
id: string;
|
||||
provider: string;
|
||||
architecture?: string;
|
||||
}
|
||||
|
||||
export interface TaskQueueItem {
|
||||
|
||||
@@ -1576,13 +1576,14 @@
|
||||
<!-- Composer Bottom Action Bar -->
|
||||
<div class="composer-action-bar">
|
||||
<div class="bar-left">
|
||||
<!-- Frosted Glass Model Toggle Capsule -->
|
||||
<!-- Frosted Glass Architecture Toggle Capsule -->
|
||||
<div class="model-toggle-capsule">
|
||||
<button
|
||||
type="button"
|
||||
class="model-toggle-btn btn-flash"
|
||||
[class.active]="selectedProfile() === 'flash'"
|
||||
(click)="setProfile('flash')"
|
||||
title="Flash: Reactive fast-loop architecture for standard app workflows (3–5s/step)"
|
||||
>
|
||||
<span class="material-symbols-outlined btn-icon">bolt</span>
|
||||
<span class="btn-text">Flash</span>
|
||||
@@ -1592,6 +1593,7 @@
|
||||
class="model-toggle-btn btn-pro"
|
||||
[class.active]="selectedProfile() === 'pro'"
|
||||
(click)="setProfile('pro')"
|
||||
title="Pro: Multi-agent graph architecture with planner & closed-loop verification for complex tasks"
|
||||
>
|
||||
<span class="material-symbols-outlined btn-icon">psychology</span>
|
||||
<span class="btn-text">Pro</span>
|
||||
@@ -1745,7 +1747,8 @@
|
||||
<span class="app-names-label">{{ getAppNamesDisplay(item.apps) }}</span>
|
||||
</div>
|
||||
|
||||
<span class="mode-badge" [class.mode-pro]="item.profile === 'pro'" [class.mode-flash]="item.profile === 'flash'">
|
||||
<span class="mode-badge" [class.mode-pro]="item.profile === 'pro'" [class.mode-flash]="item.profile === 'flash'"
|
||||
[title]="item.profile === 'pro' ? 'Uses ARTEMIS Pro (Multi-Agent Cognitive State Graph)' : 'Uses ARTEMIS Flash (Reactive Fast Loop)'">
|
||||
{{ item.profile === 'pro' ? 'Pro' : 'Flash' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -103,8 +103,8 @@
|
||||
rows="1"
|
||||
[disabled]="isSubmitting"
|
||||
[placeholder]="selectedProfile() === 'pro'
|
||||
? 'Pro: Best for complex long-running tasks, deep reasoning & video analysis'
|
||||
: 'Flash: Fast execution for direct actions and tasks within 25 steps'"
|
||||
? 'Pro: Multi-agent cognitive graph architecture for complex multi-step tasks'
|
||||
: 'Flash: Reactive fast loop for standard app workflows within 25 steps'"
|
||||
class="dock-textarea"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
@@ -141,17 +141,17 @@
|
||||
<!-- Bottom Segmented Controls Row -->
|
||||
<div class="card-footer-row">
|
||||
<div class="footer-left-group">
|
||||
<!-- Segmented Model Toggle (Flash / Pro) -->
|
||||
<div class="model-profile-toggle" role="group" aria-label="Select AI Model">
|
||||
<!-- Segmented Architecture Toggle (Flash / Pro) -->
|
||||
<div class="model-profile-toggle" role="group" aria-label="Select Agent Architecture">
|
||||
<button type="button" class="profile-toggle-pill" [class.active]="selectedProfile() === 'flash'"
|
||||
(mousedown)="$event.preventDefault()" (click)="setProfile('flash', $event)"
|
||||
title="Flash: Fast execution for standard app workflows">
|
||||
title="Flash: Reactive fast-loop architecture for standard app workflows (3–5s/step)">
|
||||
<span class="material-symbols-outlined pill-icon icon-flash">bolt</span>
|
||||
<span class="pill-label">Flash</span>
|
||||
</button>
|
||||
<button type="button" class="profile-toggle-pill" [class.active]="selectedProfile() === 'pro'"
|
||||
(mousedown)="$event.preventDefault()" (click)="setProfile('pro', $event)"
|
||||
title="Pro: Deep multimodal reasoning for complex multi-step tasks">
|
||||
title="Pro: Multi-agent cognitive graph architecture with planner & closed-loop verification for complex tasks">
|
||||
<span class="material-symbols-outlined pill-icon icon-pro">psychology</span>
|
||||
<span class="pill-label">Pro</span>
|
||||
</button>
|
||||
|
||||
@@ -64,7 +64,7 @@ export class WorkspaceComponent implements OnInit {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set model profile ('flash' vs 'pro')
|
||||
* Set agent architecture profile ('flash' vs 'pro')
|
||||
*/
|
||||
public setProfile(profile: 'flash' | 'pro', event?: MouseEvent): void {
|
||||
if (event) {
|
||||
|
||||
Reference in new issue
Block a user