N12R/N13 internal sandbox handoff and UIA selector catalog #1

Merged
szz merged 11 commits from codex/n13-uia-selector-catalog into main 2026-07-09 07:27:13 +00:00
2 changed files with 208 additions and 0 deletions
Showing only changes of commit d648c9476d - Show all commits

View File

@@ -0,0 +1,148 @@
package uiaselector
const AllowedUseReadOnlyLocate = "read_only_locate"
type Selector struct {
ID string `json:"id"`
Description string `json:"description"`
AllowedUse string `json:"allowed_use"`
Required Criteria `json:"required"`
Preferred PreferredCriteria `json:"preferred"`
Fallback []FallbackCriteria `json:"fallback"`
StabilityScore int `json:"stability_score"`
Evidence Evidence `json:"evidence"`
}
type Criteria struct {
AutomationID string `json:"automation_id,omitempty"`
ControlType string `json:"control_type,omitempty"`
FrameworkID string `json:"framework_id,omitempty"`
ClassName string `json:"class_name,omitempty"`
ClassPrefix string `json:"class_name_prefix,omitempty"`
AncestorID string `json:"ancestor_selector,omitempty"`
}
type PreferredCriteria struct {
ClassName string `json:"class_name,omitempty"`
ClassPrefix string `json:"class_name_prefix,omitempty"`
PathHint string `json:"path_hint,omitempty"`
AncestorID string `json:"ancestor_selector,omitempty"`
}
type FallbackCriteria struct {
AncestorID string `json:"ancestor_selector,omitempty"`
ControlType string `json:"control_type,omitempty"`
ClassPrefix string `json:"class_name_prefix,omitempty"`
ChildAutomationIDContains []string `json:"child_automation_id_contains,omitempty"`
ChildControlTypeContains []string `json:"child_control_type_contains,omitempty"`
}
type Evidence struct {
CaptureID string `json:"capture_id"`
UIAPath string `json:"uia_path"`
}
func DefaultCatalog() []Selector {
return []Selector{
{
ID: "main_window",
Description: "iSphere / IMPlatformClient 主窗口",
AllowedUse: AllowedUseReadOnlyLocate,
Required: Criteria{AutomationID: "frmMain", ControlType: "Window", FrameworkID: "WinForm"},
Preferred: PreferredCriteria{ClassPrefix: "WindowsForms10.Window.8.app.0", PathHint: "root"},
Fallback: []FallbackCriteria{{ControlType: "Window", ClassPrefix: "WindowsForms10.Window.8.app.0", ChildAutomationIDContains: []string{"panelLeft", "ucPluginTabControl1"}}},
StabilityScore: 95,
Evidence: Evidence{CaptureID: "2026-07-08-internal-sandbox-a", UIAPath: "root"},
},
{
ID: "main_plugin_tab",
Description: "主功能 tab 容器",
AllowedUse: AllowedUseReadOnlyLocate,
Required: Criteria{AutomationID: "ucPluginTabControl1", ControlType: "Pane", FrameworkID: "WinForm"},
Preferred: PreferredCriteria{PathHint: "root/3", AncestorID: "main_window"},
Fallback: []FallbackCriteria{{AncestorID: "main_window", ControlType: "Pane", ChildAutomationIDContains: []string{"FrmHomePage", "ucTabPage"}}},
StabilityScore: 90,
Evidence: Evidence{CaptureID: "2026-07-08-internal-sandbox-a", UIAPath: "root/3"},
},
{
ID: "home_page_window",
Description: "首页内容窗口",
AllowedUse: AllowedUseReadOnlyLocate,
Required: Criteria{AutomationID: "FrmHomePage", ControlType: "Window", FrameworkID: "WinForm"},
Preferred: PreferredCriteria{PathHint: "root/3/0/0", AncestorID: "main_plugin_tab"},
Fallback: []FallbackCriteria{{AncestorID: "main_plugin_tab", ControlType: "Window", ClassPrefix: "WindowsForms10.Window.8.app.0"}},
StabilityScore: 82,
Evidence: Evidence{CaptureID: "2026-07-08-internal-sandbox-a", UIAPath: "root/3/0/0"},
},
{
ID: "tab_page_content",
Description: "tab 页内容窗口",
AllowedUse: AllowedUseReadOnlyLocate,
Required: Criteria{AutomationID: "FrmTabPageContent", ControlType: "Window", FrameworkID: "WinForm"},
Preferred: PreferredCriteria{PathHint: "root/3/1/0", AncestorID: "main_plugin_tab"},
Fallback: []FallbackCriteria{{AncestorID: "main_plugin_tab", ControlType: "Window", ChildControlTypeContains: []string{"TitleBar"}}},
StabilityScore: 82,
Evidence: Evidence{CaptureID: "2026-07-08-internal-sandbox-a", UIAPath: "root/3/1/0"},
},
{
ID: "left_panel",
Description: "左侧主面板",
AllowedUse: AllowedUseReadOnlyLocate,
Required: Criteria{AutomationID: "panelLeft", ControlType: "Pane", FrameworkID: "WinForm"},
Preferred: PreferredCriteria{PathHint: "root/4", AncestorID: "main_window"},
Fallback: []FallbackCriteria{{AncestorID: "main_window", ControlType: "Pane", ChildAutomationIDContains: []string{"ucPnlRoster1", "ucFuzzyQuery1"}}},
StabilityScore: 92,
Evidence: Evidence{CaptureID: "2026-07-08-internal-sandbox-a", UIAPath: "root/4"},
},
{
ID: "roster_panel",
Description: "组织/联系人容器外层",
AllowedUse: AllowedUseReadOnlyLocate,
Required: Criteria{AutomationID: "ucPnlRoster1", ControlType: "Pane", FrameworkID: "WinForm"},
Preferred: PreferredCriteria{PathHint: "root/4/0/0", AncestorID: "left_panel"},
Fallback: []FallbackCriteria{{AncestorID: "left_panel", ControlType: "Pane", ChildAutomationIDContains: []string{"ucRoster1"}}},
StabilityScore: 88,
Evidence: Evidence{CaptureID: "2026-07-08-internal-sandbox-a", UIAPath: "root/4/0/0"},
},
{
ID: "roster_view",
Description: "组织/联系人列表区域",
AllowedUse: AllowedUseReadOnlyLocate,
Required: Criteria{AutomationID: "ucRoster1", ControlType: "Pane", FrameworkID: "WinForm"},
Preferred: PreferredCriteria{PathHint: "root/4/0/0/0", AncestorID: "roster_panel"},
Fallback: []FallbackCriteria{{AncestorID: "roster_panel", ControlType: "Pane", ClassPrefix: "WindowsForms10.Window.8.app.0"}},
StabilityScore: 86,
Evidence: Evidence{CaptureID: "2026-07-08-internal-sandbox-a", UIAPath: "root/4/0/0/0"},
},
{
ID: "fuzzy_query_panel",
Description: "模糊搜索组件外层",
AllowedUse: AllowedUseReadOnlyLocate,
Required: Criteria{AutomationID: "ucFuzzyQuery1", ControlType: "Pane", FrameworkID: "WinForm"},
Preferred: PreferredCriteria{PathHint: "root/4/1/0", AncestorID: "left_panel"},
Fallback: []FallbackCriteria{{AncestorID: "left_panel", ControlType: "Pane", ChildAutomationIDContains: []string{"FrmFuzzyQuery"}}},
StabilityScore: 90,
Evidence: Evidence{CaptureID: "2026-07-08-internal-sandbox-a", UIAPath: "root/4/1/0"},
},
{
ID: "fuzzy_query_window",
Description: "模糊搜索内部窗口",
AllowedUse: AllowedUseReadOnlyLocate,
Required: Criteria{AutomationID: "FrmFuzzyQuery", ControlType: "Pane", FrameworkID: "WinForm"},
Preferred: PreferredCriteria{PathHint: "root/4/1/0/0", AncestorID: "fuzzy_query_panel"},
Fallback: []FallbackCriteria{{AncestorID: "fuzzy_query_panel", ControlType: "Pane", ChildAutomationIDContains: []string{"skinAlphaTxt"}}},
StabilityScore: 88,
Evidence: Evidence{CaptureID: "2026-07-08-internal-sandbox-a", UIAPath: "root/4/1/0/0"},
},
{
ID: "fuzzy_search_edit",
Description: "模糊搜索输入框",
AllowedUse: AllowedUseReadOnlyLocate,
Required: Criteria{AutomationID: "skinAlphaTxt", ControlType: "Edit", FrameworkID: "WinForm"},
Preferred: PreferredCriteria{ClassName: "WindowsForms10.EDIT.app.0.d3a00f_r29_ad1", PathHint: "root/4/1/0/0/2", AncestorID: "fuzzy_query_window"},
Fallback: []FallbackCriteria{{AncestorID: "fuzzy_query_window", ControlType: "Edit", ClassPrefix: "WindowsForms10.EDIT.app.0"}},
StabilityScore: 93,
Evidence: Evidence{CaptureID: "2026-07-08-internal-sandbox-a", UIAPath: "root/4/1/0/0/2"},
},
}
}

View File

@@ -0,0 +1,60 @@
package uiaselector
import (
"strings"
"testing"
)
func TestDefaultCatalogContainsInitialN13Selectors(t *testing.T) {
catalog := DefaultCatalog()
wantIDs := []string{
"main_window",
"main_plugin_tab",
"home_page_window",
"tab_page_content",
"left_panel",
"roster_panel",
"roster_view",
"fuzzy_query_panel",
"fuzzy_query_window",
"fuzzy_search_edit",
}
byID := map[string]Selector{}
for _, selector := range catalog {
byID[selector.ID] = selector
}
for _, id := range wantIDs {
selector, ok := byID[id]
if !ok {
t.Fatalf("DefaultCatalog missing selector %q", id)
}
if selector.AllowedUse != AllowedUseReadOnlyLocate {
t.Fatalf("selector %q allowed_use = %q, want %q", id, selector.AllowedUse, AllowedUseReadOnlyLocate)
}
if selector.Required.AutomationID == "" {
t.Fatalf("selector %q must require automation_id", id)
}
if selector.Required.ControlType == "" {
t.Fatalf("selector %q must require control_type", id)
}
if selector.StabilityScore < 80 {
t.Fatalf("selector %q stability_score = %d, want >= 80", id, selector.StabilityScore)
}
}
}
func TestDefaultCatalogDoesNotDescribeActionTools(t *testing.T) {
for _, selector := range DefaultCatalog() {
if selector.AllowedUse != AllowedUseReadOnlyLocate {
t.Fatalf("selector %q allowed_use = %q, want read-only", selector.ID, selector.AllowedUse)
}
lower := strings.ToLower(selector.ID + " " + selector.Description)
for _, forbidden := range []string{"send", "upload", "download", "receive", "conversation", "login", "file_transfer"} {
if strings.Contains(lower, forbidden) {
t.Fatalf("selector %q contains action word %q", selector.ID, forbidden)
}
}
}
}