diff --git a/config/feature_flags.yaml b/config/feature_flags.yaml
index 7cb3b850..0e0762ff 100644
--- a/config/feature_flags.yaml
+++ b/config/feature_flags.yaml
@@ -11,79 +11,12 @@ mobile:
build_modes: [debug, profile, release]
description: Mobile assistant destination
ui_surface: mobile_shell
- tasks:
- enabled: false
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Mobile tasks destination
- ui_surface: mobile_shell
- workspace:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Mobile workspace hub destination
- ui_surface: mobile_shell
- secrets:
- enabled: true
- release_tier: experimental
- build_modes: [debug, profile, release]
- description: Mobile secrets destination
- ui_surface: mobile_shell
settings:
enabled: true
release_tier: stable
build_modes: [debug, profile, release]
description: Mobile settings destination
ui_surface: mobile_shell
- workspace:
- skills:
- enabled: false
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Mobile workspace skills launcher
- ui_surface: mobile_workspace_hub
- nodes:
- enabled: false
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Mobile workspace nodes launcher
- ui_surface: mobile_workspace_hub
- agents:
- enabled: false
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Mobile workspace agents launcher
- ui_surface: mobile_workspace_hub
- mcp_server:
- enabled: false
- release_tier: experimental
- build_modes: [debug, profile, release]
- description: Mobile workspace MCP launcher
- ui_surface: mobile_workspace_hub
- claw_hub:
- enabled: false
- release_tier: experimental
- build_modes: [debug, profile, release]
- description: Mobile workspace ClawHub launcher
- ui_surface: mobile_workspace_hub
- connectors:
- enabled: false
- release_tier: experimental
- build_modes: [debug, profile, release]
- description: Mobile workspace connectors launcher
- ui_surface: mobile_workspace_hub
- ai_gateway:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Mobile workspace AI Gateway launcher
- ui_surface: mobile_workspace_hub
- account:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Mobile workspace account launcher
- ui_surface: mobile_workspace_hub
assistant:
direct_ai:
enabled: true
@@ -113,7 +46,7 @@ mobile:
enabled: true
release_tier: experimental
build_modes: [debug, profile, release]
- description: Mobile multi-agent toggle in assistant composer
+ description: Mobile multi-agent assistant controls
ui_surface: assistant_page
local_runtime:
enabled: false
@@ -122,47 +55,35 @@ mobile:
description: Mobile does not expose desktop runtime controls
ui_surface: assistant_page
settings:
- general:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Mobile settings general tab
- ui_surface: settings_page
- workspace:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Mobile settings workspace tab
- ui_surface: settings_page
gateway:
enabled: true
release_tier: stable
build_modes: [debug, profile, release]
- description: Mobile settings gateway tab
+ description: Mobile bridge and integration settings
ui_surface: settings_page
account_access:
enabled: true
release_tier: stable
build_modes: [debug, profile, release]
- description: Mobile account access section
+ description: Mobile account access settings
ui_surface: settings_page
vault_server:
enabled: true
release_tier: stable
build_modes: [debug, profile, release]
- description: Mobile Vault server integration section
+ description: Mobile vault server settings
ui_surface: settings_page
gateway_self_hosted_base:
enabled: false
release_tier: experimental
build_modes: [debug, profile, release]
- description: Mobile self-hosted base connection controls
+ description: Mobile self-hosted gateway base controls
ui_surface: settings_page
gateway_advanced_custom_mode:
enabled: false
release_tier: experimental
build_modes: [debug, profile, release]
- description: Mobile advanced custom override mode
+ description: Mobile advanced gateway override controls
ui_surface: settings_page
gateway_setup_code:
enabled: false
@@ -170,36 +91,6 @@ mobile:
build_modes: [debug, profile, release]
description: Mobile gateway setup code editor
ui_surface: settings_page
- agents:
- enabled: false
- release_tier: experimental
- build_modes: [debug, profile, release]
- description: Mobile settings multi-agent tab
- ui_surface: settings_page
- appearance:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Mobile settings appearance tab
- ui_surface: settings_page
- diagnostics:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Mobile settings diagnostics tab
- ui_surface: settings_page
- experimental:
- enabled: true
- release_tier: experimental
- build_modes: [debug, profile, release]
- description: Mobile settings experimental tab
- ui_surface: settings_page
- about:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Mobile settings about tab
- ui_surface: settings_page
experimental_canvas:
enabled: true
release_tier: experimental
@@ -227,79 +118,12 @@ desktop:
build_modes: [debug, profile, release]
description: Desktop assistant destination
ui_surface: sidebar_navigation
- tasks:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Desktop tasks destination
- ui_surface: sidebar_navigation
- skills:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Desktop skills destination
- ui_surface: sidebar_navigation
- nodes:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Desktop nodes destination
- ui_surface: sidebar_navigation
- agents:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Desktop agents destination
- ui_surface: sidebar_navigation
- mcp_server:
- enabled: true
- release_tier: experimental
- build_modes: [debug, profile, release]
- description: Desktop MCP Hub destination
- ui_surface: sidebar_navigation
- claw_hub:
- enabled: false
- release_tier: experimental
- build_modes: [debug, profile, release]
- description: Desktop ClawHub destination
- ui_surface: sidebar_navigation
- secrets:
- enabled: false
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Desktop secrets destination
- ui_surface: sidebar_navigation
- ai_gateway:
- enabled: false
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Desktop AI Gateway destination
- ui_surface: sidebar_navigation
settings:
enabled: true
release_tier: stable
build_modes: [debug, profile, release]
description: Desktop settings destination
ui_surface: sidebar_navigation
- account:
- enabled: false
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Desktop account destination
- ui_surface: sidebar_navigation
- workspace:
- claw_hub:
- enabled: false
- release_tier: experimental
- build_modes: [debug, profile, release]
- description: Desktop workspace ClawHub tab
- ui_surface: modules_page
- connectors:
- enabled: false
- release_tier: experimental
- build_modes: [debug, profile, release]
- description: Desktop workspace connectors tab
- ui_surface: modules_page
assistant:
direct_ai:
enabled: true
@@ -329,56 +153,44 @@ desktop:
enabled: true
release_tier: beta
build_modes: [debug, profile, release]
- description: Desktop multi-agent toggle in assistant composer
+ description: Desktop multi-agent assistant controls
ui_surface: assistant_page
local_runtime:
enabled: true
release_tier: stable
build_modes: [debug, profile, release]
- description: Desktop local runtime and gateway orchestration entry
+ description: Desktop local runtime and gateway orchestration controls
ui_surface: assistant_page
settings:
- general:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Desktop settings general tab
- ui_surface: settings_page
- workspace:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Desktop settings workspace tab
- ui_surface: settings_page
gateway:
enabled: true
release_tier: stable
build_modes: [debug, profile, release]
- description: Desktop settings gateway tab
+ description: Desktop bridge and integration settings
ui_surface: settings_page
account_access:
enabled: true
release_tier: stable
build_modes: [debug, profile, release]
- description: Desktop account access section
+ description: Desktop account access settings
ui_surface: settings_page
vault_server:
enabled: true
release_tier: stable
build_modes: [debug, profile, release]
- description: Desktop Vault server integration section
+ description: Desktop vault server settings
ui_surface: settings_page
gateway_self_hosted_base:
enabled: false
release_tier: experimental
build_modes: [debug, profile, release]
- description: Desktop self-hosted base connection controls
+ description: Desktop self-hosted gateway base controls
ui_surface: settings_page
gateway_advanced_custom_mode:
enabled: false
release_tier: experimental
build_modes: [debug, profile, release]
- description: Desktop advanced custom override mode
+ description: Desktop advanced gateway override controls
ui_surface: settings_page
gateway_setup_code:
enabled: false
@@ -386,36 +198,6 @@ desktop:
build_modes: [debug, profile, release]
description: Desktop gateway setup code editor
ui_surface: settings_page
- agents:
- enabled: false
- release_tier: experimental
- build_modes: [debug, profile, release]
- description: Desktop settings multi-agent tab
- ui_surface: settings_page
- appearance:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Desktop settings appearance tab
- ui_surface: settings_page
- diagnostics:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Desktop settings diagnostics tab
- ui_surface: settings_page
- experimental:
- enabled: true
- release_tier: experimental
- build_modes: [debug, profile, release]
- description: Desktop settings experimental tab
- ui_surface: settings_page
- about:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Desktop settings about tab
- ui_surface: settings_page
experimental_canvas:
enabled: true
release_tier: experimental
@@ -443,36 +225,6 @@ web:
build_modes: [debug, profile, release]
description: Web assistant destination
ui_surface: web_shell
- tasks:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Web tasks destination
- ui_surface: web_shell
- skills:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Web skills destination
- ui_surface: web_shell
- nodes:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Web nodes destination
- ui_surface: web_shell
- secrets:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Web secrets destination
- ui_surface: web_shell
- ai_gateway:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Web LLM API destination
- ui_surface: web_shell
settings:
enabled: true
release_tier: stable
@@ -485,89 +237,89 @@ web:
release_tier: stable
build_modes: [debug, profile, release]
description: Web direct AI assistant mode
- ui_surface: web_assistant_page
+ ui_surface: assistant_page
+ local_gateway:
+ enabled: false
+ release_tier: experimental
+ build_modes: []
+ description: Web does not expose local gateway controls
+ ui_surface: assistant_page
relay_gateway:
enabled: true
release_tier: stable
build_modes: [debug, profile, release]
description: Web relay gateway assistant mode
- ui_surface: web_assistant_page
+ ui_surface: assistant_page
file_attachments:
enabled: true
release_tier: stable
build_modes: [debug, profile, release]
description: Web file attachment action in assistant composer
- ui_surface: web_assistant_page
+ ui_surface: assistant_page
multi_agent:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Web multi-agent toggle in assistant composer
- ui_surface: web_assistant_page
- local_gateway:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Web local gateway assistant mode
- ui_surface: web_assistant_page
+ enabled: false
+ release_tier: experimental
+ build_modes: []
+ description: Web multi-agent controls disabled
+ ui_surface: assistant_page
local_runtime:
enabled: false
release_tier: experimental
build_modes: []
- description: Web does not expose desktop runtime controls
- ui_surface: web_assistant_page
+ description: Web does not expose local runtime controls
+ ui_surface: assistant_page
settings:
- general:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Web settings general tab
- ui_surface: web_settings_page
gateway:
enabled: true
release_tier: stable
build_modes: [debug, profile, release]
- description: Web settings gateway tab
- ui_surface: web_settings_page
+ description: Web bridge and integration settings
+ ui_surface: settings_page
account_access:
enabled: true
- release_tier: beta
- build_modes: []
- description: Web does not expose account access section
- ui_surface: web_settings_page
+ release_tier: stable
+ build_modes: [debug, profile, release]
+ description: Web account access settings
+ ui_surface: settings_page
vault_server:
- enabled: true
- release_tier: beta
+ enabled: false
+ release_tier: experimental
build_modes: []
- description: Web does not expose vault server integration
- ui_surface: web_settings_page
+ description: Web vault server settings disabled
+ ui_surface: settings_page
gateway_self_hosted_base:
enabled: false
release_tier: experimental
build_modes: []
- description: Web does not expose self-hosted base connection controls
- ui_surface: web_settings_page
+ description: Web self-hosted gateway base controls disabled
+ ui_surface: settings_page
gateway_advanced_custom_mode:
enabled: false
release_tier: experimental
build_modes: []
- description: Web does not expose advanced custom override mode
- ui_surface: web_settings_page
+ description: Web advanced gateway override controls disabled
+ ui_surface: settings_page
gateway_setup_code:
enabled: false
release_tier: experimental
build_modes: []
- description: Web does not expose gateway setup code editor
- ui_surface: web_settings_page
- appearance:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Web settings appearance tab
- ui_surface: web_settings_page
- about:
- enabled: true
- release_tier: stable
- build_modes: [debug, profile, release]
- description: Web settings about tab
- ui_surface: web_settings_page
+ description: Web gateway setup code editor disabled
+ ui_surface: settings_page
+ experimental_canvas:
+ enabled: false
+ release_tier: experimental
+ build_modes: []
+ description: Web experimental canvas host toggle disabled
+ ui_surface: settings_page
+ experimental_bridge:
+ enabled: false
+ release_tier: experimental
+ build_modes: []
+ description: Web experimental bridge toggle disabled
+ ui_surface: settings_page
+ experimental_debug:
+ enabled: false
+ release_tier: experimental
+ build_modes: []
+ description: Web experimental debug runtime toggle disabled
+ ui_surface: settings_page
diff --git a/docs/architecture/xworkmate-core-module-inventory-2026-04-13.md b/docs/architecture/xworkmate-core-module-inventory-2026-04-13.md
index f94ced45..77ff5ee6 100644
--- a/docs/architecture/xworkmate-core-module-inventory-2026-04-13.md
+++ b/docs/architecture/xworkmate-core-module-inventory-2026-04-13.md
@@ -4,308 +4,214 @@ Last Updated: 2026-04-13
## Repo Context
-本文件按仓库真实代码形态盘点 XWorkmate 当前核心模块,不只描述“产品主链”,也显式标出仍然留在仓库中的受限入口、别名入口与陈旧残留。
+本仓库当前已经收敛到 `assistant + settings` 双端极简 surface。
-平台观察按两大产品面组织:
-
-- `Desktop APP`:`macOS / Linux / Windows`
-- `Mobile APP`:`iOS / Android`
-
-状态口径:
-
-- `Active`:当前 surface 仍然直接承载主链
-- `Gated`:代码存在,但是否可达取决于 manifest / platform / shell 映射
-- `Alias`:主要是跳转或折叠到别的当前页面
-- `Legacy-present`:仓库中仍有代码,但不属于当前主要 surface
-
-当前仓库需要特别注意的事实:
-
-- 桌面端真实壳层由 [`lib/app/app_shell_desktop.dart`](../../lib/app/app_shell_desktop.dart) 控制,当前页面栈只保留 `assistant + settings`
-- `workspace_page_registry.dart` 仍然保留 `tasks / skills / nodes / agents / mcpServer / clawHub / account`
-- `feature_flags.yaml`、`UiFeatureAccess.destinationMappingsInternal`、`AppShell._desktopDestinations` 不是完全同一口径
+- `Desktop APP` 与 `Mobile APP` 顶层都只保留 `assistant`、`settings`
+- `feature_flags.yaml` 是 surface 可见性的唯一声明源
+- `UiFeatureManifest / UiFeatureAccess / AppCapabilities` 负责把 manifest 解析成当前平台允许能力
+- `AppShell / MobileShell` 与 `workspace_page_registry.dart` 已同步收敛到同一口径,不再存在“manifest 允许但 shell/registry 仍保留旧页”的双重真源
+- `xworkmate-app` 当前只保留与 `xworkmate-bridge` C/S 主链直接相关的 surface、gate、controller、runtime 合同
+- 已删除独立 `tasks / skills / modules / mcp / claw_hub / secrets / ai_gateway / account` 页面入口、alias 路由与对应枚举残留
## Overall Layering
```mermaid
-flowchart LR
- subgraph APP["lib/app"]
- A1["workspace_page_registry.dart
workspace_navigation.dart
ui_feature_manifest_core.dart
ui_feature_manifest_fallback.dart"]
- A2["AppShell / AppControllerDesktop*"]
+flowchart TD
+ subgraph L1["Surface Visibility"]
+ A1["config/feature_flags.yaml"]
+ A2["UiFeatureManifest / UiFeatureAccess / AppCapabilities"]
+ A3["AppShell / MobileShell / workspace_page_registry.dart"]
+ A4["AssistantPage / SettingsPage"]
end
- subgraph FEATURES["lib/features"]
- F1["AssistantPage"]
- F2["SettingsPage"]
- F3["TasksPage"]
- F4["ModulesPage"]
- F5["SkillsPage"]
- F6["ClawHubPage"]
- F7["McpServerPage"]
- F8["MobileShell"]
+ subgraph L2["Local Orchestration"]
+ B1["AppControllerDesktop*"]
+ B2["SettingsController"]
+ B3["GatewaySessionsController"]
+ B4["GatewayChatController"]
+ B5["GatewayAgentsController"]
+ B6["DerivedTasksController"]
+ B7["SkillsController"]
end
- subgraph RUNTIME["lib/runtime"]
- R1["SettingsController"]
- R2["DerivedTasksController"]
- R3["GatewayAcpClient"]
- R4["ExternalCodeAgentAcpDesktopTransport"]
- R5["GoTaskServiceClient"]
- R6["AgentRegistry"]
- R7["MultiAgentOrchestrator"]
- R8["SettingsStore"]
+ subgraph L3["Bridge Contract"]
+ C1["GatewayAcpClient"]
+ C2["ExternalCodeAgentAcpDesktopTransport"]
+ C3["GoTaskServiceClient"]
+ C4["Managed bridge/account sync contract"]
end
- A1 --> F1
- A1 --> F2
- A1 --> F3
- A1 --> F4
- A1 --> F5
- A1 --> F6
- A1 --> F7
- A1 --> F8
+ subgraph L4["Upstream Adapters"]
+ D1["xworkmate-bridge"]
+ D2["Upstream providers / agent runtimes / ACP adapters"]
+ end
- A2 --> F1
- A2 --> F2
- A2 --> F8
- A2 --> R1
- A2 --> R2
- A2 --> R3
- A2 --> R4
- A2 --> R5
- A2 --> R6
- A2 --> R7
-
- F1 --> R2
- F1 --> R3
- F1 --> R4
- F1 --> R5
- F2 --> R1
- F2 --> R8
- F3 --> R2
- F4 --> R6
- F4 --> R7
- F7 --> R3
-
- R1 --> R8
- R4 --> R3
- R4 --> R5
- R7 --> R5
+ A1 --> A2 --> A3 --> A4
+ A4 --> B1
+ B1 --> B2
+ B1 --> B3
+ B1 --> B4
+ B1 --> B5
+ B1 --> B6
+ B1 --> B7
+ B1 --> C1
+ B1 --> C2
+ B1 --> C3
+ B2 --> C4
+ C1 --> D1
+ C2 --> D1
+ C3 --> D1
+ D1 --> D2
```
## Surface And Gate Flow
```mermaid
flowchart TD
- M1["config/feature_flags.yaml"]
- M2["fallbackUiFeatureManifestYamlInternal"]
- M3["UiFeatureManifestLoader / UiFeatureManifest"]
- M4["UiFeatureAccess.allowedDestinations
feature switches"]
-
- D1["Desktop APP
AppShell._desktopDestinations"]
- D2["Mobile APP
MobileShellTab / MobileWorkspaceLauncher"]
- D3["workspace_page_registry.dart"]
-
+ M1["feature_flags.yaml"]
+ M2["UiFeatureManifest / AppCapabilities"]
+ M3["AppShell / MobileShell"]
+ M4["workspace_page_registry.dart"]
P1["AssistantPage"]
P2["SettingsPage"]
- P3["TasksPage"]
- P4["ModulesPage"]
- P5["SkillsPage"]
- P6["McpServerPage"]
- P7["ClawHubPage"]
+ C1["AppController"]
+ R1["GoTaskServiceClient / GatewayAcpClient"]
+ B1["xworkmate-bridge"]
+ U1["upstream adapters"]
- M1 --> M3
+ M1 --> M2
M2 --> M3
- M3 --> M4
-
- M4 --> D1
- M4 --> D2
- M4 --> D3
-
- D1 --> P1
- D1 --> P2
-
- D2 --> P1
- D2 --> P2
- D2 --> P3
- D2 --> P4
- D2 --> P5
- D2 --> P6
- D2 --> P7
-
- D3 --> P1
- D3 --> P2
- D3 --> P3
- D3 --> P4
- D3 --> P5
- D3 --> P6
- D3 --> P7
+ M2 --> M4
+ M3 --> P1
+ M3 --> P2
+ M4 --> P1
+ M4 --> P2
+ P1 --> C1
+ P2 --> C1
+ C1 --> R1
+ R1 --> B1
+ B1 --> U1
```
+当前真实口径:
+
+- 没有 fallback manifest
+- 没有 `secrets -> settings`、`ai_gateway -> settings`、`account -> settings` 兼容别名
+- 没有独立 `modules`/`workspace hub`/`fake module matrix`
+
## Global Summary
-> `Current Status` 按模块组总体判断;平台差异在后面的 `Desktop APP`、`Mobile APP` 和详细表中展开。
+`Current Status` 按模块组总体判断;平台差异在后面的 `Desktop APP`、`Mobile APP` 和详细段落中展开。
-| Module Group | Primary Paths | App Entry | Feature/Page Class | Runtime/Core Classes | Core Functions / Extensions | Surface | Gate / Routing Source | Current Status |
-| --- | --- | --- | --- | --- | --- | --- | --- | --- |
-| Assistant | `lib/features/assistant/*`, `lib/app/app_shell_desktop.dart` | `AppShell`, `workspace_page_registry.dart` | `AssistantPage` | `GatewayAcpClient`, `ExternalCodeAgentAcpDesktopTransport`, `GoTaskServiceClient` | `submitPromptInternal`, `buildMainWorkspaceInternal`, `setAssistantExecutionTarget`, `buildExternalAcpRoutingForSessionInternal` | Desktop + Mobile | surface mapping + direct route | `Active` |
-| Settings | `lib/features/settings/*`, `lib/runtime/runtime_controllers_settings*` | `AppShell`, `navigateTo/openSettings` | `SettingsPage`, `SettingsAccountPanel` | `SettingsController`, `SettingsStore` | `_loginAccount`, `_syncAccount`, `loginAccount`, `syncAccountSettings`, `reloadDerivedStateInternal` | Desktop + Mobile | surface mapping + settings alias | `Active` |
-| Tasks | `lib/features/tasks/tasks_page.dart`, `lib/runtime/runtime_controllers_derived_tasks.dart` | `workspace_page_registry.dart`, dormant mobile/desktop route slots | `TasksPage` | `DerivedTasksController`, `DesktopTaskThreadRepository` | `recompute`, `taskItemsForTab`, `switchSession` | Registry present, shell not primary | desktop manifest / mobile manifest / surface mapping | `Gated` |
-| Agents | `lib/runtime/agent_registry.dart`, `lib/runtime/multi_agent_*` | Assistant runtime lane + `ModulesPage` tabs | `ModulesPage` (agents tab) | `AgentRegistry`, `MultiAgentOrchestrator`, `MultiAgentMountManager` | `register`, `listAgents`, `runCollaboration`, `runEngineerInternal` | Assistant runtime + dormant module UI | runtime only + surface mapping | `Active` |
-| Modules | `lib/features/modules/modules_page.dart` | `navigateTo`, `openModules`, `workspace_page_registry.dart` | `ModulesPage` | `AgentRegistry`, `InstancesController`, `SkillsController` | `_normalizeTab`, `_isTabVisible`, `_visibleTabs`, `openModules` | Registry present, current shell弱化 | surface mapping + desktop manifest | `Gated` |
-| MCP/ACP | `lib/features/mcp_server/mcp_server_page.dart`, `lib/runtime/*acp*` | Assistant execution lane, registry, routing extensions | `McpServerPage` | `GatewayAcpClient`, `GoTaskServiceClient`, `ExternalCodeAgentAcpDesktopTransport` | `resolveExternalAcpRouting`, `executeTask`, `loadExternalAcpCapabilities`, `resolveBridgeAcpEndpointInternal` | Runtime mainline + dormant MCP page | runtime only + desktop manifest | `Active` |
-| Skills / ClawHub | `lib/features/skills/skills_page.dart`, `lib/features/claw_hub/claw_hub_page.dart` | registry + mobile workspace launcher | `SkillsPage`, `ClawHubPage` | `SkillDirectoryAccessService`, `SkillsController` | `refresh`, `_resolveSelectedSkill`, `executeCommandInternal` | Skills有数据面,ClawHub偏占位壳 | mobile manifest / desktop manifest | `Gated` |
-| Mobile Workspace | `lib/features/mobile/*` | compact mobile path in `AppShell`, `MobileShell` | `MobileShell`, `MobileWorkspaceLauncherInternal` | shared `AppController`, `DerivedTasksController` | `tabForDestinationInternal`, `selectTabInternal`, `buildCurrentPageInternal`, `showPairingGuidePageFlowInternal` | iOS + Android | mobile manifest + surface mapping | `Active` |
-| Feature Manifest Fallback | `config/feature_flags.yaml`, `lib/app/ui_feature_manifest*.dart` | `UiFeatureManifestLoader`, `featuresFor()` | N/A | `UiFeatureManifest`, `UiFeatureAccess` | `forPlatform`, `allowedDestinations`, `sanitizeSettingsTab`, `load()` | Cross-platform | direct route | `Active` |
-
-## Desktop APP (`macOS / Linux / Windows`)
-
-### Desktop Surface Summary
-
-| Concern | Current Repo Truth | Notes |
+| Module Group | Current Status | Current Repo Truth |
| --- | --- | --- |
-| Main shell | `AppShell` desktop path | 当前实际桌面页面栈只构建 `assistant + settings` |
-| Dormant registry pages | `TasksPage`, `SkillsPage`, `ModulesPage`, `McpServerPage`, `ClawHubPage` | 仍保留在 `workspace_page_registry.dart` |
-| Runtime richness | Assistant + bridge + ACP + multi-agent 最完整 | Desktop 是唯一完整本地 runtime / external ACP 宿主 |
-| Risk | manifest / registry / shell 三份口径并存 | 结构评审重点应放在“单一事实源” |
+| Desktop APP | Active | 顶层 only `assistant + settings` |
+| Mobile APP | Active | 顶层 only `assistant + settings` |
+| Assistant | Active | 保留 bridge/runtime 主链与任务/技能数据面 |
+| Settings | Active | 保留 bridge/account/integration 主链 |
+| Tasks | Removed surface | 不再有独立页面,仅保留 assistant/task-state 数据 |
+| Modules | Removed surface | 不再有独立页面、tab、registry、alias |
-## Mobile APP (`iOS / Android`)
+## Desktop APP (macOS / Linux / Windows)
-### Mobile Surface Summary
+Status: `Active`
-| Concern | Current Repo Truth | Notes |
-| --- | --- | --- |
-| Main shell | `MobileShell` | 当前主入口是 `assistant / workspace / secrets / settings` |
-| Workspace hub | `MobileWorkspaceLauncherInternal` | 实际条目由 `features.allowedDestinations.contains(...)` 决定 |
-| Pairing / bridge | `mobile_gateway_pairing_guide_page.dart` + setup-code flow | 移动端是典型 bridge thin client |
-| Risk | `MobileShellTab` 与 manifest 允许项之间存在保留目的地 | 例如 `tasks` tab 枚举仍在,但 manifest 已关闭 |
+- 桌面顶层 shell 只保留 `assistant`、`settings`
+- `workspace_page_registry.dart` 只注册 `assistant`、`settings`
+- 不再保留 `tasks / skills / modules / mcp / claw_hub / account` 独立桌面入口
+- 设置相关 bridge/account/integration 操作全部收口到 `SettingsPage`
+- Assistant 仍然承载完整 desktop bridge/runtime 主链
+
+## Mobile APP (iOS / Android)
+
+Status: `Active`
+
+- 移动端顶层 tab 只保留 `assistant`、`settings`
+- 删除 `workspace / tasks / secrets` 顶层 tab
+- 删除 `MobileWorkspaceLauncherInternal`
+- 配对、Bridge connect、setup code 等流程保留为 `settings` detail flow 与 mobile-safe strip/sheet 能力,不再占独立 top-level surface
## Assistant
-| Path | Layer | Primary Class / Extension | Key Functions / Methods | Depends On | Used By | Surface | Gate | Status | Notes |
-| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
-| `lib/app/app_shell_desktop.dart` | app | `AppShell`, `_AppShellState` | `_desktopDestinations`, `_mobileDestinations`, `_createSidebarConversation`, `_pageForDestination` | `AppController`, `workspace_page_registry`, `UiFeatureAccess` | Desktop shell, compact mobile path | Desktop + Mobile | surface mapping | `Active` | 桌面实际只渲染 `assistant + settings` |
-| `lib/app/workspace_page_registry.dart` | app | `WorkspacePageSpec` | `workspacePageSpecsInternal`, `buildWorkspacePage` | feature pages | `AppShell`, `MobileShell` | Desktop + Mobile | direct route | `Active` | registry 仍保留多余页面规格 |
-| `lib/features/assistant/assistant_page_main.dart` | feature | `AssistantPage`, `AssistantPageStateInternal` | `build`, `handleComposerContentHeightChangedInternal` | `AppController`, runtime models, focus/artifact widgets | registry, `AppShell` | Desktop + Mobile | direct route | `Active` | 对话主页面壳层 |
-| `lib/features/assistant/assistant_page_state_closure.dart` | feature | `AssistantPageStateClosureInternal` | `buildMainWorkspaceInternal` | `AssistantPageStateInternal`, widgets, controller | `AssistantPage` | Desktop + Mobile | direct route | `Active` | 负责主工作区布局、conversation/composer 拼装 |
-| `lib/features/assistant/assistant_page_state_actions.dart` | feature | `AssistantPageStateActionsInternal` | `pickAttachmentsInternal`, `submitPromptInternal`, `buildAttachmentPayloadsInternal`, `pickAutoAgentInternal` | `AppController`, file selector, runtime models | `AssistantPage` | Desktop + Mobile | direct route | `Active` | 助手主要动作闭包 |
-| `lib/app/app_controller_desktop_workspace_execution.dart` | app | `AppControllerDesktopWorkspaceExecution` | `setAssistantExecutionTarget`, `setAssistantSingleAgentProvider`, `applyAssistantExecutionTargetInternal` | `AppController`, thread binding, settings runtime | `AssistantPage` | Desktop | runtime only | `Active` | 桌面执行 target / provider / thread 绑定主链 |
-| `lib/app/app_controller_desktop_external_acp_routing.dart` | app | `AppControllerDesktopExternalAcpRouting` | `buildExternalAcpRoutingForSessionInternal` | assistant thread records, `GoTaskServiceClient` models | Desktop assistant execution | Desktop | runtime only | `Active` | 把 session 级显式选择折叠成 ACP routing config |
-| `lib/widgets/assistant_focus_panel.dart` + `assistant_artifact_sidebar.dart` | feature | Focus / Artifact side panels | panel build/render helpers | `AssistantArtifactSnapshot`, controller focus state | `AssistantPage` | Desktop + Mobile | direct route | `Active` | 属于 assistant 主链侧边闭包,不是独立模块 |
+Status: `Active`
+
+保留的主链 runtime / controller:
+
+- `GatewayAcpClient`
+- `ExternalCodeAgentAcpDesktopTransport`
+- `GoTaskServiceClient`
+- `GatewaySessionsController`
+- `GatewayChatController`
+- `GatewayAgentsController`
+- `DerivedTasksController`
+- `SkillsController`
+
+当前 Assistant 事实:
+
+- provider catalog 只来自 bridge capabilities,不再恢复任何 preset / backfill / fallback provider truth
+- task state 仍在 assistant 内被消费,但不再拥有独立 `TasksPage`
+- skills 数据仍在 assistant 内被消费,但不再拥有独立 `SkillsPage`
+- assistant focus 只保留仍有真实落点的 `settings / language / theme`
## Settings
-| Path | Layer | Primary Class / Extension | Key Functions / Methods | Depends On | Used By | Surface | Gate | Status | Notes |
-| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
-| `lib/features/settings/settings_page_core.dart` | feature | `SettingsPage`, `_SettingsPageState` | `_saveAccountProfile`, `_loginAccount`, `_syncAccount`, `_verifyAccountMfa`, `_refreshBridgeCapabilities` | `AppController`, `SettingsController`, `SettingsAccountPanel` | registry, `AppShell` | Desktop + Mobile | surface mapping | `Active` | 当前设置主页面 |
-| `lib/features/settings/settings_account_panel.dart` | feature | `SettingsAccountPanel`, `_SignedOutAccountPanel`, `_PendingMfaAccountPanel`, `_SignedInAccountPanel` | `build` | `SettingsSnapshot`, `AccountSyncState` | `SettingsPage` | Desktop + Mobile | direct route | `Active` | 账户登录 / MFA / 同步 UI 壳层 |
-| `lib/runtime/runtime_controllers_settings.dart` | runtime | `SettingsController` | `initialize`, `refreshDerivedState`, `saveSnapshot`, `saveGatewaySecrets` | `SettingsStore`, secure refs, runtime models | `SettingsPage`, app runtime | Desktop + Mobile | runtime only | `Active` | 设置控制器根对象 |
-| `lib/runtime/runtime_controllers_settings_account.dart` | runtime | `SettingsControllerAccountExtension` | `loginAccount`, `verifyAccountMfa`, `syncAccountSettings`, `reloadDerivedStateInternal`, `loadEffectiveGatewayToken` | `SettingsController`, secure storage | `SettingsPage`, bridge/account flow | Desktop + Mobile | runtime only | `Active` | 对外暴露账户同步与 secret 解析 API |
-| `lib/runtime/runtime_controllers_settings_account_impl.dart` | runtime | account impl helpers | `loginAccountSettingsInternal`, `completeAccountSignInSettingsInternal`, `restoreAccountSessionSettingsInternal`, `syncAccountSettingsInternal` | `AccountRuntimeClient`, `SettingsStore` | `SettingsControllerAccountExtension` | Desktop + Mobile | runtime only | `Active` | 当前 bridge/account 合同链核心 |
-| `lib/runtime/settings_store.dart` | runtime | `SettingsStore` | snapshot / secure refs / account session persistence API | local storage, secure storage | `SettingsController` | Desktop + Mobile | runtime only | `Active` | 设置、账号、线程元数据统一存储层 |
+Status: `Active`
+
+当前设置面已经收敛为单一 bridge/settings 主链:
+
+- `SettingsTab` 只保留 `gateway`
+- `SettingsDetailPage` 只保留 `gatewayConnection`
+- `SettingsNavigationContext` 只保留当前真实 detail flow 所需字段
+- 账户登录、MFA、同步与 managed bridge contract 回写都收口在 `SettingsPage + SettingsAccountPanel`
+
+已删除的旧设置残留:
+
+- `ModulesTab`
+- `SecretsTab`
+- `AiGatewayTab`
+- `aiGatewayIntegration`
+- `externalAgents`
+- `diagnosticsAdvanced`
+- `vaultProvider`
## Tasks
-| Path | Layer | Primary Class / Extension | Key Functions / Methods | Depends On | Used By | Surface | Gate | Status | Notes |
-| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
-| `lib/features/tasks/tasks_page.dart` | feature | `TasksPage`, `_TasksPageState` | `build`, `_matchesQuery`, `_resolveSelectedTask` | `AppController`, `DerivedTasksController` | registry | Desktop + Mobile registry | desktop manifest / mobile manifest | `Gated` | 页面存在,但当前主 shell 不再把它作为首要入口 |
-| `lib/runtime/runtime_controllers_derived_tasks.dart` | runtime | `DerivedTasksController` | `recompute`, `statusForSessionInternal`, `timeLabelInternal`, `durationLabelInternal` | sessions, `TaskThread`, scheduler data | `TasksPage`, mobile workspace hero stats | Cross-platform | runtime only | `Active` | 任务聚合的真实数据源 |
-| `lib/app/task_thread_repositories.dart` | app | `DesktopTaskThreadRepository`, `WebTaskThreadRepository` | `replace`, `replaceAll`, `removeWhere`, `flush` | `TaskThread` | app thread/session flow | Desktop + Web | runtime only | `Active` | 任务线程持久化仓储,不是页面但直接供 task 聚合链路使用 |
-| `lib/app/app_controller_desktop_thread_sessions.dart` | app | `AppControllerDesktopThreadSessions` | session switch / assistant session normalization APIs | `AppController`, task repositories | Assistant + tasks data source | Desktop | runtime only | `Active` | 任务页依赖其提供 session/thread 事实 |
+Status: `Removed surface`
-## Agents
+保留范围仅限 assistant/task-state 数据面:
-| Path | Layer | Primary Class / Extension | Key Functions / Methods | Depends On | Used By | Surface | Gate | Status | Notes |
-| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
-| `lib/runtime/agent_registry.dart` | runtime | `AgentRegistry` | `register`, `unregister`, `listAgents`, `clearRegistration` | `GatewayRuntime` | assistant runtime, modules agent tab | Cross-platform runtime | runtime only | `Active` | 代理发现与注册中心 |
-| `lib/runtime/multi_agent_orchestrator_core.dart` | runtime | `MultiAgentOrchestrator` | `updateConfig`, `enable`, `disable`, `runCollaboration`, `abort` | `MultiAgentConfig`, CLI/HTTP factories | assistant multi-agent flow | Desktop-focused runtime | runtime only | `Active` | 多代理协作核心编排器 |
-| `lib/runtime/multi_agent_orchestrator_workflow.dart` | runtime | `MultiAgentOrchestratorWorkflowInternal` | `runArchitectInternal`, `runEngineerInternal`, `runTesterInternal`, `runFixInternal`, `runCliPromptInternal` | orchestrator core, CLI tools | `MultiAgentOrchestrator` | Desktop runtime | runtime only | `Active` | 角色工作流实现层 |
-| `lib/runtime/multi_agent_mounts.dart` | runtime | `MultiAgentMountManager`, `CliMountAdapter` | `reconcile`, `_reconcileLocally`, adapter `reconcile()` | Codex/Opencode/Aris bridges | multi-agent config sync | Desktop runtime | runtime only | `Active` | 多 CLI 挂载目标协调层 |
-| `lib/runtime/runtime_models_multi_agent.dart` | runtime | `MultiAgentConfig`, `ManagedSkillEntry`, `ManagedMcpServerEntry` | config/model copy & state carriers | runtime models | orchestrator + settings + assistant | Cross-platform models | runtime only | `Active` | agents 模块的配置与状态模型 |
-| `lib/features/modules/modules_page.dart` | feature | `ModulesPage` agents tab shell | `_normalizeTab`, `_isTabVisible` | `AgentRegistry`, controller state | registry route | Desktop registry | desktop manifest / surface mapping | `Gated` | 代理 UI 与 runtime core 是两层,不应混为一个模块 |
+- `DerivedTasksController`
+- `DesktopTaskThreadRepository`
+- assistant 内部 task rail / session/task 聚合
+
+已删除:
+
+- `TasksPage`
+- 顶层 `WorkspaceDestination.tasks`
+- `MobileShellTab.tasks`
## Modules
-| Path | Layer | Primary Class / Extension | Key Functions / Methods | Depends On | Used By | Surface | Gate | Status | Notes |
-| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
-| `lib/features/modules/modules_page.dart` | feature | `ModulesPage`, `_ModulesPageState` | `_normalizeTab`, `_isTabVisible`, `_visibleTabs`, `_tabForLabel`, `build` | `AppController`, `UiFeatureAccess`, agents/instances/skills data | registry | Desktop registry | surface mapping + desktop manifest | `Gated` | 现存聚合页;当前桌面主 shell 不直接暴露 |
-| `lib/app/app_controller_desktop_navigation.dart` | app | `AppControllerDesktopNavigation` | `navigateTo`, `openModules`, `openSettings`, `openSecrets`, `openAiGateway` | `capabilities`, settings/module tabs | shells, pages | Desktop + Mobile controller API | surface mapping + settings alias | `Active` | 模块/设置别名折叠逻辑在这里 |
-| `lib/app/workspace_navigation.dart` | app | breadcrumb/navigation helpers | `buildWorkspaceBreadcrumbs`, `buildSettingsBreadcrumbs`, `openSettingsNavigationContext` | `AppController`, nav context | feature pages | Desktop + Mobile | direct route | `Active` | 模块页与设置页共享导航上下文装配 |
-| `lib/app/workspace_page_registry.dart` | app | destination -> page registry | `workspacePageSpecsInternal`, `buildWorkspacePage` | all feature pages | shells | Desktop + Mobile | direct route | `Active` | `nodes`/`agents` 仍然映射回 `ModulesPage` |
+Status: `Removed surface`
-## MCP / ACP
+已删除:
-| Path | Layer | Primary Class / Extension | Key Functions / Methods | Depends On | Used By | Surface | Gate | Status | Notes |
-| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
-| `lib/features/mcp_server/mcp_server_page.dart` | feature | `McpServerPage` | `build` | `AppController.connectors`, detail panel | registry route | Desktop registry | desktop manifest | `Gated` | 页面存在,但当前桌面主 shell 不直接显示 |
-| `lib/runtime/acp_endpoint_paths.dart` | runtime | `AcpEndpointPaths` | ACP path constants | runtime URI builders | gateway/desktop transport | Cross-platform runtime | runtime only | `Active` | ACP 端点路径单点定义 |
-| `lib/runtime/gateway_acp_client.dart` | runtime | `GatewayAcpClient` | capability load, session RPC, notification/result merge | HTTP / ACP RPC | assistant + bridge runtime | Cross-platform runtime | runtime only | `Active` | ACP 主客户端 |
-| `lib/runtime/go_task_service_client.dart` | runtime | request/result/value models + transport abstractions | `toExternalAcpParams`, `goTaskServiceResultFromAcpResponse`, `goTaskServiceUpdateFromAcpNotification` | ACP payload contracts | desktop transport, app controller | Cross-platform runtime | runtime only | `Active` | 任务执行统一协议面 |
-| `lib/runtime/external_code_agent_acp_desktop_transport.dart` | runtime | `ExternalCodeAgentAcpDesktopTransport` | `loadExternalAcpCapabilities`, `resolveExternalAcpRouting`, `executeTask`, `cancelTask`, `closeTask` | `GatewayAcpClient`, endpoint resolver | desktop assistant runtime | Desktop | runtime only | `Active` | 桌面 external ACP transport |
-| `lib/app/app_controller_desktop_external_acp_routing.dart` | app | `AppControllerDesktopExternalAcpRouting` | `buildExternalAcpRoutingForSessionInternal` | assistant thread state, `GoTaskServiceClient` models | desktop assistant execution | Desktop | runtime only | `Active` | session 事实 -> ACP routing config |
-| `lib/app/app_controller_desktop_runtime_helpers.dart` | app | `AppControllerDesktopRuntimeHelpers` | `resolveBridgeAcpEndpointInternal` and runtime resolver helpers | settings/account sync, runtime models | desktop assistant runtime | Desktop | runtime only | `Active` | Bridge 端点解析与桌面 runtime 帮助函数 |
+- `ModulesPage`
+- `SkillsPage`
+- `McpServerPage`
+- `ClawHubPage`
+- `WorkspaceDestination.skills / nodes / agents / mcpServer / clawHub`
+- `openModules` / module alias navigation API
+- `workspace_page_registry` 中所有模块类 destination spec
-## Skills / ClawHub
+同时清理的孤儿 controller:
-| Path | Layer | Primary Class / Extension | Key Functions / Methods | Depends On | Used By | Surface | Gate | Status | Notes |
-| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
-| `lib/features/skills/skills_page.dart` | feature | `SkillsPage`, `_SkillsPageState` | `build`, `_matchesQuery`, `_resolveSelectedSkill` | `AppController.skills`, `SkillsController` | registry route | Desktop registry / mobile workspace registry | desktop manifest / mobile manifest | `Gated` | 技能页是真数据页,但当前主 shell 不直接暴露 |
-| `lib/features/claw_hub/claw_hub_page.dart` | feature | `ClawHubPage`, `ClawHubPageStateInternal` | `executeCommandInternal`, `handleSearchInternal`, `handleInstallInternal`, `handleUpdateInternal` | local controllers only | registry route | Desktop registry / mobile workspace registry | desktop manifest / mobile manifest | `Legacy-present` | 更像 UI placeholder shell,不是当前真实后端主链 |
-| `lib/runtime/skill_directory_access.dart` | runtime | `SkillDirectoryAccessService` + platform impls | `pickDirectory`, `grant`, platform-specific access methods | file selector / macOS access | skills install/import flows | Cross-platform runtime | runtime only | `Active` | 技能目录访问能力的真实后端 |
-| `lib/features/mobile/mobile_shell_workspace.dart` | feature | `MobileWorkspaceLauncherInternal` | workspace entries build via `features.allowedDestinations.contains(...)` | `UiFeatureAccess`, controller | mobile workspace hub | Mobile | mobile manifest | `Gated` | `skills / nodes / agents / mcp / claw_hub` 都在这里被最终筛掉或放行 |
-
-## Mobile Workspace
-
-| Path | Layer | Primary Class / Extension | Key Functions / Methods | Depends On | Used By | Surface | Gate | Status | Notes |
-| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
-| `lib/features/mobile/mobile_shell_core.dart` | feature | `MobileShell`, `MobileShellStateInternal`, `MobileShellTab` | `tabForDestinationInternal`, `selectTabInternal`, `buildCurrentPageInternal`, `showPairingGuidePageFlowInternal`, `showMobileSafeSheetInternal` | `AppController`, `workspace_page_registry`, feature manifest | iOS + Android | mobile shell | mobile manifest + surface mapping | `Active` | 移动端主壳层 |
-| `lib/features/mobile/mobile_shell_workspace.dart` | feature | `MobileWorkspaceLauncherInternal` | workspace entry filtering and hub build | `UiFeatureAccess`, controller stats | `MobileShell` | iOS + Android | mobile manifest | `Active` | 工作区入口聚合面 |
-| `lib/features/mobile/mobile_shell_nav.dart` | feature | `BottomPillNavInternal` | bottom nav build | `MobileShellTab` state | `MobileShell` | iOS + Android | direct route | `Active` | 移动底部导航壳 |
-| `lib/features/mobile/mobile_shell_sheet.dart` | feature | `MobileSafeSheetInternal` | connection/health sheet build | controller runtime state | `MobileShell` | iOS + Android | direct route | `Active` | 移动安全/连接抽屉面 |
-| `lib/features/mobile/mobile_shell_strip.dart` | feature | `MobileSafeStripInternal` | top strip build | controller runtime state | `MobileShell` | iOS + Android | direct route | `Active` | 移动顶部状态条 |
-| `lib/features/mobile/mobile_gateway_pairing_guide_page.dart` | feature | `MobileGatewayPairingGuidePage`, `_MobileGatewayQrScannerPageState` | pairing guide + QR setup flow | controller connect/setup-code APIs | `MobileShell` | iOS + Android | direct route | `Active` | bridge 配对引导页 |
-
-## Feature Manifest Fallback
-
-| Path | Layer | Primary Class / Extension | Key Functions / Methods | Depends On | Used By | Surface | Gate | Status | Notes |
-| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
-| `config/feature_flags.yaml` | app | manifest source file | flag definitions by platform/module/feature | YAML loader | `UiFeatureManifestLoader` | Cross-platform | direct route | `Active` | 仓库主 manifest 源 |
-| `lib/app/ui_feature_manifest_core.dart` | app | `UiFeatureManifest`, `UiFeatureAccess`, `UiFeatureManifestLoader` | `forPlatform`, `allowedDestinations`, `availableSettingsTabs`, `sanitizeExecutionTarget`, `load` | manifest YAML, runtime models | `AppController.featuresFor()` | Cross-platform | direct route | `Active` | 解析与运行时访问层 |
-| `lib/app/ui_feature_manifest_fallback.dart` | app | `fallbackUiFeatureManifestYamlInternal` | embedded fallback YAML | `UiFeatureManifest.fromYamlString` | loader fallback path | Cross-platform | direct route | `Active` | fallback 定义仍保留完整多平台矩阵 |
-| `lib/app/workspace_page_registry.dart` | app | page registry | `workspacePageSpecsInternal`, `buildWorkspacePage` | feature pages | shells | Cross-platform | surface mapping | `Active` | manifest 并不自动裁剪 registry |
-| `lib/app/app_shell_desktop.dart` | app | `AppShell` shell filter | `_desktopDestinations`, `_mobileDestinations` | controller capabilities, registry | root shell | Desktop + Mobile | surface mapping | `Active` | 当前真实 surface 比 manifest/registry 更窄 |
-
-## Five-Platform Architecture Review
-
-| Platform | Current Shape | Architecture Review | Recommendation |
-| --- | --- | --- | --- |
-| `macOS` | 最完整的 desktop runtime 宿主;assistant + settings 是当前真实桌面主链 | 本地 workspace 绑定、external ACP、bridge 合同链、multi-agent 都优先围绕 macOS 成熟 | 把 macOS 明确设为 desktop reference platform,并补一条端到端 smoke baseline:assistant send -> ACP routing -> working directory -> artifact/result |
-| `Linux` | 共享 desktop Flutter 壳,但未见与 macOS 同强度的平台专项收口 | 进程启动、路径、secure storage、文件选择、CLI 挂载更容易出现平台漂移 | 把 `DesktopPlatformService`、路径规范化、CLI 启动能力做成显式 Linux 验证层,补最小功能矩阵测试 |
-| `Windows` | 共享 desktop Flutter 壳,但 shell quoting / path separator 风险最高 | task thread working directory、subprocess 参数转义、存储后端兼容性是主要风险点 | 为 Windows 增加工作目录/命令转义专项验证,避免把 macOS 假设直接推广到 Windows |
-| `iOS` | 移动端主形态是 bridge thin client;本地 runtime 默认关闭 | 当前强项是配对、设置、账户、bridge setup code;弱项是 dormant workspace 目的地仍保留在模型里 | 保持 iOS 只承载 assistant + workspace hub + settings 主链,并把 dormant destinations 从壳层枚举进一步剥离 |
-| `Android` | 与 iOS 共用 mobile shell,但扫描/权限/系统行为波动更大 | QR pairing、剪贴板、文件选择、通知/后台行为更容易受系统差异影响 | 把扫码、setup-code、连接恢复做成 Android 专项回归集合,确保 bridge thin-client 路线稳定 |
+- `InstancesController`
+- `ConnectorsController`
## Architecture Review Suggestions
-1. **统一 surface 单一事实源**
- 目前 `feature_flags.yaml`、`UiFeatureAccess.destinationMappingsInternal`、`workspace_page_registry.dart`、`AppShell._desktopDestinations` 同时参与裁剪。建议收敛成“manifest -> access -> shell”单链,registry 只保留已允许的规格,避免同一页面在三个地方各自决定是否可达。
-
-2. **显式区分“当前 surface”与“仓库保留页”**
- `TasksPage`、`SkillsPage`、`ModulesPage`、`McpServerPage`、`ClawHubPage` 目前都属于“代码存在,但当前主壳层不主推”的状态。建议在目录或文档上明确 `current` / `dormant` / `legacy-present`,降低维护误判。
-
-3. **把 runtime core 与 page shell 拆开评审**
- `Agents`、`MCP/ACP`、`Skills` 的真实主链大量在 `lib/runtime` 与 `AppControllerDesktop*` 扩展里,而不在页面壳层。后续评审应以 transport / controller / protocol 为主,不要被 `ModulesPage` 这类聚合页误导。
-
-4. **确认 ClawHub 的产品定位**
- 当前 `ClawHubPage` 更像一个本地命令台 / placeholder shell,而不是与 `SkillsPage` 同等级的真实数据面。建议要么升级为真实 marketplace backend 面,要么明确标记为 legacy tool shell。
-
-5. **让生成文档与当前 manifest 同步**
- 仓库已有 `docs/plans/xworkmate-ui-feature-matrix.md`,但它描述的 flag 状态已经落后于当前实现。建议把 feature matrix / inventory 变成可重复生成文档,避免“文档说 enabled,壳层却不显示”的结构漂移。
-
-## Conclusion: 主链 vs 受限 vs 兼容
-
-- `主链 / Active`:`Assistant`、`Settings`、`MCP/ACP runtime`、`Agent runtime core`、`Mobile Workspace`、`Feature Manifest Fallback`
-- `受限 / Gated`:`TasksPage`、`SkillsPage`、`ModulesPage`、`McpServerPage` 以及 mobile workspace 中的 `skills/nodes/agents/mcp_server/claw_hub`
-- `兼容壳 / Alias`:`navigateTo(aiGateway|secrets)` -> `openSettings(gateway)`、`WorkspaceDestination.account` -> `Settings`
-- `陈旧残留 / Legacy-present`:`ClawHubPage` 及其命令台式实现、仍保留在 registry 但不属于当前桌面主页面栈的页面规格
-
-对实现者最重要的结论只有一条:**当前仓库的真实主链不是“所有页面都还在线”,而是“页面、manifest、registry、shell 四层并存,真正当前可达的 surface 已经明显窄于仓库残留代码面”。**
+- 继续坚持 `feature_flags.yaml -> UiFeatureManifest/AppCapabilities -> Shell/Registry` 的单一 surface 事实源,不再引入第二套 alias 或 dormant registry。
+- `xworkmate-app` 不再维护独立模块壳;任何新的 bridge 能力都只能落到 `assistant` 或 `settings`,不能恢复 `tasks/modules/...` 独立 page matrix。
+- provider、routing、bridge endpoint、managed account sync 的真源继续归 `xworkmate-bridge` 合同与同步链拥有,app 只做消费与最小本地编排。
+- 不再维护兼容 alias、休眠 destination、伪模块矩阵;发现新的 `legacy / fallback / compat` 残留时,默认动作仍然是删除而不是保留占位。
diff --git a/lib/app/app_capabilities.dart b/lib/app/app_capabilities.dart
index de591ce3..b9fbc0be 100644
--- a/lib/app/app_capabilities.dart
+++ b/lib/app/app_capabilities.dart
@@ -8,7 +8,6 @@ class AppCapabilities {
required this.supportsLocalGateway,
required this.supportsRelayGateway,
required this.supportsDesktopRuntime,
- required this.supportsDiagnostics,
});
final Set allowedDestinations;
@@ -16,7 +15,6 @@ class AppCapabilities {
final bool supportsLocalGateway;
final bool supportsRelayGateway;
final bool supportsDesktopRuntime;
- final bool supportsDiagnostics;
bool supportsDestination(WorkspaceDestination destination) {
return allowedDestinations.contains(destination);
@@ -29,7 +27,6 @@ class AppCapabilities {
supportsLocalGateway: access.supportsLocalGateway,
supportsRelayGateway: access.supportsRelayGateway,
supportsDesktopRuntime: access.supportsDesktopRuntime,
- supportsDiagnostics: access.supportsDiagnostics,
);
}
}
diff --git a/lib/app/app_controller_desktop_core.dart b/lib/app/app_controller_desktop_core.dart
index e4e9e96c..fd115b6c 100644
--- a/lib/app/app_controller_desktop_core.dart
+++ b/lib/app/app_controller_desktop_core.dart
@@ -166,15 +166,9 @@ class AppController extends ChangeNotifier {
chatControllerInternal = GatewayChatController(
runtimeCoordinatorInternal.gateway,
);
- instancesControllerInternal = InstancesController(
- runtimeCoordinatorInternal.gateway,
- );
skillsControllerInternal = SkillsController(
runtimeCoordinatorInternal.gateway,
);
- connectorsControllerInternal = ConnectorsController(
- runtimeCoordinatorInternal.gateway,
- );
modelsControllerInternal = ModelsController(
runtimeCoordinatorInternal.gateway,
settingsControllerInternal,
@@ -253,9 +247,7 @@ class AppController extends ChangeNotifier {
agentsControllerInternal.dispose();
sessionsControllerInternal.dispose();
chatControllerInternal.dispose();
- instancesControllerInternal.dispose();
skillsControllerInternal.dispose();
- connectorsControllerInternal.dispose();
modelsControllerInternal.dispose();
cronJobsControllerInternal.dispose();
devicesControllerInternal.dispose();
@@ -279,9 +271,7 @@ class AppController extends ChangeNotifier {
late final GatewayAgentsController agentsControllerInternal;
late final GatewaySessionsController sessionsControllerInternal;
late final GatewayChatController chatControllerInternal;
- late final InstancesController instancesControllerInternal;
late final SkillsController skillsControllerInternal;
- late final ConnectorsController connectorsControllerInternal;
late final ModelsController modelsControllerInternal;
late final CronJobsController cronJobsControllerInternal;
late final DevicesController devicesControllerInternal;
@@ -330,10 +320,7 @@ class AppController extends ChangeNotifier {
WorkspaceDestination destinationInternal = WorkspaceDestination.assistant;
ThemeMode themeModeInternal = ThemeMode.light;
AppSidebarState sidebarStateInternal = AppSidebarState.expanded;
- ModulesTab modulesTabInternal = ModulesTab.nodes;
- SecretsTab secretsTabInternal = SecretsTab.vault;
- AiGatewayTab aiGatewayTabInternal = AiGatewayTab.models;
- SettingsTab settingsTabInternal = SettingsTab.general;
+ SettingsTab settingsTabInternal = SettingsTab.gateway;
SettingsDetailPage? settingsDetailInternal;
SettingsNavigationContext? settingsNavigationContextInternal;
DetailPanelData? detailPanelInternal;
@@ -402,9 +389,6 @@ class AppController extends ChangeNotifier {
);
ThemeMode get themeMode => themeModeInternal;
AppSidebarState get sidebarState => sidebarStateInternal;
- ModulesTab get modulesTab => modulesTabInternal;
- SecretsTab get secretsTab => secretsTabInternal;
- AiGatewayTab get aiGatewayTab => aiGatewayTabInternal;
SettingsTab get settingsTab => settingsTabInternal;
SettingsDetailPage? get settingsDetail => settingsDetailInternal;
SettingsNavigationContext? get settingsNavigationContext =>
@@ -451,9 +435,7 @@ class AppController extends ChangeNotifier {
MultiAgentMountManager get multiAgentMountManager =>
multiAgentMountManagerInternal;
GatewayChatController get chatController => chatControllerInternal;
- InstancesController get instancesController => instancesControllerInternal;
SkillsController get skillsController => skillsControllerInternal;
- ConnectorsController get connectorsController => connectorsControllerInternal;
ModelsController get modelsController => modelsControllerInternal;
CronJobsController get cronJobsController => cronJobsControllerInternal;
DevicesController get devicesController => devicesControllerInternal;
@@ -487,11 +469,7 @@ class AppController extends ChangeNotifier {
sessionsControllerInternal.sessions;
List get assistantSessions =>
assistantSessionsInternal();
- List get instances =>
- instancesControllerInternal.items;
List get skills => skillsControllerInternal.items;
- List get connectors =>
- connectorsControllerInternal.items;
List get models => modelsControllerInternal.items;
List get cronJobs => cronJobsControllerInternal.items;
GatewayDevicePairingList get devices => devicesControllerInternal.items;
@@ -697,9 +675,6 @@ class AppController extends ChangeNotifier {
void navigateHome() => AppControllerDesktopNavigation(this).navigateHome();
- void openModules({ModulesTab tab = ModulesTab.nodes}) =>
- AppControllerDesktopNavigation(this).openModules(tab: tab);
-
void openSettings({
SettingsTab tab = SettingsTab.gateway,
SettingsDetailPage? detail,
diff --git a/lib/app/app_controller_desktop_gateway.dart b/lib/app/app_controller_desktop_gateway.dart
index badbb2d6..0682be39 100644
--- a/lib/app/app_controller_desktop_gateway.dart
+++ b/lib/app/app_controller_desktop_gateway.dart
@@ -239,9 +239,7 @@ extension AppControllerDesktopGateway on AppController {
await agentsControllerInternal.refresh();
await sessionsControllerInternal.refresh();
chatControllerInternal.clear();
- await instancesControllerInternal.refresh();
await skillsControllerInternal.refresh();
- await connectorsControllerInternal.refresh();
await modelsControllerInternal.refresh();
await cronJobsControllerInternal.refresh();
devicesControllerInternal.clear();
@@ -278,13 +276,11 @@ extension AppControllerDesktopGateway on AppController {
await refreshGatewayHealth();
await refreshAgents();
await refreshSessions();
- await instancesControllerInternal.refresh();
await skillsControllerInternal.refresh(
agentId: agentsControllerInternal.selectedAgentId.isEmpty
? null
: agentsControllerInternal.selectedAgentId,
);
- await connectorsControllerInternal.refresh();
await modelsControllerInternal.refresh();
await cronJobsControllerInternal.refresh();
await devicesControllerInternal.refresh(quiet: true);
diff --git a/lib/app/app_controller_desktop_navigation.dart b/lib/app/app_controller_desktop_navigation.dart
index 12f3474b..72f7f650 100644
--- a/lib/app/app_controller_desktop_navigation.dart
+++ b/lib/app/app_controller_desktop_navigation.dart
@@ -50,29 +50,17 @@ extension AppControllerDesktopNavigation on AppController {
if (!capabilities.supportsDestination(destination)) {
return;
}
- if (destination == WorkspaceDestination.aiGateway ||
- destination == WorkspaceDestination.secrets) {
- openSettings(tab: SettingsTab.gateway);
- return;
- }
- final nextModulesTab = switch (destination) {
- WorkspaceDestination.nodes => ModulesTab.nodes,
- WorkspaceDestination.agents => ModulesTab.agents,
- _ => modulesTabInternal,
- };
final shouldClearSettingsDrillIn =
settingsDetailInternal != null ||
settingsNavigationContextInternal != null;
final changed =
destinationInternal != destination ||
detailPanelInternal != null ||
- shouldClearSettingsDrillIn ||
- nextModulesTab != modulesTabInternal;
+ shouldClearSettingsDrillIn;
if (!changed) {
return;
}
destinationInternal = destination;
- modulesTabInternal = nextModulesTab;
settingsDetailInternal = null;
settingsNavigationContextInternal = null;
detailPanelInternal = null;
@@ -107,74 +95,6 @@ extension AppControllerDesktopNavigation on AppController {
}
}
- void openModules({ModulesTab tab = ModulesTab.nodes}) {
- if (tab == ModulesTab.gateway) {
- openSettings(tab: SettingsTab.gateway);
- return;
- }
- final destination = tab == ModulesTab.agents
- ? WorkspaceDestination.agents
- : WorkspaceDestination.nodes;
- if (!capabilities.supportsDestination(destination)) {
- return;
- }
- final changed =
- destinationInternal != destination ||
- modulesTabInternal != tab ||
- detailPanelInternal != null ||
- settingsDetailInternal != null ||
- settingsNavigationContextInternal != null;
- if (!changed) {
- return;
- }
- destinationInternal = destination;
- modulesTabInternal = tab;
- detailPanelInternal = null;
- settingsDetailInternal = null;
- settingsNavigationContextInternal = null;
- notifyListeners();
- }
-
- void setModulesTab(ModulesTab tab) {
- if (modulesTabInternal == tab) {
- return;
- }
- modulesTabInternal = tab;
- notifyListeners();
- }
-
- void openSecrets({SecretsTab tab = SecretsTab.vault}) {
- if (!capabilities.supportsDestination(WorkspaceDestination.settings)) {
- return;
- }
- secretsTabInternal = tab;
- openSettings(tab: SettingsTab.gateway);
- }
-
- void setSecretsTab(SecretsTab tab) {
- if (secretsTabInternal == tab) {
- return;
- }
- secretsTabInternal = tab;
- notifyListeners();
- }
-
- void openAiGateway({AiGatewayTab tab = AiGatewayTab.models}) {
- if (!capabilities.supportsDestination(WorkspaceDestination.settings)) {
- return;
- }
- aiGatewayTabInternal = tab;
- openSettings(tab: SettingsTab.gateway);
- }
-
- void setAiGatewayTab(AiGatewayTab tab) {
- if (aiGatewayTabInternal == tab) {
- return;
- }
- aiGatewayTabInternal = tab;
- notifyListeners();
- }
-
void openSettings({
SettingsTab tab = SettingsTab.gateway,
SettingsDetailPage? detail,
diff --git a/lib/app/app_controller_desktop_runtime_helpers.dart b/lib/app/app_controller_desktop_runtime_helpers.dart
index f9b2ce81..dbc5ac3b 100644
--- a/lib/app/app_controller_desktop_runtime_helpers.dart
+++ b/lib/app/app_controller_desktop_runtime_helpers.dart
@@ -470,9 +470,7 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
agentsControllerInternal.addListener(relayChildChangeInternal);
sessionsControllerInternal.addListener(relayChildChangeInternal);
chatControllerInternal.addListener(relayChildChangeInternal);
- instancesControllerInternal.addListener(relayChildChangeInternal);
skillsControllerInternal.addListener(relayChildChangeInternal);
- connectorsControllerInternal.addListener(relayChildChangeInternal);
modelsControllerInternal.addListener(relayChildChangeInternal);
cronJobsControllerInternal.addListener(relayChildChangeInternal);
devicesControllerInternal.addListener(relayChildChangeInternal);
@@ -488,9 +486,7 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
agentsControllerInternal.removeListener(relayChildChangeInternal);
sessionsControllerInternal.removeListener(relayChildChangeInternal);
chatControllerInternal.removeListener(relayChildChangeInternal);
- instancesControllerInternal.removeListener(relayChildChangeInternal);
skillsControllerInternal.removeListener(relayChildChangeInternal);
- connectorsControllerInternal.removeListener(relayChildChangeInternal);
modelsControllerInternal.removeListener(relayChildChangeInternal);
cronJobsControllerInternal.removeListener(relayChildChangeInternal);
devicesControllerInternal.removeListener(relayChildChangeInternal);
diff --git a/lib/app/app_controller_desktop_thread_sessions.dart b/lib/app/app_controller_desktop_thread_sessions.dart
index 2c3091a5..81a9e3eb 100644
--- a/lib/app/app_controller_desktop_thread_sessions.dart
+++ b/lib/app/app_controller_desktop_thread_sessions.dart
@@ -321,11 +321,6 @@ extension AppControllerDesktopThreadSessions on AppController {
String gatewayAddressLabelInternal(GatewayConnectionProfile profile) =>
gatewayAddressLabelThreadSessionInternal(profile);
- List get secretReferences =>
- settingsControllerInternal.buildSecretReferences();
- List get secretAuditTrail =>
- settingsControllerInternal.auditTrail;
- List get runtimeLogs => runtimeInternal.logs;
List get assistantNavigationDestinations =>
normalizeAssistantNavigationDestinations(
appUiState.assistantNavigationDestinations,
diff --git a/lib/app/app_shell_desktop.dart b/lib/app/app_shell_desktop.dart
index 98f83139..e552541b 100644
--- a/lib/app/app_shell_desktop.dart
+++ b/lib/app/app_shell_desktop.dart
@@ -34,9 +34,6 @@ class _AppShellState extends State {
static const _mobileDestinations = [
WorkspaceDestination.assistant,
- WorkspaceDestination.tasks,
- WorkspaceDestination.skills,
- WorkspaceDestination.secrets,
WorkspaceDestination.settings,
];
@@ -189,10 +186,7 @@ class _AppShellState extends State {
final showPinnedDetail =
controller.detailPanel != null &&
constraints.maxWidth > 1280;
- final mobileDestination =
- controller.destination == WorkspaceDestination.account
- ? WorkspaceDestination.settings
- : controller.destination;
+ final mobileDestination = controller.destination;
final availableMobileDestinations = _mobileDestinations
.where(controller.capabilities.supportsDestination)
.toList(growable: false);
@@ -320,9 +314,10 @@ class _AppShellState extends State {
onExpandFromCollapsed: () =>
_toggleSidebarVisibility(controller),
onOpenHome: controller.navigateHome,
- onOpenAccount: () => controller.navigateTo(
- WorkspaceDestination.account,
- ),
+ onOpenAccount: () =>
+ controller.openSettings(
+ tab: SettingsTab.gateway,
+ ),
onOpenThemeToggle: () =>
controller.setThemeMode(
controller.themeMode == ThemeMode.dark
@@ -500,9 +495,6 @@ class _AppShellState extends State {
WorkspaceDestination _resolveDesktopDestination(
WorkspaceDestination destination,
) {
- if (destination == WorkspaceDestination.account) {
- return WorkspaceDestination.settings;
- }
if (_desktopDestinations.contains(destination)) {
return destination;
}
diff --git a/lib/app/app_store_policy.dart b/lib/app/app_store_policy.dart
index b33bf977..f08d2054 100644
--- a/lib/app/app_store_policy.dart
+++ b/lib/app/app_store_policy.dart
@@ -27,38 +27,6 @@ UiFeatureManifest applyAppleAppStorePolicy(
var next = manifest;
final disabledPaths = <(UiFeaturePlatform, String, String)>[
- (
- hostPlatform,
- 'navigation',
- _featureKeyLeaf(UiFeatureKeys.navigationAgents),
- ),
- (
- hostPlatform,
- 'navigation',
- _featureKeyLeaf(UiFeatureKeys.navigationMcpServer),
- ),
- (
- hostPlatform,
- 'navigation',
- _featureKeyLeaf(UiFeatureKeys.navigationClawHub),
- ),
- (hostPlatform, 'workspace', _featureKeyLeaf(UiFeatureKeys.workspaceAgents)),
- (
- hostPlatform,
- 'workspace',
- _featureKeyLeaf(UiFeatureKeys.workspaceMcpServer),
- ),
- (
- hostPlatform,
- 'workspace',
- _featureKeyLeaf(UiFeatureKeys.workspaceClawHub),
- ),
- (hostPlatform, 'settings', _featureKeyLeaf(UiFeatureKeys.settingsAgents)),
- (
- hostPlatform,
- 'settings',
- _featureKeyLeaf(UiFeatureKeys.settingsExperimental),
- ),
(
hostPlatform,
'settings',
diff --git a/lib/app/ui_feature_manifest_core.dart b/lib/app/ui_feature_manifest_core.dart
index f7cd5ba6..217f7d2a 100644
--- a/lib/app/ui_feature_manifest_core.dart
+++ b/lib/app/ui_feature_manifest_core.dart
@@ -36,26 +36,7 @@ UiFeaturePlatform resolveUiFeaturePlatformFromContext(BuildContext context) {
abstract final class UiFeatureKeys {
static const navigationAssistant = 'navigation.assistant';
- static const navigationTasks = 'navigation.tasks';
- static const navigationWorkspace = 'navigation.workspace';
- static const navigationSkills = 'navigation.skills';
- static const navigationNodes = 'navigation.nodes';
- static const navigationAgents = 'navigation.agents';
- static const navigationMcpServer = 'navigation.mcp_server';
- static const navigationClawHub = 'navigation.claw_hub';
- static const navigationSecrets = 'navigation.secrets';
- static const navigationAiGateway = 'navigation.ai_gateway';
static const navigationSettings = 'navigation.settings';
- static const navigationAccount = 'navigation.account';
-
- static const workspaceSkills = 'workspace.skills';
- static const workspaceNodes = 'workspace.nodes';
- static const workspaceAgents = 'workspace.agents';
- static const workspaceMcpServer = 'workspace.mcp_server';
- static const workspaceClawHub = 'workspace.claw_hub';
- static const workspaceConnectors = 'workspace.connectors';
- static const workspaceAiGateway = 'workspace.ai_gateway';
- static const workspaceAccount = 'workspace.account';
static const assistantDirectAi = 'assistant.direct_ai';
static const assistantLocalGateway = 'assistant.local_gateway';
@@ -64,8 +45,6 @@ abstract final class UiFeatureKeys {
static const assistantMultiAgent = 'assistant.multi_agent';
static const assistantLocalRuntime = 'assistant.local_runtime';
- static const settingsGeneral = 'settings.general';
- static const settingsWorkspace = 'settings.workspace';
static const settingsGateway = 'settings.gateway';
static const settingsAccountAccess = 'settings.account_access';
static const settingsVaultServer = 'settings.vault_server';
@@ -74,11 +53,6 @@ abstract final class UiFeatureKeys {
static const settingsGatewayAdvancedCustomMode =
'settings.gateway_advanced_custom_mode';
static const settingsGatewaySetupCode = 'settings.gateway_setup_code';
- static const settingsAgents = 'settings.agents';
- static const settingsAppearance = 'settings.appearance';
- static const settingsDiagnostics = 'settings.diagnostics';
- static const settingsExperimental = 'settings.experimental';
- static const settingsAbout = 'settings.about';
static const settingsExperimentalCanvas = 'settings.experimental_canvas';
static const settingsExperimentalBridge = 'settings.experimental_bridge';
static const settingsExperimentalDebug = 'settings.experimental_debug';
@@ -380,16 +354,7 @@ class UiFeatureAccess {
>{
UiFeaturePlatform.mobile: {
UiFeatureKeys.navigationAssistant: WorkspaceDestination.assistant,
- UiFeatureKeys.navigationTasks: WorkspaceDestination.tasks,
- UiFeatureKeys.navigationSecrets: WorkspaceDestination.secrets,
UiFeatureKeys.navigationSettings: WorkspaceDestination.settings,
- UiFeatureKeys.workspaceSkills: WorkspaceDestination.skills,
- UiFeatureKeys.workspaceNodes: WorkspaceDestination.nodes,
- UiFeatureKeys.workspaceAgents: WorkspaceDestination.agents,
- UiFeatureKeys.workspaceMcpServer: WorkspaceDestination.mcpServer,
- UiFeatureKeys.workspaceClawHub: WorkspaceDestination.clawHub,
- UiFeatureKeys.workspaceAiGateway: WorkspaceDestination.aiGateway,
- UiFeatureKeys.workspaceAccount: WorkspaceDestination.account,
},
UiFeaturePlatform.desktop: {
UiFeatureKeys.navigationAssistant: WorkspaceDestination.assistant,
@@ -397,11 +362,7 @@ class UiFeatureAccess {
},
UiFeaturePlatform.web: {
UiFeatureKeys.navigationAssistant: WorkspaceDestination.assistant,
- UiFeatureKeys.navigationTasks: WorkspaceDestination.tasks,
- UiFeatureKeys.navigationSkills: WorkspaceDestination.skills,
- UiFeatureKeys.navigationNodes: WorkspaceDestination.nodes,
- UiFeatureKeys.navigationSecrets: WorkspaceDestination.secrets,
- UiFeatureKeys.navigationAiGateway: WorkspaceDestination.aiGateway,
+ UiFeatureKeys.navigationSettings: WorkspaceDestination.settings,
},
};
@@ -439,9 +400,7 @@ class UiFeatureAccess {
return allowed;
}
- bool get showsWorkspaceHub =>
- platform == UiFeaturePlatform.mobile &&
- isEnabledPath(UiFeatureKeys.navigationWorkspace);
+ bool get showsWorkspaceHub => false;
bool get supportsDirectAi => isEnabledPath(UiFeatureKeys.assistantDirectAi);
@@ -461,9 +420,6 @@ class UiFeatureAccess {
platform == UiFeaturePlatform.desktop &&
isEnabledPath(UiFeatureKeys.assistantLocalRuntime);
- bool get supportsDiagnostics =>
- isEnabledPath(UiFeatureKeys.settingsDiagnostics);
-
bool get supportsAccountAccess =>
isEnabledPath(UiFeatureKeys.settingsAccountAccess);
diff --git a/lib/app/workspace_navigation.dart b/lib/app/workspace_navigation.dart
index d08e34ce..d3f4a0c3 100644
--- a/lib/app/workspace_navigation.dart
+++ b/lib/app/workspace_navigation.dart
@@ -58,22 +58,6 @@ void openSettingsNavigationContext(
AppController controller,
SettingsNavigationContext context,
) {
- if (context.modulesTab != null) {
- if (context.modulesTab == ModulesTab.gateway) {
- controller.openSettings(tab: SettingsTab.gateway);
- return;
- }
- controller.openModules(tab: context.modulesTab!);
- return;
- }
- if (context.secretsTab != null) {
- controller.openSettings(tab: SettingsTab.gateway);
- return;
- }
- if (context.aiGatewayTab != null) {
- controller.openSettings(tab: SettingsTab.gateway);
- return;
- }
if (context.settingsTab != null ||
context.destination == WorkspaceDestination.settings) {
controller.openSettings(tab: context.settingsTab ?? SettingsTab.gateway);
diff --git a/lib/app/workspace_page_registry.dart b/lib/app/workspace_page_registry.dart
index 6de266a2..39072140 100644
--- a/lib/app/workspace_page_registry.dart
+++ b/lib/app/workspace_page_registry.dart
@@ -1,12 +1,7 @@
import 'package:flutter/material.dart';
import '../features/assistant/assistant_page.dart';
-import '../features/claw_hub/claw_hub_page.dart';
-import '../features/mcp_server/mcp_server_page.dart';
-import '../features/modules/modules_page.dart';
import '../features/settings/settings_page.dart';
-import '../features/skills/skills_page.dart';
-import '../features/tasks/tasks_page.dart';
import '../models/app_models.dart';
import 'app_controller.dart';
@@ -45,74 +40,6 @@ workspacePageSpecsInternal = {
showStandaloneTaskRail: false,
),
),
- WorkspaceDestination.tasks: WorkspacePageSpec(
- destination: WorkspaceDestination.tasks,
- desktopBuilder: (controller, onOpenDetail) =>
- TasksPage(controller: controller, onOpenDetail: onOpenDetail),
- mobileBuilder: (controller, onOpenDetail) =>
- TasksPage(controller: controller, onOpenDetail: onOpenDetail),
- ),
- WorkspaceDestination.skills: WorkspacePageSpec(
- destination: WorkspaceDestination.skills,
- desktopBuilder: (controller, onOpenDetail) =>
- SkillsPage(controller: controller, onOpenDetail: onOpenDetail),
- mobileBuilder: (controller, onOpenDetail) =>
- SkillsPage(controller: controller, onOpenDetail: onOpenDetail),
- ),
- WorkspaceDestination.nodes: WorkspacePageSpec(
- destination: WorkspaceDestination.nodes,
- desktopBuilder: (controller, onOpenDetail) => ModulesPage(
- controller: controller,
- onOpenDetail: onOpenDetail,
- initialTab: controller.modulesTab,
- ),
- mobileBuilder: (controller, onOpenDetail) => ModulesPage(
- controller: controller,
- onOpenDetail: onOpenDetail,
- initialTab: controller.modulesTab,
- ),
- ),
- WorkspaceDestination.agents: WorkspacePageSpec(
- destination: WorkspaceDestination.agents,
- desktopBuilder: (controller, onOpenDetail) => ModulesPage(
- controller: controller,
- onOpenDetail: onOpenDetail,
- initialTab: controller.modulesTab,
- ),
- mobileBuilder: (controller, onOpenDetail) => ModulesPage(
- controller: controller,
- onOpenDetail: onOpenDetail,
- initialTab: controller.modulesTab,
- ),
- ),
- WorkspaceDestination.mcpServer: WorkspacePageSpec(
- destination: WorkspaceDestination.mcpServer,
- desktopBuilder: (controller, onOpenDetail) =>
- McpServerPage(controller: controller, onOpenDetail: onOpenDetail),
- mobileBuilder: (controller, onOpenDetail) =>
- McpServerPage(controller: controller, onOpenDetail: onOpenDetail),
- ),
- WorkspaceDestination.clawHub: WorkspacePageSpec(
- destination: WorkspaceDestination.clawHub,
- desktopBuilder: (controller, onOpenDetail) =>
- ClawHubPage(controller: controller, onOpenDetail: onOpenDetail),
- mobileBuilder: (controller, onOpenDetail) =>
- ClawHubPage(controller: controller, onOpenDetail: onOpenDetail),
- ),
- WorkspaceDestination.secrets: WorkspacePageSpec(
- destination: WorkspaceDestination.secrets,
- desktopBuilder: (controller, onOpenDetail) =>
- SettingsPage(controller: controller, initialTab: SettingsTab.gateway),
- mobileBuilder: (controller, onOpenDetail) =>
- SettingsPage(controller: controller, initialTab: SettingsTab.gateway),
- ),
- WorkspaceDestination.aiGateway: WorkspacePageSpec(
- destination: WorkspaceDestination.aiGateway,
- desktopBuilder: (controller, onOpenDetail) =>
- SettingsPage(controller: controller, initialTab: SettingsTab.gateway),
- mobileBuilder: (controller, onOpenDetail) =>
- SettingsPage(controller: controller, initialTab: SettingsTab.gateway),
- ),
WorkspaceDestination.settings: WorkspacePageSpec(
destination: WorkspaceDestination.settings,
desktopBuilder: (controller, onOpenDetail) => SettingsPage(
@@ -128,13 +55,6 @@ workspacePageSpecsInternal = {
navigationContext: controller.settingsNavigationContext,
),
),
- WorkspaceDestination.account: WorkspacePageSpec(
- destination: WorkspaceDestination.account,
- desktopBuilder: (controller, onOpenDetail) =>
- SettingsPage(controller: controller, initialTab: SettingsTab.gateway),
- mobileBuilder: (controller, onOpenDetail) =>
- SettingsPage(controller: controller, initialTab: SettingsTab.gateway),
- ),
};
Widget buildWorkspacePage({
diff --git a/lib/features/claw_hub/claw_hub_page.dart b/lib/features/claw_hub/claw_hub_page.dart
deleted file mode 100644
index e6fab4eb..00000000
--- a/lib/features/claw_hub/claw_hub_page.dart
+++ /dev/null
@@ -1,503 +0,0 @@
-import 'package:flutter/material.dart';
-
-import '../../app/app_controller.dart';
-import '../../i18n/app_language.dart';
-import '../../models/app_models.dart';
-import '../../theme/app_palette.dart';
-import '../../widgets/section_header.dart';
-import '../../widgets/surface_card.dart';
-import '../../widgets/top_bar.dart';
-
-class ClawHubPage extends StatefulWidget {
- const ClawHubPage({
- super.key,
- required this.controller,
- required this.onOpenDetail,
- });
-
- final AppController controller;
- final ValueChanged onOpenDetail;
-
- @override
- State createState() => ClawHubPageStateInternal();
-}
-
-class ClawHubPageStateInternal extends State {
- final searchControllerInternal = TextEditingController();
- final commandControllerInternal = TextEditingController();
- final scrollControllerInternal = ScrollController();
- final List logsInternal = [];
- bool isExecutingInternal = false;
-
- @override
- void dispose() {
- searchControllerInternal.dispose();
- commandControllerInternal.dispose();
- scrollControllerInternal.dispose();
- super.dispose();
- }
-
- void addLogInternal(
- String message, {
- ClawHubLogType type = ClawHubLogType.info,
- }) {
- setState(() {
- logsInternal.add(
- ClawHubLogEntry(
- timestamp: DateTime.now(),
- message: message,
- type: type,
- ),
- );
- });
- // Auto-scroll to bottom
- WidgetsBinding.instance.addPostFrameCallback((_) {
- if (scrollControllerInternal.hasClients) {
- scrollControllerInternal.animateTo(
- scrollControllerInternal.position.maxScrollExtent,
- duration: const Duration(milliseconds: 200),
- curve: Curves.easeOut,
- );
- }
- });
- }
-
- void executeCommandInternal(String input) {
- if (input.trim().isEmpty) return;
-
- addLogInternal('\$ clawhub \$input', type: ClawHubLogType.command);
- commandControllerInternal.clear();
-
- final parts = input.trim().split(RegExp(r'\s+'));
- final command = parts.isNotEmpty ? parts[0] : '';
- final args = parts.length > 1 ? parts.sublist(1) : [];
-
- switch (command) {
- case 'search':
- handleSearchInternal(args);
- break;
- case 'install':
- handleInstallInternal(args);
- break;
- case 'update':
- handleUpdateInternal(args);
- break;
- case 'help':
- case '--help':
- case '-h':
- showHelpInternal();
- break;
- default:
- addLogInternal(
- 'Unknown command: \$command. Type "clawhub help" for available commands.',
- type: ClawHubLogType.error,
- );
- }
- }
-
- void handleSearchInternal(List args) {
- final query = args.join(' ');
- if (query.isEmpty) {
- addLogInternal(
- 'Usage: clawhub search ""',
- type: ClawHubLogType.warning,
- );
- return;
- }
-
- setState(() => isExecutingInternal = true);
- addLogInternal('Searching for "\$query"...');
-
- // Simulate search results
- Future.delayed(const Duration(milliseconds: 800), () {
- setState(() => isExecutingInternal = false);
- addLogInternal('');
- addLogInternal('Found 3 packages:', type: ClawHubLogType.success);
- addLogInternal(' ├─ skill-analyzer v1.2.0 Code analysis skill');
- addLogInternal(' ├─ feishu-connector v2.1.3 Feishu integration');
- addLogInternal(
- ' └─ azure-deploy v3.0.1 Azure deployment helper',
- );
- addLogInternal('');
- addLogInternal('Use "clawhub install " to install a package.');
- });
- }
-
- void handleInstallInternal(List args) {
- if (args.isEmpty) {
- addLogInternal(
- 'Usage: clawhub install ',
- type: ClawHubLogType.warning,
- );
- return;
- }
-
- setState(() => isExecutingInternal = true);
- addLogInternal('Installing ${args[0]}...');
-
- Future.delayed(const Duration(milliseconds: 1200), () {
- setState(() => isExecutingInternal = false);
- addLogInternal(
- '✓ Successfully installed ${args[0]}',
- type: ClawHubLogType.success,
- );
- addLogInternal(' Location: ~/.clawhub/skills/${args[0]}');
- addLogInternal(' Run "clawhub update" to check for updates.');
- });
- }
-
- void handleUpdateInternal(List args) {
- final isAll = args.contains('--all') || args.contains('-a');
- final slug = isAll ? null : (args.isNotEmpty ? args[0] : null);
-
- setState(() => isExecutingInternal = true);
-
- if (isAll) {
- addLogInternal('Checking for updates...');
- Future.delayed(const Duration(milliseconds: 1000), () {
- setState(() => isExecutingInternal = false);
- addLogInternal(
- '✓ All packages are up to date',
- type: ClawHubLogType.success,
- );
- });
- } else if (slug != null) {
- addLogInternal('Updating \$slug...');
- Future.delayed(const Duration(milliseconds: 800), () {
- setState(() => isExecutingInternal = false);
- addLogInternal(
- '✓ \$slug updated to latest version',
- type: ClawHubLogType.success,
- );
- });
- } else {
- addLogInternal(
- 'Usage: clawhub update or clawhub update --all',
- type: ClawHubLogType.warning,
- );
- setState(() => isExecutingInternal = false);
- }
- }
-
- void showHelpInternal() {
- addLogInternal('');
- addLogInternal('ClawHub Package Manager', type: ClawHubLogType.success);
- addLogInternal('Usage: clawhub [options]');
- addLogInternal('');
- addLogInternal('Commands:');
- addLogInternal(' search "" Search for packages');
- addLogInternal(' install Install a package');
- addLogInternal(' update Update a specific package');
- addLogInternal(' update --all Update all packages');
- addLogInternal(' help Show this help message');
- addLogInternal('');
- }
-
- @override
- Widget build(BuildContext context) {
- final palette = context.palette;
-
- return AnimatedBuilder(
- animation: widget.controller,
- builder: (context, _) {
- return SingleChildScrollView(
- padding: const EdgeInsets.fromLTRB(32, 32, 32, 8),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- TopBar(
- breadcrumbs: [
- AppBreadcrumbItem(
- label: appText('主页', 'Home'),
- icon: Icons.home_rounded,
- onTap: widget.controller.navigateHome,
- ),
- const AppBreadcrumbItem(label: 'ClawHub'),
- ],
- title: 'ClawHub',
- subtitle: appText(
- 'NPM 风格的包管理中心,支持搜索、安装和更新 Skills。',
- 'NPM-style package manager for skills.',
- ),
- ),
- const SizedBox(height: 24),
- SectionHeader(
- title: appText('终端', 'Terminal'),
- subtitle: appText('执行终端命令', 'Execute terminal commands'),
- ),
- const SizedBox(height: 12),
- SurfaceCard(
- child: Container(
- height: 400,
- decoration: BoxDecoration(
- color: palette.surfaceSecondary.withValues(alpha: 0.5),
- borderRadius: BorderRadius.circular(12),
- ),
- child: Column(
- children: [
- // Terminal header
- Container(
- padding: const EdgeInsets.symmetric(
- horizontal: 16,
- vertical: 12,
- ),
- decoration: BoxDecoration(
- color: palette.surfaceSecondary,
- borderRadius: const BorderRadius.vertical(
- top: Radius.circular(12),
- ),
- ),
- child: Row(
- children: [
- Icon(
- Icons.terminal_rounded,
- size: 16,
- color: palette.textSecondary,
- ),
- const SizedBox(width: 8),
- Text(
- 'clawhub',
- style: TextStyle(
- fontSize: 13,
- fontWeight: FontWeight.w600,
- color: palette.textSecondary,
- ),
- ),
- const Spacer(),
- if (isExecutingInternal)
- SizedBox(
- width: 14,
- height: 14,
- child: CircularProgressIndicator(
- strokeWidth: 2,
- color: palette.accent,
- ),
- ),
- ],
- ),
- ),
- // Terminal output
- Expanded(
- child: Container(
- padding: const EdgeInsets.all(16),
- child: ListView.builder(
- controller: scrollControllerInternal,
- itemCount: logsInternal.length,
- itemBuilder: (context, index) {
- final log = logsInternal[index];
- return LogLineInternal(
- entry: log,
- palette: palette,
- );
- },
- ),
- ),
- ),
- // Command input
- Container(
- padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(
- color: palette.surfaceSecondary,
- border: Border(
- top: BorderSide(color: palette.strokeSoft),
- ),
- borderRadius: const BorderRadius.vertical(
- bottom: Radius.circular(12),
- ),
- ),
- child: Row(
- children: [
- Text(
- '\$',
- style: TextStyle(
- fontFamily: 'monospace',
- fontSize: 14,
- fontWeight: FontWeight.w600,
- color: palette.accent,
- ),
- ),
- const SizedBox(width: 8),
- Expanded(
- child: TextField(
- controller: commandControllerInternal,
- style: TextStyle(
- fontFamily: 'monospace',
- fontSize: 14,
- color: palette.textPrimary,
- ),
- decoration: InputDecoration(
- hintText: appText(
- '输入命令 (search, install, update)',
- 'Type command (search, install, update)',
- ),
- hintStyle: TextStyle(
- fontFamily: 'monospace',
- fontSize: 14,
- color: palette.textMuted,
- ),
- border: InputBorder.none,
- isDense: true,
- contentPadding: EdgeInsets.zero,
- ),
- onSubmitted: executeCommandInternal,
- ),
- ),
- IconButton(
- icon: Icon(
- Icons.send_rounded,
- size: 18,
- color: palette.accent,
- ),
- onPressed: () => executeCommandInternal(
- commandControllerInternal.text,
- ),
- visualDensity: VisualDensity.compact,
- ),
- ],
- ),
- ),
- ],
- ),
- ),
- ),
- const SizedBox(height: 24),
- SectionHeader(
- title: appText('快速操作', 'Quick Actions'),
- subtitle: appText('常用操作快捷入口', 'Quick access to common actions'),
- ),
- const SizedBox(height: 12),
- Wrap(
- spacing: 12,
- runSpacing: 12,
- children: [
- QuickActionButtonInternal(
- icon: Icons.search_rounded,
- label: appText('搜索技能', 'Search Skills'),
- onTap: () => executeCommandInternal('search analytics'),
- ),
- QuickActionButtonInternal(
- icon: Icons.download_rounded,
- label: appText('安装技能', 'Install Skill'),
- onTap: () =>
- executeCommandInternal('install example-skill'),
- ),
- QuickActionButtonInternal(
- icon: Icons.update_rounded,
- label: appText('更新全部', 'Update All'),
- onTap: () => executeCommandInternal('update --all'),
- ),
- QuickActionButtonInternal(
- icon: Icons.help_outline_rounded,
- label: appText('查看帮助', 'View Help'),
- onTap: () => executeCommandInternal('help'),
- ),
- ],
- ),
- ],
- ),
- );
- },
- );
- }
-}
-
-enum ClawHubLogType { info, command, success, warning, error }
-
-class ClawHubLogEntry {
- final DateTime timestamp;
- final String message;
- final ClawHubLogType type;
-
- ClawHubLogEntry({
- required this.timestamp,
- required this.message,
- required this.type,
- });
-}
-
-class LogLineInternal extends StatelessWidget {
- const LogLineInternal({
- super.key,
- required this.entry,
- required this.palette,
- });
-
- final ClawHubLogEntry entry;
- final AppPalette palette;
-
- Color get colorInternal {
- switch (entry.type) {
- case ClawHubLogType.command:
- return palette.accent;
- case ClawHubLogType.success:
- return Colors.green;
- case ClawHubLogType.warning:
- return Colors.orange;
- case ClawHubLogType.error:
- return Colors.red;
- case ClawHubLogType.info:
- return palette.textPrimary;
- }
- }
-
- @override
- Widget build(BuildContext context) {
- return Padding(
- padding: const EdgeInsets.only(bottom: 4),
- child: Text(
- entry.message,
- style: TextStyle(
- fontFamily: 'monospace',
- fontSize: 13,
- color: colorInternal,
- height: 1.4,
- ),
- ),
- );
- }
-}
-
-class QuickActionButtonInternal extends StatelessWidget {
- const QuickActionButtonInternal({
- super.key,
- required this.icon,
- required this.label,
- required this.onTap,
- });
-
- final IconData icon;
- final String label;
- final VoidCallback onTap;
-
- @override
- Widget build(BuildContext context) {
- final palette = context.palette;
-
- return Material(
- color: palette.surfaceSecondary,
- borderRadius: BorderRadius.circular(8),
- child: InkWell(
- onTap: onTap,
- borderRadius: BorderRadius.circular(8),
- child: Container(
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Icon(icon, size: 18, color: palette.accent),
- const SizedBox(width: 8),
- Text(
- label,
- style: TextStyle(
- fontSize: 14,
- fontWeight: FontWeight.w500,
- color: palette.textPrimary,
- ),
- ),
- ],
- ),
- ),
- ),
- );
- }
-}
diff --git a/lib/features/mcp_server/mcp_server_page.dart b/lib/features/mcp_server/mcp_server_page.dart
deleted file mode 100644
index 2570d553..00000000
--- a/lib/features/mcp_server/mcp_server_page.dart
+++ /dev/null
@@ -1,181 +0,0 @@
-import 'package:flutter/material.dart';
-
-import '../../app/app_controller.dart';
-import '../../i18n/app_language.dart';
-import '../../models/app_models.dart';
-import '../../runtime/runtime_models.dart';
-import '../../widgets/surface_card.dart';
-import '../../widgets/top_bar.dart';
-
-class McpServerPage extends StatelessWidget {
- const McpServerPage({
- super.key,
- required this.controller,
- required this.onOpenDetail,
- });
-
- final AppController controller;
- final ValueChanged onOpenDetail;
-
- @override
- Widget build(BuildContext context) {
- final items = controller.connectors;
-
- return AnimatedBuilder(
- animation: controller,
- builder: (context, _) {
- return SingleChildScrollView(
- padding: const EdgeInsets.fromLTRB(32, 32, 32, 8),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- TopBar(
- breadcrumbs: [
- AppBreadcrumbItem(
- label: appText('主页', 'Home'),
- icon: Icons.home_rounded,
- onTap: controller.navigateHome,
- ),
- const AppBreadcrumbItem(label: 'MCP Hub'),
- ],
- title: 'MCP Hub',
- subtitle: appText(
- '管理 MCP 服务器连接与工具配置。',
- 'Manage MCP server connections and tool configurations.',
- ),
- trailing: Wrap(
- spacing: 12,
- runSpacing: 12,
- children: [
- SizedBox(
- width: 220,
- child: TextField(
- decoration: InputDecoration(
- hintText: appText('搜索服务器', 'Search servers'),
- prefixIcon: Icon(Icons.search_rounded),
- ),
- ),
- ),
- IconButton(
- onPressed: () async {
- await controller.connectorsController.refresh();
- },
- icon: const Icon(Icons.refresh_rounded),
- ),
- ],
- ),
- ),
- const SizedBox(height: 24),
- if (items.isEmpty)
- SurfaceCard(
- child: Text(
- controller.connection.status ==
- RuntimeConnectionStatus.connected
- ? appText(
- '当前没有连接的 MCP 服务器。',
- 'No MCP servers connected.',
- )
- : appText(
- '恢复 xworkmate-bridge 连接后可查看 MCP 服务器。',
- 'MCP servers are visible again after xworkmate-bridge reconnects.',
- ),
- ),
- )
- else
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: items
- .map(
- (connector) => Padding(
- padding: const EdgeInsets.only(bottom: 12),
- child: SurfaceCard(
- onTap: () => onOpenDetail(
- DetailPanelData(
- title: connector.label,
- subtitle: appText('连接器', 'Connector'),
- icon: Icons.dns_rounded,
- status: StatusInfo(
- connector.status,
- connector.connected
- ? StatusTone.success
- : StatusTone.neutral,
- ),
- description: connector.detailLabel,
- meta: connector.meta,
- actions: [appText('配置', 'Configure')],
- sections: [
- DetailSection(
- title: appText('详情', 'Details'),
- items: [
- DetailItem(
- label: appText('ID', 'ID'),
- value: connector.id,
- ),
- DetailItem(
- label: appText('状态', 'Status'),
- value: connector.status,
- ),
- DetailItem(
- label: appText('已配置', 'Configured'),
- value: connector.configured
- ? appText('是', 'Yes')
- : appText('否', 'No'),
- ),
- DetailItem(
- label: appText('已启用', 'Enabled'),
- value: connector.enabled
- ? appText('是', 'Yes')
- : appText('否', 'No'),
- ),
- ],
- ),
- ],
- ),
- ),
- child: Row(
- children: [
- Expanded(
- flex: 4,
- child: Column(
- crossAxisAlignment:
- CrossAxisAlignment.start,
- children: [
- Text(
- connector.label,
- style: Theme.of(
- context,
- ).textTheme.titleMedium,
- ),
- const SizedBox(height: 6),
- Text(
- connector.detailLabel,
- style: Theme.of(
- context,
- ).textTheme.bodySmall,
- ),
- ],
- ),
- ),
- Expanded(
- flex: 2,
- child: Text(
- connector.connected
- ? appText('已连接', 'Connected')
- : appText('未连接', 'Disconnected'),
- ),
- ),
- const Icon(Icons.chevron_right_rounded),
- ],
- ),
- ),
- ),
- )
- .toList(),
- ),
- ],
- ),
- );
- },
- );
- }
-}
diff --git a/lib/features/mobile/mobile_shell.dart b/lib/features/mobile/mobile_shell.dart
index 08cc6907..c8bea1d9 100644
--- a/lib/features/mobile/mobile_shell.dart
+++ b/lib/features/mobile/mobile_shell.dart
@@ -1,5 +1,4 @@
export 'mobile_shell_core.dart';
export 'mobile_shell_strip.dart';
export 'mobile_shell_sheet.dart';
-export 'mobile_shell_workspace.dart';
export 'mobile_shell_nav.dart';
diff --git a/lib/features/mobile/mobile_shell_core.dart b/lib/features/mobile/mobile_shell_core.dart
index 937d2fba..ffd5d5db 100644
--- a/lib/features/mobile/mobile_shell_core.dart
+++ b/lib/features/mobile/mobile_shell_core.dart
@@ -1,7 +1,9 @@
// ignore_for_file: unused_import, unnecessary_import
import 'dart:async';
+
import 'package:flutter/material.dart';
+
import '../../app/app_controller.dart';
import '../../app/ui_feature_manifest.dart';
import '../../app/workspace_page_registry.dart';
@@ -12,36 +14,24 @@ import '../../theme/app_palette.dart';
import '../../theme/app_theme.dart';
import '../../widgets/detail_drawer.dart';
import 'mobile_gateway_pairing_guide_page.dart';
-import 'mobile_shell_strip.dart';
-import 'mobile_shell_sheet.dart';
-import 'mobile_shell_workspace.dart';
import 'mobile_shell_nav.dart';
+import 'mobile_shell_sheet.dart';
+import 'mobile_shell_strip.dart';
-enum MobileShellTab { assistant, tasks, workspace, secrets, settings }
+enum MobileShellTab { assistant, settings }
extension MobileShellTabPresentationInternal on MobileShellTab {
String get label => switch (this) {
MobileShellTab.assistant => appText('助手', 'Assistant'),
- MobileShellTab.tasks => appText('任务', 'Tasks'),
- MobileShellTab.workspace => appText('工作区', 'Workspace'),
- MobileShellTab.secrets => appText('密钥', 'Secrets'),
MobileShellTab.settings => appText('设置', 'Settings'),
};
IconData get icon => switch (this) {
MobileShellTab.assistant => Icons.chat_bubble_outline_rounded,
- MobileShellTab.tasks => Icons.layers_rounded,
- MobileShellTab.workspace => Icons.grid_view_rounded,
- MobileShellTab.secrets => Icons.key_rounded,
MobileShellTab.settings => Icons.settings_rounded,
};
}
-const tealSoftInternal = Color(0xFFDDF3EF);
-const tealLineInternal = Color(0xFF49A892);
-const violetSoftInternal = Color(0xFFECE2FF);
-const violetLineInternal = Color(0xFF7A61B6);
-
class MobileShell extends StatefulWidget {
const MobileShell({super.key, required this.controller});
@@ -52,92 +42,24 @@ class MobileShell extends StatefulWidget {
}
class MobileShellStateInternal extends State {
- bool showWorkspaceHubInternal = false;
- late WorkspaceDestination lastDestinationInternal;
-
- @override
- void initState() {
- super.initState();
- lastDestinationInternal = widget.controller.destination;
- widget.controller.addListener(handleControllerChangedInternal);
- }
-
- @override
- void didUpdateWidget(covariant MobileShell oldWidget) {
- super.didUpdateWidget(oldWidget);
- if (oldWidget.controller == widget.controller) {
- return;
- }
- oldWidget.controller.removeListener(handleControllerChangedInternal);
- lastDestinationInternal = widget.controller.destination;
- widget.controller.addListener(handleControllerChangedInternal);
- }
-
- @override
- void dispose() {
- widget.controller.removeListener(handleControllerChangedInternal);
- super.dispose();
- }
-
- void handleControllerChangedInternal() {
- final destination = widget.controller.destination;
- if (destination == lastDestinationInternal) {
- return;
- }
- lastDestinationInternal = destination;
- if (showWorkspaceHubInternal && mounted) {
- setState(() {
- showWorkspaceHubInternal = false;
- });
- }
- }
-
MobileShellTab tabForDestinationInternal(WorkspaceDestination destination) {
return switch (destination) {
WorkspaceDestination.assistant => MobileShellTab.assistant,
- WorkspaceDestination.tasks => MobileShellTab.tasks,
- WorkspaceDestination.skills ||
- WorkspaceDestination.nodes ||
- WorkspaceDestination.agents ||
- WorkspaceDestination.mcpServer ||
- WorkspaceDestination.clawHub ||
- WorkspaceDestination.aiGateway => MobileShellTab.workspace,
- WorkspaceDestination.secrets => MobileShellTab.secrets,
WorkspaceDestination.settings => MobileShellTab.settings,
- WorkspaceDestination.account => MobileShellTab.settings,
};
}
void selectTabInternal(MobileShellTab tab) {
switch (tab) {
case MobileShellTab.assistant:
- setState(() => showWorkspaceHubInternal = false);
widget.controller.navigateTo(WorkspaceDestination.assistant);
return;
- case MobileShellTab.tasks:
- setState(() => showWorkspaceHubInternal = false);
- widget.controller.navigateTo(WorkspaceDestination.tasks);
- return;
- case MobileShellTab.workspace:
- prefetchMobileSafeStateInternal();
- setState(() => showWorkspaceHubInternal = true);
- return;
- case MobileShellTab.secrets:
- setState(() => showWorkspaceHubInternal = false);
- widget.controller.navigateTo(WorkspaceDestination.secrets);
- return;
case MobileShellTab.settings:
- setState(() => showWorkspaceHubInternal = false);
widget.controller.navigateTo(WorkspaceDestination.settings);
return;
}
}
- void openWorkspaceDestinationInternal(WorkspaceDestination destination) {
- setState(() => showWorkspaceHubInternal = false);
- widget.controller.navigateTo(destination);
- }
-
void openDetailSheetInternal(DetailPanelData detail) {
widget.controller.openDetail(detail);
}
@@ -157,6 +79,7 @@ class MobileShellStateInternal extends State {
rootLabel: appText('移动端', 'Mobile'),
destination: WorkspaceDestination.settings,
sectionLabel: appText('集成', 'Integrations'),
+ settingsTab: SettingsTab.gateway,
gatewayProfileIndex: kGatewayRemoteProfileIndex,
prefersGatewaySetupCode: false,
),
@@ -185,6 +108,7 @@ class MobileShellStateInternal extends State {
rootLabel: appText('移动端', 'Mobile'),
destination: WorkspaceDestination.settings,
sectionLabel: appText('集成', 'Integrations'),
+ settingsTab: SettingsTab.gateway,
gatewayProfileIndex: kGatewayRemoteProfileIndex,
prefersGatewaySetupCode: true,
),
@@ -258,9 +182,6 @@ class MobileShellStateInternal extends State {
);
return;
}
- if (!mounted) {
- return;
- }
final codeController = TextEditingController();
final enteredCode = await showDialog(
context: context,
@@ -347,18 +268,8 @@ class MobileShellStateInternal extends State {
}
Widget buildCurrentPageInternal() {
- final features = widget.controller.featuresFor(UiFeaturePlatform.mobile);
- if (showWorkspaceHubInternal && features.showsWorkspaceHub) {
- return MobileWorkspaceLauncherInternal(
- controller: widget.controller,
- onOpenGatewayConnect: showConnectSheetInternal,
- onSelectDestination: openWorkspaceDestinationInternal,
- );
- }
-
- final destination = widget.controller.destination;
return buildWorkspacePage(
- destination: destination,
+ destination: widget.controller.destination,
controller: widget.controller,
onOpenDetail: openDetailSheetInternal,
surface: WorkspacePageSurface.mobile,
@@ -376,25 +287,16 @@ class MobileShellStateInternal extends State {
final availableTabs = [
if (features.isEnabledPath(UiFeatureKeys.navigationAssistant))
MobileShellTab.assistant,
- if (features.isEnabledPath(UiFeatureKeys.navigationTasks))
- MobileShellTab.tasks,
- if (features.showsWorkspaceHub) MobileShellTab.workspace,
- if (features.isEnabledPath(UiFeatureKeys.navigationSecrets))
- MobileShellTab.secrets,
if (features.isEnabledPath(UiFeatureKeys.navigationSettings))
MobileShellTab.settings,
];
- final currentTab = showWorkspaceHubInternal
- ? MobileShellTab.workspace
- : tabForDestinationInternal(widget.controller.destination);
+ final currentTab = tabForDestinationInternal(widget.controller.destination);
final resolvedCurrentTab = availableTabs.contains(currentTab)
? currentTab
: (availableTabs.isEmpty ? currentTab : availableTabs.first);
- final destinationKey = showWorkspaceHubInternal
- ? const ValueKey('mobile-shell-workspace')
- : ValueKey(
- 'mobile-shell-${widget.controller.destination.name}',
- );
+ final destinationKey = ValueKey(
+ 'mobile-shell-${widget.controller.destination.name}',
+ );
final detailPanel = widget.controller.detailPanel;
final palette = context.palette;
return Scaffold(
diff --git a/lib/features/mobile/mobile_shell_nav.dart b/lib/features/mobile/mobile_shell_nav.dart
index 16669a27..c9c9e60c 100644
--- a/lib/features/mobile/mobile_shell_nav.dart
+++ b/lib/features/mobile/mobile_shell_nav.dart
@@ -15,7 +15,6 @@ import 'mobile_gateway_pairing_guide_page.dart';
import 'mobile_shell_core.dart';
import 'mobile_shell_strip.dart';
import 'mobile_shell_sheet.dart';
-import 'mobile_shell_workspace.dart';
class BottomPillNavInternal extends StatelessWidget {
const BottomPillNavInternal({
diff --git a/lib/features/mobile/mobile_shell_sheet.dart b/lib/features/mobile/mobile_shell_sheet.dart
index e491730e..8c8d8517 100644
--- a/lib/features/mobile/mobile_shell_sheet.dart
+++ b/lib/features/mobile/mobile_shell_sheet.dart
@@ -14,7 +14,6 @@ import '../../widgets/detail_drawer.dart';
import 'mobile_gateway_pairing_guide_page.dart';
import 'mobile_shell_core.dart';
import 'mobile_shell_strip.dart';
-import 'mobile_shell_workspace.dart';
import 'mobile_shell_nav.dart';
class MobileSafeSheetInternal extends StatelessWidget {
diff --git a/lib/features/mobile/mobile_shell_strip.dart b/lib/features/mobile/mobile_shell_strip.dart
index 855c9930..16ff9c61 100644
--- a/lib/features/mobile/mobile_shell_strip.dart
+++ b/lib/features/mobile/mobile_shell_strip.dart
@@ -14,7 +14,6 @@ import '../../widgets/detail_drawer.dart';
import 'mobile_gateway_pairing_guide_page.dart';
import 'mobile_shell_core.dart';
import 'mobile_shell_sheet.dart';
-import 'mobile_shell_workspace.dart';
import 'mobile_shell_nav.dart';
class MobileSafeStripInternal extends StatelessWidget {
diff --git a/lib/features/mobile/mobile_shell_workspace.dart b/lib/features/mobile/mobile_shell_workspace.dart
deleted file mode 100644
index 8f205d3c..00000000
--- a/lib/features/mobile/mobile_shell_workspace.dart
+++ /dev/null
@@ -1,450 +0,0 @@
-// ignore_for_file: unused_import, unnecessary_import
-
-import 'dart:async';
-import 'package:flutter/material.dart';
-import '../../app/app_controller.dart';
-import '../../app/ui_feature_manifest.dart';
-import '../../app/workspace_page_registry.dart';
-import '../../i18n/app_language.dart';
-import '../../models/app_models.dart';
-import '../../runtime/runtime_models.dart';
-import '../../theme/app_palette.dart';
-import '../../theme/app_theme.dart';
-import '../../widgets/detail_drawer.dart';
-import 'mobile_gateway_pairing_guide_page.dart';
-import 'mobile_shell_core.dart';
-import 'mobile_shell_strip.dart';
-import 'mobile_shell_sheet.dart';
-import 'mobile_shell_nav.dart';
-
-class MobileWorkspaceLauncherInternal extends StatelessWidget {
- const MobileWorkspaceLauncherInternal({
- super.key,
- required this.controller,
- required this.onOpenGatewayConnect,
- required this.onSelectDestination,
- });
-
- final AppController controller;
- final VoidCallback onOpenGatewayConnect;
- final ValueChanged onSelectDestination;
-
- @override
- Widget build(BuildContext context) {
- final connection = controller.connection;
- final palette = context.palette;
- final features = controller.featuresFor(UiFeaturePlatform.mobile);
- final entries =
- [
- WorkspaceEntryInternal(
- destination: WorkspaceDestination.skills,
- subtitle: appText('技能包与依赖状态', 'Packages and dependency status'),
- iconColor: palette.accent,
- iconBackground: palette.accentMuted,
- ),
- WorkspaceEntryInternal(
- destination: WorkspaceDestination.nodes,
- subtitle: appText('边缘节点与实例', 'Edge nodes and instances'),
- iconColor: tealLineInternal,
- iconBackground: tealSoftInternal,
- ),
- WorkspaceEntryInternal(
- destination: WorkspaceDestination.agents,
- subtitle: appText('代理运行态与配置', 'Agent state and configuration'),
- iconColor: palette.warning,
- iconBackground: palette.warning.withValues(alpha: 0.12),
- ),
- WorkspaceEntryInternal(
- destination: WorkspaceDestination.mcpServer,
- subtitle: appText('MCP 连接与工具注册', 'MCP endpoints and tools'),
- iconColor: palette.accent,
- iconBackground: palette.accentMuted,
- ),
- WorkspaceEntryInternal(
- destination: WorkspaceDestination.clawHub,
- subtitle: appText('技能与模板市场', 'Marketplace and templates'),
- iconColor: violetLineInternal,
- iconBackground: violetSoftInternal,
- ),
- WorkspaceEntryInternal(
- destination: WorkspaceDestination.aiGateway,
- subtitle: appText('模型与代理网关', 'Models and agent gateway'),
- iconColor: palette.accent,
- iconBackground: palette.accentMuted,
- ),
- ]
- .where(
- (entry) =>
- features.allowedDestinations.contains(entry.destination),
- )
- .toList(growable: false);
-
- return SingleChildScrollView(
- padding: const EdgeInsets.fromLTRB(18, 18, 18, 12),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- LauncherHeaderInternal(
- title: appText('工作区', 'Workspace'),
- subtitle: appText(
- 'Android 与 iOS 统一移动入口,集中访问全部核心模块。',
- 'Shared mobile entry for Android and iOS with access to all core modules.',
- ),
- primaryLabel: connection.status == RuntimeConnectionStatus.connected
- ? appText('查看连接', 'Connection')
- : appText('连接 Bridge', 'Connect Bridge'),
- secondaryLabel: appText('返回助手', 'Open Assistant'),
- onPrimaryPressed: onOpenGatewayConnect,
- onSecondaryPressed: () =>
- onSelectDestination(WorkspaceDestination.assistant),
- ),
- const SizedBox(height: 18),
- WorkspaceHeroInternal(
- connection: connection,
- activeAgentName: controller.activeAgentName,
- sessionCount: controller.sessions.length,
- runningTaskCount: controller.tasksController.running.length,
- ),
- const SizedBox(height: 18),
- LayoutBuilder(
- builder: (context, constraints) {
- final columns = constraints.maxWidth >= 760 ? 2 : 1;
- final width = columns == 2
- ? (constraints.maxWidth - 16) / 2
- : constraints.maxWidth;
- return Wrap(
- spacing: 16,
- runSpacing: 16,
- children: entries
- .map(
- (entry) => SizedBox(
- width: width,
- child: WorkspaceShortcutCardInternal(
- entry: entry,
- onTap: () => onSelectDestination(entry.destination),
- ),
- ),
- )
- .toList(),
- );
- },
- ),
- ],
- ),
- );
- }
-}
-
-class WorkspaceEntryInternal {
- const WorkspaceEntryInternal({
- required this.destination,
- required this.subtitle,
- required this.iconColor,
- required this.iconBackground,
- });
-
- final WorkspaceDestination destination;
- final String subtitle;
- final Color iconColor;
- final Color iconBackground;
-}
-
-class LauncherHeaderInternal extends StatelessWidget {
- const LauncherHeaderInternal({
- super.key,
- required this.title,
- required this.subtitle,
- required this.primaryLabel,
- required this.secondaryLabel,
- required this.onPrimaryPressed,
- required this.onSecondaryPressed,
- });
-
- final String title;
- final String subtitle;
- final String primaryLabel;
- final String secondaryLabel;
- final VoidCallback onPrimaryPressed;
- final VoidCallback onSecondaryPressed;
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- final palette = context.palette;
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- title,
- style: theme.textTheme.headlineSmall?.copyWith(
- fontWeight: FontWeight.w600,
- color: palette.textPrimary,
- ),
- ),
- const SizedBox(height: 8),
- Text(
- subtitle,
- style: theme.textTheme.bodyLarge?.copyWith(
- color: palette.textSecondary,
- ),
- ),
- const SizedBox(height: 16),
- Wrap(
- spacing: 12,
- runSpacing: 12,
- children: [
- GradientActionButtonInternal(
- label: primaryLabel,
- onPressed: onPrimaryPressed,
- ),
- OutlinedButton.icon(
- onPressed: onSecondaryPressed,
- icon: const Icon(Icons.arrow_outward_rounded),
- label: Text(secondaryLabel),
- ),
- ],
- ),
- ],
- );
- }
-}
-
-class WorkspaceHeroInternal extends StatelessWidget {
- const WorkspaceHeroInternal({
- super.key,
- required this.connection,
- required this.activeAgentName,
- required this.sessionCount,
- required this.runningTaskCount,
- });
-
- final GatewayConnectionSnapshot connection;
- final String activeAgentName;
- final int sessionCount;
- final int runningTaskCount;
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- final palette = context.palette;
- final statusLabel = connection.status == RuntimeConnectionStatus.connected
- ? appText('会话已就绪', 'Session Ready')
- : appText('等待接入', 'Awaiting Connection');
- final statusColor = connection.status == RuntimeConnectionStatus.connected
- ? palette.success
- : palette.textSecondary;
-
- return Container(
- width: double.infinity,
- padding: const EdgeInsets.all(16),
- decoration: BoxDecoration(
- color: palette.surfacePrimary,
- borderRadius: BorderRadius.circular(AppRadius.card),
- border: Border.all(color: palette.strokeSoft),
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- statusLabel,
- style: theme.textTheme.labelLarge?.copyWith(color: statusColor),
- ),
- const SizedBox(height: 10),
- Text(
- connection.remoteAddress ?? 'xworkmate.svc.plus',
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- style: theme.textTheme.headlineSmall?.copyWith(
- fontWeight: FontWeight.w600,
- color: palette.textPrimary,
- ),
- ),
- const SizedBox(height: 8),
- Text(
- activeAgentName,
- style: theme.textTheme.bodyLarge?.copyWith(
- color: palette.textSecondary,
- ),
- ),
- const SizedBox(height: 18),
- Wrap(
- spacing: 12,
- runSpacing: 12,
- children: [
- HeroMetricInternal(
- label: appText('会话', 'Sessions'),
- value: '$sessionCount',
- icon: Icons.chat_bubble_outline_rounded,
- ),
- HeroMetricInternal(
- label: appText('运行任务', 'Running'),
- value: '$runningTaskCount',
- icon: Icons.play_circle_outline_rounded,
- ),
- HeroMetricInternal(
- label: appText('状态', 'Status'),
- value: connection.status.label,
- icon: Icons.monitor_heart_outlined,
- ),
- ],
- ),
- ],
- ),
- );
- }
-}
-
-class HeroMetricInternal extends StatelessWidget {
- const HeroMetricInternal({
- super.key,
- required this.label,
- required this.value,
- required this.icon,
- });
-
- final String label;
- final String value;
- final IconData icon;
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- final palette = context.palette;
- return Container(
- padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
- decoration: BoxDecoration(
- color: palette.surfaceSecondary.withValues(alpha: 0.94),
- borderRadius: BorderRadius.circular(AppRadius.card),
- border: Border.all(color: palette.strokeSoft),
- ),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Icon(icon, size: 18, color: palette.accent),
- const SizedBox(width: 8),
- Text(
- '$label · $value',
- style: theme.textTheme.labelLarge?.copyWith(
- color: palette.textPrimary,
- ),
- ),
- ],
- ),
- );
- }
-}
-
-class WorkspaceShortcutCardInternal extends StatelessWidget {
- const WorkspaceShortcutCardInternal({
- super.key,
- required this.entry,
- required this.onTap,
- });
-
- final WorkspaceEntryInternal entry;
- final VoidCallback onTap;
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- final palette = context.palette;
- return Material(
- color: Colors.transparent,
- child: InkWell(
- onTap: onTap,
- borderRadius: BorderRadius.circular(AppRadius.card),
- child: Ink(
- padding: const EdgeInsets.all(16),
- decoration: BoxDecoration(
- color: palette.surfacePrimary,
- borderRadius: BorderRadius.circular(AppRadius.card),
- border: Border.all(color: palette.strokeSoft),
- ),
- child: Row(
- children: [
- Container(
- width: 48,
- height: 48,
- decoration: BoxDecoration(
- color: entry.iconBackground,
- borderRadius: BorderRadius.circular(AppRadius.card),
- ),
- child: Icon(
- entry.destination.icon,
- color: entry.iconColor,
- size: 22,
- ),
- ),
- const SizedBox(width: 12),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- entry.destination.label,
- style: theme.textTheme.titleMedium?.copyWith(
- fontWeight: FontWeight.w600,
- color: palette.textPrimary,
- ),
- ),
- const SizedBox(height: 4),
- Text(
- entry.subtitle,
- maxLines: 2,
- overflow: TextOverflow.ellipsis,
- style: theme.textTheme.bodyMedium?.copyWith(
- color: palette.textSecondary,
- ),
- ),
- ],
- ),
- ),
- const SizedBox(width: 12),
- Icon(Icons.chevron_right_rounded, color: palette.textSecondary),
- ],
- ),
- ),
- ),
- );
- }
-}
-
-class GradientActionButtonInternal extends StatelessWidget {
- const GradientActionButtonInternal({
- super.key,
- required this.label,
- required this.onPressed,
- });
-
- final String label;
- final VoidCallback onPressed;
-
- @override
- Widget build(BuildContext context) {
- final palette = context.palette;
- return DecoratedBox(
- decoration: BoxDecoration(
- gradient: LinearGradient(
- begin: Alignment.topLeft,
- end: Alignment.bottomRight,
- colors: [palette.accent, palette.accentHover],
- ),
- borderRadius: BorderRadius.circular(AppRadius.button),
- ),
- child: FilledButton(
- onPressed: onPressed,
- style: FilledButton.styleFrom(
- backgroundColor: Colors.transparent,
- foregroundColor: Colors.white,
- shadowColor: Colors.transparent,
- minimumSize: const Size(0, AppSizes.buttonHeightMobile),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(AppRadius.button),
- ),
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 0),
- ),
- child: Text(label),
- ),
- );
- }
-}
diff --git a/lib/features/modules/modules_page.dart b/lib/features/modules/modules_page.dart
deleted file mode 100644
index 6aca7c08..00000000
--- a/lib/features/modules/modules_page.dart
+++ /dev/null
@@ -1,799 +0,0 @@
-import 'package:flutter/material.dart';
-
-import '../../app/app_controller.dart';
-import '../../app/ui_feature_manifest.dart';
-import '../../app/workspace_navigation.dart';
-import '../../app/app_metadata.dart';
-import '../../i18n/app_language.dart';
-import '../../models/app_models.dart';
-import '../../runtime/runtime_models.dart';
-import '../../widgets/metric_card.dart';
-import '../../widgets/section_header.dart';
-import '../../widgets/section_tabs.dart';
-import '../../widgets/status_badge.dart';
-import '../../widgets/surface_card.dart';
-import '../../widgets/top_bar.dart';
-
-class ModulesPage extends StatefulWidget {
- const ModulesPage({
- super.key,
- required this.controller,
- required this.onOpenDetail,
- this.initialTab,
- });
-
- final AppController controller;
- final ValueChanged onOpenDetail;
- final ModulesTab? initialTab;
-
- @override
- State createState() => _ModulesPageState();
-}
-
-class _ModulesPageState extends State {
- late ModulesTab _tab;
-
- ModulesTab _normalizeTab(ModulesTab tab) {
- final normalized = tab == ModulesTab.gateway ? ModulesTab.nodes : tab;
- if (_isTabVisible(normalized)) {
- return normalized;
- }
- return ModulesTab.skills;
- }
-
- bool _isTabVisible(ModulesTab tab) {
- if (tab == ModulesTab.clawHub) {
- final features = widget.controller.featuresFor(UiFeaturePlatform.desktop);
- return features.isEnabledPath(UiFeatureKeys.workspaceClawHub);
- }
- if (tab == ModulesTab.connectors) {
- final features = widget.controller.featuresFor(UiFeaturePlatform.desktop);
- return features.isEnabledPath(UiFeatureKeys.workspaceConnectors);
- }
- return true;
- }
-
- List get _visibleTabs => ModulesTab.values
- .where((item) => item != ModulesTab.gateway)
- .where(_isTabVisible)
- .toList(growable: false);
-
- ModulesTab _tabForLabel(String value) {
- return _visibleTabs.firstWhere(
- (item) => item.label == value,
- orElse: () => ModulesTab.skills,
- );
- }
-
- @override
- void initState() {
- super.initState();
- _tab = _normalizeTab(widget.initialTab ?? widget.controller.modulesTab);
- }
-
- @override
- void didUpdateWidget(covariant ModulesPage oldWidget) {
- super.didUpdateWidget(oldWidget);
- final nextTab = _normalizeTab(
- widget.initialTab ?? widget.controller.modulesTab,
- );
- if (nextTab != _tab) {
- setState(() => _tab = nextTab);
- }
- }
-
- @override
- Widget build(BuildContext context) {
- final controller = widget.controller;
- final metrics = [
- MetricSummary(
- label: appText('网关', 'Gateway'),
- value: controller.connection.status.label,
- caption: controller.connection.remoteAddress ?? kAppVersionLabel,
- icon: Icons.wifi_tethering_rounded,
- status: _connectionStatus(controller.connection.status),
- ),
- MetricSummary(
- label: appText('节点', 'Nodes'),
- value: '${controller.instances.length}',
- caption: appText(
- '${controller.instances.where((item) => item.mode == 'active').length} 个活跃实例',
- '${controller.instances.where((item) => item.mode == 'active').length} active',
- ),
- icon: Icons.developer_board_rounded,
- ),
- MetricSummary(
- label: appText('代理', 'Agents'),
- value: '${controller.agents.length}',
- caption: controller.activeAgentName,
- icon: Icons.hub_rounded,
- ),
- ];
-
- return AnimatedBuilder(
- animation: controller,
- builder: (context, _) {
- return SingleChildScrollView(
- padding: const EdgeInsets.fromLTRB(32, 32, 32, 8),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- TopBar(
- breadcrumbs: buildWorkspaceBreadcrumbs(
- controller: controller,
- rootLabel: appText('模块', 'Modules'),
- sectionLabel: _tab.label,
- ),
- title: appText('模块', 'Modules'),
- subtitle: appText(
- '管理代理、节点、技能和平台服务。',
- 'Manage agents, nodes, skills, and platform services.',
- ),
- trailing: Wrap(
- spacing: 12,
- runSpacing: 12,
- children: [
- SizedBox(
- width: 220,
- child: TextField(
- decoration: InputDecoration(
- hintText: appText('搜索模块', 'Search modules'),
- prefixIcon: Icon(Icons.search_rounded),
- ),
- ),
- ),
- IconButton(
- onPressed: () async {
- await controller.refreshGatewayHealth();
- await controller.refreshAgents();
- await controller.refreshSessions();
- await controller.instancesController.refresh();
- await controller.skillsController.refresh(
- agentId: controller.selectedAgentId.isEmpty
- ? null
- : controller.selectedAgentId,
- );
- await controller.modelsController.refresh();
- await controller.cronJobsController.refresh();
- },
- icon: const Icon(Icons.refresh_rounded),
- ),
- FilledButton.tonalIcon(
- onPressed: () =>
- controller.openSettings(tab: SettingsTab.gateway),
- icon: const Icon(Icons.add_rounded),
- label: Text(appText('打开设置中心', 'Open Settings')),
- ),
- ],
- ),
- ),
- const SizedBox(height: 24),
- SectionTabs(
- items: _visibleTabs.map((item) => item.label).toList(),
- value: _tab.label,
- onChanged: (value) => setState(() {
- _tab = _tabForLabel(value);
- controller.openModules(tab: _tab);
- }),
- ),
- const SizedBox(height: 24),
- LayoutBuilder(
- builder: (context, constraints) {
- final width = constraints.maxWidth > 980
- ? (constraints.maxWidth - 32) / 3
- : constraints.maxWidth > 640
- ? (constraints.maxWidth - 16) / 2
- : constraints.maxWidth;
- return Wrap(
- spacing: 16,
- runSpacing: 16,
- children: metrics
- .map(
- (metric) => SizedBox(
- width: width,
- child: MetricCard(metric: metric),
- ),
- )
- .toList(),
- );
- },
- ),
- const SizedBox(height: 28),
- switch (_tab) {
- ModulesTab.nodes => _NodesPanel(
- controller: controller,
- onOpenDetail: widget.onOpenDetail,
- ),
- ModulesTab.agents => _AgentsPanel(
- controller: controller,
- onOpenDetail: widget.onOpenDetail,
- ),
- ModulesTab.skills => _SkillsPanel(
- controller: controller,
- onOpenDetail: widget.onOpenDetail,
- ),
- ModulesTab.clawHub => _SkillsPanel(
- controller: controller,
- onOpenDetail: widget.onOpenDetail,
- ),
- ModulesTab.connectors => _SkillsPanel(
- controller: controller,
- onOpenDetail: widget.onOpenDetail,
- ),
- ModulesTab.gateway => _NodesPanel(
- controller: controller,
- onOpenDetail: widget.onOpenDetail,
- ),
- },
- ],
- ),
- );
- },
- );
- }
-}
-
-class _NodesPanel extends StatelessWidget {
- const _NodesPanel({required this.controller, required this.onOpenDetail});
-
- final AppController controller;
- final ValueChanged onOpenDetail;
-
- @override
- Widget build(BuildContext context) {
- final items = controller.instances;
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- SectionHeader(
- title: appText('节点', 'Nodes'),
- subtitle: appText(
- '来自 Gateway 运行时的在线实例与存在性数据。',
- 'Live system-presence data from the gateway runtime.',
- ),
- ),
- const SizedBox(height: 16),
- if (items.isEmpty)
- SurfaceCard(
- child: Text(
- controller.connection.status == RuntimeConnectionStatus.connected
- ? appText('暂时还没有上报在线实例。', 'No live instances reported yet.')
- : appText(
- '恢复 xworkmate-bridge 连接后可加载实例与在线状态。',
- 'Instances and presence return after xworkmate-bridge reconnects.',
- ),
- ),
- )
- else
- ...items.map(
- (node) => Padding(
- padding: const EdgeInsets.only(bottom: 12),
- child: SurfaceCard(
- onTap: () => onOpenDetail(
- DetailPanelData(
- title: node.host ?? node.id,
- subtitle: appText('实例', 'Instance'),
- icon: Icons.developer_board_rounded,
- status: _instanceStatus(node),
- description: node.text,
- meta: [
- node.platform ?? appText('未知', 'unknown'),
- node.deviceFamily ?? appText('未知', 'unknown'),
- ],
- actions: [appText('刷新', 'Refresh')],
- sections: [
- DetailSection(
- title: appText('运行时', 'Runtime'),
- items: [
- DetailItem(label: 'IP', value: node.ip ?? 'n/a'),
- DetailItem(
- label: 'Version',
- value: node.version ?? 'n/a',
- ),
- DetailItem(
- label: appText('模式', 'Mode'),
- value: node.mode ?? 'n/a',
- ),
- DetailItem(
- label: appText('最近输入', 'Last Input'),
- value: node.lastInputSeconds == null
- ? 'n/a'
- : '${node.lastInputSeconds}s',
- ),
- ],
- ),
- ],
- ),
- ),
- child: Row(
- children: [
- Expanded(
- flex: 4,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- node.host ?? node.id,
- style: Theme.of(context).textTheme.titleMedium,
- ),
- const SizedBox(height: 6),
- Text(
- '${node.platform ?? appText('未知', 'unknown')} · ${node.deviceFamily ?? appText('未知', 'unknown')}',
- style: Theme.of(context).textTheme.bodySmall,
- ),
- ],
- ),
- ),
- Expanded(
- flex: 2,
- child: StatusBadge(status: _instanceStatus(node)),
- ),
- Expanded(flex: 2, child: Text(node.version ?? 'n/a')),
- Expanded(flex: 2, child: Text(node.mode ?? 'n/a')),
- const Icon(Icons.chevron_right_rounded),
- ],
- ),
- ),
- ),
- ),
- ],
- );
- }
-}
-
-class _AgentsPanel extends StatelessWidget {
- const _AgentsPanel({required this.controller, required this.onOpenDetail});
-
- final AppController controller;
- final ValueChanged onOpenDetail;
-
- @override
- Widget build(BuildContext context) {
- final items = controller.agents;
- return LayoutBuilder(
- builder: (context, constraints) {
- final width = constraints.maxWidth > 1220
- ? (constraints.maxWidth - 32) / 3
- : constraints.maxWidth > 760
- ? (constraints.maxWidth - 16) / 2
- : constraints.maxWidth;
- if (items.isEmpty) {
- return SurfaceCard(
- child: Text(
- controller.connection.status == RuntimeConnectionStatus.connected
- ? appText(
- '网关当前没有返回代理列表。',
- 'No agents reported by the gateway.',
- )
- : appText(
- '恢复 xworkmate-bridge 连接后可加载代理。',
- 'Agents return after xworkmate-bridge reconnects.',
- ),
- ),
- );
- }
- return Wrap(
- spacing: 16,
- runSpacing: 16,
- children: items
- .map(
- (agent) => SizedBox(
- width: width,
- child: SurfaceCard(
- onTap: () => onOpenDetail(
- DetailPanelData(
- title: agent.name,
- subtitle: appText('代理', 'Agent'),
- icon: Icons.hub_rounded,
- status: controller.selectedAgentId == agent.id
- ? StatusInfo(
- appText('已选中', 'Selected'),
- StatusTone.accent,
- )
- : StatusInfo(
- appText('可用', 'Available'),
- StatusTone.success,
- ),
- description: appText(
- '可用于会话路由的 Gateway 执行代理。',
- 'Gateway operator agent available for session routing.',
- ),
- meta: [agent.id, agent.theme],
- actions: [
- appText('选择', 'Select'),
- appText('打开会话', 'Open Session'),
- ],
- sections: [
- DetailSection(
- title: appText('身份信息', 'Identity'),
- items: [
- DetailItem(
- label: appText('名称', 'Name'),
- value: agent.name,
- ),
- DetailItem(label: 'ID', value: agent.id),
- DetailItem(
- label: appText('主题', 'Theme'),
- value: agent.theme,
- ),
- ],
- ),
- ],
- ),
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- Expanded(
- child: Text(
- agent.name,
- style: Theme.of(context).textTheme.titleLarge,
- ),
- ),
- StatusBadge(
- status: controller.selectedAgentId == agent.id
- ? StatusInfo(
- appText('已选中', 'Selected'),
- StatusTone.accent,
- )
- : StatusInfo(
- appText('就绪', 'Ready'),
- StatusTone.success,
- ),
- compact: true,
- ),
- ],
- ),
- const SizedBox(height: 10),
- Text(
- 'ID: ${agent.id}',
- style: Theme.of(context).textTheme.bodyMedium,
- ),
- const SizedBox(height: 14),
- Wrap(
- spacing: 8,
- runSpacing: 8,
- children: [
- FilledButton.tonal(
- onPressed: () => controller.selectAgent(agent.id),
- child: Text(appText('选择', 'Select')),
- ),
- OutlinedButton(
- onPressed: () => controller.refreshSessions(),
- child: Text(appText('打开', 'Open')),
- ),
- ],
- ),
- ],
- ),
- ),
- ),
- )
- .toList(),
- );
- },
- );
- }
-}
-
-class _SkillsPanel extends StatelessWidget {
- const _SkillsPanel({required this.controller, required this.onOpenDetail});
-
- final AppController controller;
- final ValueChanged onOpenDetail;
-
- @override
- Widget build(BuildContext context) {
- final items = controller.skills;
- final currentMode = controller.currentAssistantExecutionTarget;
- final modeCards = _buildModeCards(items, currentMode);
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- SectionHeader(
- title: appText('技能模式', 'Skill modes'),
- subtitle: appText(
- '用相同界面简洁区分 Agent 与 Gateway 两种路径,以及各自可用的技能包。',
- 'Keep the same page structure while separating the agent and gateway paths and their available skill packs.',
- ),
- ),
- const SizedBox(height: 16),
- LayoutBuilder(
- builder: (context, constraints) {
- final width = constraints.maxWidth > 1220
- ? (constraints.maxWidth - 32) / 3
- : constraints.maxWidth > 760
- ? (constraints.maxWidth - 16) / 2
- : constraints.maxWidth;
- return Wrap(
- spacing: 16,
- runSpacing: 16,
- children: modeCards
- .map(
- (card) => SizedBox(
- width: width,
- child: _SkillModeCard(data: card),
- ),
- )
- .toList(),
- );
- },
- ),
- const SizedBox(height: 24),
- SectionHeader(
- title: appText('技能明细', 'Skill details'),
- subtitle: appText(
- '保留当前运行时返回的原始技能列表,便于查看状态、来源和依赖。',
- 'Keep the raw runtime skill list for status, source, and dependency inspection.',
- ),
- ),
- const SizedBox(height: 16),
- if (items.isEmpty)
- SurfaceCard(
- child: Text(
- controller.connection.status == RuntimeConnectionStatus.connected
- ? appText(
- '当前网关或代理没有加载技能。',
- 'No skills loaded for the active gateway / agent.',
- )
- : appText(
- '恢复 xworkmate-bridge 连接后可加载技能。',
- 'Skills return after xworkmate-bridge reconnects.',
- ),
- ),
- )
- else
- ...items.map(
- (skill) => Padding(
- padding: const EdgeInsets.only(bottom: 12),
- child: SurfaceCard(
- onTap: () => onOpenDetail(
- DetailPanelData(
- title: skill.name,
- subtitle: appText('技能', 'Skill'),
- icon: Icons.extension_rounded,
- status: skill.disabled
- ? StatusInfo(
- appText('已禁用', 'Disabled'),
- StatusTone.warning,
- )
- : StatusInfo(
- appText('已启用', 'Enabled'),
- StatusTone.success,
- ),
- description: skill.description,
- meta: [skill.source, skill.skillKey],
- actions: [appText('刷新', 'Refresh')],
- sections: [
- DetailSection(
- title: appText('依赖要求', 'Requirements'),
- items: [
- DetailItem(
- label: appText('缺失二进制', 'Missing bins'),
- value: skill.missingBins.isEmpty
- ? appText('无', 'None')
- : skill.missingBins.join(', '),
- ),
- DetailItem(
- label: appText('缺失环境变量', 'Missing env'),
- value: skill.missingEnv.isEmpty
- ? appText('无', 'None')
- : skill.missingEnv.join(', '),
- ),
- DetailItem(
- label: appText('缺失配置', 'Missing config'),
- value: skill.missingConfig.isEmpty
- ? appText('无', 'None')
- : skill.missingConfig.join(', '),
- ),
- ],
- ),
- ],
- ),
- ),
- child: Row(
- children: [
- Expanded(
- flex: 4,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- skill.name,
- style: Theme.of(context).textTheme.titleMedium,
- ),
- const SizedBox(height: 6),
- Text(
- skill.description,
- style: Theme.of(context).textTheme.bodySmall,
- ),
- ],
- ),
- ),
- Expanded(
- flex: 2,
- child: StatusBadge(
- status: skill.disabled
- ? StatusInfo(
- appText('已禁用', 'Disabled'),
- StatusTone.warning,
- )
- : StatusInfo(
- appText('已启用', 'Enabled'),
- StatusTone.success,
- ),
- ),
- ),
- Expanded(flex: 2, child: Text(skill.source)),
- Expanded(
- flex: 2,
- child: Text(skill.primaryEnv ?? 'workspace'),
- ),
- const Icon(Icons.chevron_right_rounded),
- ],
- ),
- ),
- ),
- ),
- ],
- );
- }
-
- List<_SkillModeCardData> _buildModeCards(
- List items,
- AssistantExecutionTarget currentMode,
- ) {
- final gatewaySkills = items.toList(growable: false);
- return <_SkillModeCardData>[
- _SkillModeCardData(
- title: appText('Gateway', 'Gateway'),
- subtitle: appText(
- '通过 xworkmate-bridge 暴露运行时技能,统一承接当前 gateway 路径。',
- 'Expose runtime skill packs through xworkmate-bridge as the single gateway path.',
- ),
- icon: Icons.lan_rounded,
- status: currentMode == AssistantExecutionTarget.gateway
- ? StatusInfo(appText('当前模式', 'Current mode'), StatusTone.accent)
- : StatusInfo(appText('可切换', 'Available'), StatusTone.success),
- chips: [
- appText('统一路由', 'Unified routing'),
- appText('xworkmate-bridge', 'xworkmate-bridge'),
- ],
- skills: currentMode == AssistantExecutionTarget.gateway
- ? gatewaySkills.map((item) => item.name).toList()
- : const [],
- emptyLabel: appText(
- '切换到 Gateway 模式后,将显示当前 bridge 返回的技能包。',
- 'Switch to gateway mode to inspect the active skill packs returned by the bridge.',
- ),
- ),
- ];
- }
-}
-
-class _SkillModeCardData {
- const _SkillModeCardData({
- required this.title,
- required this.subtitle,
- required this.icon,
- required this.status,
- required this.chips,
- required this.skills,
- required this.emptyLabel,
- });
-
- final String title;
- final String subtitle;
- final IconData icon;
- final StatusInfo status;
- final List chips;
- final List skills;
- final String emptyLabel;
-}
-
-class _SkillModeCard extends StatelessWidget {
- const _SkillModeCard({required this.data});
-
- final _SkillModeCardData data;
-
- @override
- Widget build(BuildContext context) {
- return SurfaceCard(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- CircleAvatar(radius: 20, child: Icon(data.icon, size: 20)),
- const SizedBox(width: 12),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- data.title,
- style: Theme.of(context).textTheme.titleMedium,
- ),
- const SizedBox(height: 4),
- StatusBadge(status: data.status, compact: true),
- ],
- ),
- ),
- ],
- ),
- const SizedBox(height: 14),
- Text(data.subtitle, style: Theme.of(context).textTheme.bodySmall),
- if (data.chips.isNotEmpty) ...[
- const SizedBox(height: 14),
- Wrap(
- spacing: 8,
- runSpacing: 8,
- children: data.chips
- .map(
- (item) => Chip(
- label: Text(item),
- visualDensity: VisualDensity.compact,
- ),
- )
- .toList(),
- ),
- ],
- const SizedBox(height: 14),
- Text(
- appText('可用技能包', 'Available skill packs'),
- style: Theme.of(context).textTheme.labelLarge,
- ),
- const SizedBox(height: 8),
- if (data.skills.isEmpty)
- Text(data.emptyLabel, style: Theme.of(context).textTheme.bodySmall)
- else
- Wrap(
- spacing: 8,
- runSpacing: 8,
- children: data.skills
- .map(
- (item) => Chip(
- label: Text(item),
- visualDensity: VisualDensity.compact,
- ),
- )
- .toList(),
- ),
- ],
- ),
- );
- }
-}
-
-StatusInfo _connectionStatus(RuntimeConnectionStatus status) =>
- switch (status) {
- RuntimeConnectionStatus.connected => StatusInfo(
- appText('健康', 'Healthy'),
- StatusTone.success,
- ),
- RuntimeConnectionStatus.connecting => StatusInfo(
- appText('连接中', 'Connecting'),
- StatusTone.accent,
- ),
- RuntimeConnectionStatus.error => StatusInfo(
- appText('错误', 'Error'),
- StatusTone.danger,
- ),
- RuntimeConnectionStatus.offline => StatusInfo(
- appText('离线', 'Offline'),
- StatusTone.neutral,
- ),
- };
-
-StatusInfo _instanceStatus(GatewayInstanceSummary item) {
- final mode = (item.mode ?? '').toLowerCase();
- if (mode.contains('error') || mode.contains('warn')) {
- return StatusInfo(appText('告警', 'Warning'), StatusTone.warning);
- }
- if (mode.contains('active') || mode.contains('online')) {
- return StatusInfo(appText('在线', 'Online'), StatusTone.success);
- }
- return StatusInfo(appText('已发现', 'Seen'), StatusTone.neutral);
-}
diff --git a/lib/features/skills/skills_page.dart b/lib/features/skills/skills_page.dart
deleted file mode 100644
index 573c98b9..00000000
--- a/lib/features/skills/skills_page.dart
+++ /dev/null
@@ -1,480 +0,0 @@
-import 'package:flutter/material.dart';
-
-import '../../app/app_controller.dart';
-import '../../app/workspace_navigation.dart';
-import '../../i18n/app_language.dart';
-import '../../models/app_models.dart';
-import '../../runtime/runtime_models.dart';
-import '../../theme/app_palette.dart';
-import '../../widgets/desktop_workspace_scaffold.dart';
-import '../../widgets/status_badge.dart';
-import '../../widgets/surface_card.dart';
-
-class SkillsPage extends StatefulWidget {
- const SkillsPage({
- super.key,
- required this.controller,
- required this.onOpenDetail,
- });
-
- final AppController controller;
- final ValueChanged onOpenDetail;
-
- @override
- State createState() => _SkillsPageState();
-}
-
-class _SkillsPageState extends State {
- final TextEditingController _searchController = TextEditingController();
- String _query = '';
- String? _selectedSkillKey;
-
- @override
- void dispose() {
- _searchController.dispose();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return AnimatedBuilder(
- animation: widget.controller,
- builder: (context, _) {
- final controller = widget.controller;
- final skills = controller.skills
- .where(_matchesQuery)
- .toList(growable: false);
- final selected = _resolveSelectedSkill(skills);
- return DesktopWorkspaceScaffold(
- breadcrumbs: buildWorkspaceBreadcrumbs(
- controller: controller,
- rootLabel: WorkspaceDestination.skills.label,
- ),
- eyebrow: appText('技能与能力包', 'Skills and capabilities'),
- title: appText('技能工作台', 'Skills workspace'),
- subtitle: appText(
- '左侧浏览技能包,右侧查看描述、依赖和使用建议。',
- 'Browse skills on the left, inspect descriptions, dependencies, and usage on the right.',
- ),
- toolbar: Wrap(
- spacing: 10,
- runSpacing: 10,
- crossAxisAlignment: WrapCrossAlignment.center,
- children: [
- SizedBox(
- width: 240,
- child: TextField(
- controller: _searchController,
- onChanged: (value) {
- setState(() {
- _query = value.trim().toLowerCase();
- });
- },
- decoration: InputDecoration(
- hintText: appText('搜索技能', 'Search skills'),
- prefixIcon: const Icon(Icons.search_rounded),
- suffixIcon: _query.isEmpty
- ? null
- : IconButton(
- tooltip: appText('清除', 'Clear'),
- onPressed: () {
- _searchController.clear();
- setState(() {
- _query = '';
- });
- },
- icon: const Icon(Icons.close_rounded),
- ),
- ),
- ),
- ),
- IconButton(
- tooltip: appText('刷新技能', 'Refresh skills'),
- onPressed: () async {
- await controller.skillsController.refresh(
- agentId: controller.selectedAgentId.isEmpty
- ? null
- : controller.selectedAgentId,
- );
- },
- icon: const Icon(Icons.refresh_rounded),
- ),
- FilledButton.tonalIcon(
- onPressed: () =>
- controller.navigateTo(WorkspaceDestination.assistant),
- icon: const Icon(Icons.auto_awesome_rounded),
- label: Text(appText('回到对话使用', 'Use in assistant')),
- ),
- ],
- ),
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: SurfaceCard(
- padding: EdgeInsets.zero,
- borderRadius: 20,
- child: ClipRRect(
- borderRadius: BorderRadius.circular(20),
- child: Row(
- children: [
- SizedBox(
- width: 360,
- child: _SkillsListPanel(
- skills: skills,
- selectedSkillKey: selected?.skillKey,
- onSelectSkill: (skill) {
- setState(() {
- _selectedSkillKey = skill.skillKey;
- });
- },
- ),
- ),
- Container(width: 1, color: context.palette.strokeSoft),
- Expanded(
- child: _SkillDetailPanel(
- controller: controller,
- selected: selected,
- ),
- ),
- ],
- ),
- ),
- ),
- ),
- );
- },
- );
- }
-
- bool _matchesQuery(GatewaySkillSummary skill) {
- if (_query.isEmpty) {
- return true;
- }
- final haystack = [
- skill.name,
- skill.description,
- skill.source,
- skill.skillKey,
- skill.primaryEnv ?? '',
- ].join(' ').toLowerCase();
- return haystack.contains(_query);
- }
-
- GatewaySkillSummary? _resolveSelectedSkill(List skills) {
- if (skills.isEmpty) {
- return null;
- }
- for (final skill in skills) {
- if (skill.skillKey == _selectedSkillKey) {
- return skill;
- }
- }
- return skills.first;
- }
-}
-
-class _SkillsListPanel extends StatelessWidget {
- const _SkillsListPanel({
- required this.skills,
- required this.selectedSkillKey,
- required this.onSelectSkill,
- });
-
- final List skills;
- final String? selectedSkillKey;
- final ValueChanged onSelectSkill;
-
- @override
- Widget build(BuildContext context) {
- final palette = context.palette;
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Padding(
- padding: const EdgeInsets.fromLTRB(14, 14, 14, 10),
- child: Row(
- children: [
- Text(
- appText('技能列表', 'Skill list'),
- style: Theme.of(context).textTheme.titleSmall,
- ),
- const SizedBox(width: 8),
- Text(
- '${skills.length}',
- style: Theme.of(
- context,
- ).textTheme.bodySmall?.copyWith(color: palette.textMuted),
- ),
- ],
- ),
- ),
- Container(height: 1, color: palette.strokeSoft),
- Expanded(
- child: skills.isEmpty
- ? Center(
- child: Padding(
- padding: const EdgeInsets.all(20),
- child: Text(
- appText(
- '当前没有可展示的技能。',
- 'No skills are available right now.',
- ),
- textAlign: TextAlign.center,
- style: Theme.of(context).textTheme.bodyMedium?.copyWith(
- color: palette.textSecondary,
- ),
- ),
- ),
- )
- : ListView.separated(
- padding: const EdgeInsets.all(10),
- itemCount: skills.length,
- separatorBuilder: (_, _) => const SizedBox(height: 8),
- itemBuilder: (context, index) {
- final skill = skills[index];
- return _SkillListTile(
- skill: skill,
- selected: skill.skillKey == selectedSkillKey,
- onTap: () => onSelectSkill(skill),
- );
- },
- ),
- ),
- ],
- );
- }
-}
-
-class _SkillListTile extends StatelessWidget {
- const _SkillListTile({
- required this.skill,
- required this.selected,
- required this.onTap,
- });
-
- final GatewaySkillSummary skill;
- final bool selected;
- final VoidCallback onTap;
-
- @override
- Widget build(BuildContext context) {
- final palette = context.palette;
- return Material(
- color: selected ? palette.surfacePrimary : Colors.transparent,
- borderRadius: BorderRadius.circular(18),
- child: InkWell(
- onTap: onTap,
- borderRadius: BorderRadius.circular(18),
- child: Container(
- padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(18),
- color: selected ? palette.surfaceSecondary : Colors.transparent,
- boxShadow: selected
- ? [
- BoxShadow(
- color: palette.shadow.withValues(alpha: 0.06),
- blurRadius: 8,
- offset: const Offset(0, 2),
- ),
- ]
- : const [],
- ),
- child: Text(
- skill.name,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- style: Theme.of(context).textTheme.titleSmall?.copyWith(
- fontWeight: FontWeight.w600,
- color: selected ? palette.textPrimary : null,
- ),
- ),
- ),
- ),
- );
- }
-}
-
-class _SkillDetailPanel extends StatelessWidget {
- const _SkillDetailPanel({required this.controller, required this.selected});
-
- final AppController controller;
- final GatewaySkillSummary? selected;
-
- @override
- Widget build(BuildContext context) {
- final palette = context.palette;
- if (selected == null) {
- return Center(
- child: Text(
- appText('选择左侧技能查看详情。', 'Select a skill on the left.'),
- style: Theme.of(
- context,
- ).textTheme.bodyLarge?.copyWith(color: palette.textSecondary),
- ),
- );
- }
-
- return Padding(
- padding: const EdgeInsets.all(18),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Wrap(
- spacing: 8,
- runSpacing: 8,
- children: [
- Text(
- selected!.name,
- style: Theme.of(context).textTheme.headlineSmall?.copyWith(
- fontWeight: FontWeight.w600,
- ),
- ),
- StatusBadge(
- status: selected!.disabled
- ? _skillStatus(
- appText('已禁用', 'Disabled'),
- StatusTone.warning,
- )
- : _skillStatus(
- appText('已启用', 'Enabled'),
- StatusTone.success,
- ),
- ),
- ],
- ),
- const SizedBox(height: 8),
- Text(
- selected!.description,
- style: Theme.of(context).textTheme.bodyMedium?.copyWith(
- color: palette.textSecondary,
- height: 1.5,
- ),
- ),
- const SizedBox(height: 18),
- Wrap(
- spacing: 12,
- runSpacing: 12,
- children: [
- _DependencyCard(
- title: appText('缺失二进制', 'Missing bins'),
- values: selected!.missingBins,
- ),
- _DependencyCard(
- title: appText('缺失环境变量', 'Missing env'),
- values: selected!.missingEnv,
- ),
- _DependencyCard(
- title: appText('缺失配置', 'Missing config'),
- values: selected!.missingConfig,
- ),
- ],
- ),
- const SizedBox(height: 18),
- Container(
- width: double.infinity,
- padding: const EdgeInsets.all(14),
- decoration: BoxDecoration(
- color: palette.surfaceSecondary,
- borderRadius: BorderRadius.circular(20),
- boxShadow: [
- BoxShadow(
- color: palette.shadow.withValues(alpha: 0.04),
- blurRadius: 8,
- offset: const Offset(0, 2),
- ),
- ],
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- appText('在对话中使用', 'Use in the assistant'),
- style: Theme.of(context).textTheme.titleSmall,
- ),
- const SizedBox(height: 8),
- Text(
- appText(
- '回到 Assistant 后,可通过下方建议按钮或直接描述需求来调用该技能上下文。',
- 'After returning to Assistant, use the suggested chips or describe the task directly to route into this skill context.',
- ),
- style: Theme.of(context).textTheme.bodyMedium?.copyWith(
- color: palette.textSecondary,
- height: 1.45,
- ),
- ),
- ],
- ),
- ),
- const Spacer(),
- Wrap(
- spacing: 10,
- runSpacing: 10,
- children: [
- FilledButton.icon(
- onPressed: () =>
- controller.navigateTo(WorkspaceDestination.assistant),
- icon: const Icon(Icons.auto_awesome_rounded),
- label: Text(appText('去对话中使用', 'Use in assistant')),
- ),
- OutlinedButton.icon(
- onPressed: () async {
- await controller.skillsController.refresh(
- agentId: controller.selectedAgentId.isEmpty
- ? null
- : controller.selectedAgentId,
- );
- },
- icon: const Icon(Icons.refresh_rounded),
- label: Text(appText('刷新', 'Refresh')),
- ),
- ],
- ),
- ],
- ),
- );
- }
-}
-
-class _DependencyCard extends StatelessWidget {
- const _DependencyCard({required this.title, required this.values});
-
- final String title;
- final List values;
-
- @override
- Widget build(BuildContext context) {
- final palette = context.palette;
- return Container(
- width: 220,
- padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(
- color: palette.surfaceSecondary,
- borderRadius: BorderRadius.circular(18),
- boxShadow: [
- BoxShadow(
- color: palette.shadow.withValues(alpha: 0.04),
- blurRadius: 8,
- offset: const Offset(0, 2),
- ),
- ],
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(title, style: Theme.of(context).textTheme.titleSmall),
- const SizedBox(height: 8),
- Text(
- values.isEmpty ? appText('无', 'None') : values.join(', '),
- style: Theme.of(context).textTheme.bodySmall?.copyWith(
- color: palette.textSecondary,
- height: 1.45,
- ),
- ),
- ],
- ),
- );
- }
-}
-
-StatusInfo _skillStatus(String label, StatusTone tone) =>
- StatusInfo(label, tone);
diff --git a/lib/features/tasks/tasks_page.dart b/lib/features/tasks/tasks_page.dart
deleted file mode 100644
index fd414607..00000000
--- a/lib/features/tasks/tasks_page.dart
+++ /dev/null
@@ -1,583 +0,0 @@
-import 'package:flutter/material.dart';
-
-import '../../app/app_controller.dart';
-import '../../app/workspace_navigation.dart';
-import '../../i18n/app_language.dart';
-import '../../models/app_models.dart';
-import '../../runtime/runtime_models.dart';
-import '../../theme/app_palette.dart';
-import '../../widgets/desktop_workspace_scaffold.dart';
-import '../../widgets/metric_card.dart';
-import '../../widgets/section_tabs.dart';
-import '../../widgets/status_badge.dart';
-import '../../widgets/surface_card.dart';
-
-class TasksPage extends StatefulWidget {
- const TasksPage({
- super.key,
- required this.controller,
- required this.onOpenDetail,
- });
-
- final AppController controller;
- final ValueChanged onOpenDetail;
-
- @override
- State createState() => _TasksPageState();
-}
-
-class _TasksPageState extends State {
- TasksTab _tab = TasksTab.queue;
- final TextEditingController _searchController = TextEditingController();
- String _query = '';
- String? _selectedTaskId;
-
- @override
- void dispose() {
- _searchController.dispose();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- final controller = widget.controller;
- final allItems = controller.taskItemsForTab(_tabKey);
- final items = allItems.where(_matchesQuery).toList(growable: false);
- final selected = _resolveSelectedTask(items);
- final metrics = [
- MetricSummary(
- label: appText('总数', 'Total'),
- value: '${controller.tasksController.totalCount}',
- caption: appText('任务 / 会话聚合', 'Task / session aggregate'),
- icon: Icons.layers_rounded,
- ),
- MetricSummary(
- label: appText('运行中', 'Running'),
- value: '${controller.tasksController.running.length}',
- caption: appText('当前活跃执行', 'Active executions'),
- icon: Icons.play_circle_outline_rounded,
- status: _taskStatusInfo('Running'),
- ),
- MetricSummary(
- label: appText('失败', 'Failed'),
- value: '${controller.tasksController.failed.length}',
- caption: appText('中断或报错', 'Interrupted or failed'),
- icon: Icons.error_outline_rounded,
- status: _taskStatusInfo('Failed'),
- ),
- MetricSummary(
- label: appText('计划中', 'Scheduled'),
- value: '${controller.tasksController.scheduled.length}',
- caption: appText('来自 cron 调度器', 'Loaded from cron scheduler'),
- icon: Icons.event_repeat_rounded,
- ),
- ];
-
- return AnimatedBuilder(
- animation: controller,
- builder: (context, _) {
- final palette = context.palette;
- return DesktopWorkspaceScaffold(
- breadcrumbs: buildWorkspaceBreadcrumbs(
- controller: controller,
- rootLabel: WorkspaceDestination.tasks.label,
- ),
- eyebrow: appText('任务与线程', 'Tasks and sessions'),
- title: appText('任务工作台', 'Task workspace'),
- subtitle: appText(
- '左侧筛选和切换任务,右侧查看当前任务详情。',
- 'Filter and switch tasks on the left, inspect the current task on the right.',
- ),
- toolbar: Wrap(
- spacing: 10,
- runSpacing: 10,
- crossAxisAlignment: WrapCrossAlignment.center,
- children: [
- SizedBox(
- width: 240,
- child: TextField(
- controller: _searchController,
- onChanged: (value) {
- setState(() {
- _query = value.trim().toLowerCase();
- });
- },
- decoration: InputDecoration(
- hintText: appText('搜索任务 / 会话', 'Search tasks / sessions'),
- prefixIcon: const Icon(Icons.search_rounded),
- suffixIcon: _query.isEmpty
- ? null
- : IconButton(
- tooltip: appText('清除', 'Clear'),
- onPressed: () {
- _searchController.clear();
- setState(() {
- _query = '';
- });
- },
- icon: const Icon(Icons.close_rounded),
- ),
- ),
- ),
- ),
- IconButton(
- tooltip: appText('刷新任务', 'Refresh tasks'),
- onPressed: controller.refreshSessions,
- icon: const Icon(Icons.refresh_rounded),
- ),
- if (_tab == TasksTab.scheduled)
- Chip(
- avatar: const Icon(Icons.lock_outline_rounded, size: 16),
- label: Text(
- appText('计划任务只读', 'Scheduled tasks are read-only'),
- ),
- ),
- ],
- ),
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: Column(
- children: [
- SectionTabs(
- items: TasksTab.values.map((item) => item.label).toList(),
- value: _tab.label,
- onChanged: (value) {
- setState(() {
- _tab = TasksTab.values.firstWhere(
- (item) => item.label == value,
- );
- _selectedTaskId = null;
- });
- },
- ),
- const SizedBox(height: 16),
- SizedBox(
- height: 172,
- child: ListView.separated(
- scrollDirection: Axis.horizontal,
- itemCount: metrics.length,
- separatorBuilder: (_, _) => const SizedBox(width: 12),
- itemBuilder: (context, index) => SizedBox(
- width: 240,
- child: MetricCard(metric: metrics[index]),
- ),
- ),
- ),
- const SizedBox(height: 16),
- Expanded(
- child: SurfaceCard(
- padding: EdgeInsets.zero,
- borderRadius: 20,
- child: ClipRRect(
- borderRadius: BorderRadius.circular(20),
- child: Row(
- children: [
- SizedBox(
- width: 360,
- child: _TaskListPanel(
- tab: _tab,
- items: items,
- selectedTaskId: selected?.id,
- onSelectTask: (task) {
- setState(() {
- _selectedTaskId = task.id;
- });
- },
- ),
- ),
- Container(width: 1, color: palette.strokeSoft),
- Expanded(
- child: _TaskDetailPanel(
- controller: controller,
- tab: _tab,
- selected: selected,
- ),
- ),
- ],
- ),
- ),
- ),
- ),
- ],
- ),
- ),
- );
- },
- );
- }
-
- String get _tabKey => _tab.label;
-
- bool _matchesQuery(DerivedTaskItem item) {
- if (_query.isEmpty) {
- return true;
- }
- final haystack = [
- item.title,
- item.summary,
- item.owner,
- item.surface,
- item.sessionKey,
- ].join(' ').toLowerCase();
- return haystack.contains(_query);
- }
-
- DerivedTaskItem? _resolveSelectedTask(List items) {
- if (items.isEmpty) {
- return null;
- }
- for (final item in items) {
- if (item.id == _selectedTaskId) {
- return item;
- }
- }
- return items.first;
- }
-}
-
-class _TaskListPanel extends StatelessWidget {
- const _TaskListPanel({
- required this.tab,
- required this.items,
- required this.selectedTaskId,
- required this.onSelectTask,
- });
-
- final TasksTab tab;
- final List items;
- final String? selectedTaskId;
- final ValueChanged onSelectTask;
-
- @override
- Widget build(BuildContext context) {
- final palette = context.palette;
- final emptyLabel = tab == TasksTab.scheduled
- ? appText('当前没有计划任务。', 'No scheduled tasks right now.')
- : appText('当前筛选下没有任务。', 'No tasks match the current filter.');
-
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Padding(
- padding: const EdgeInsets.fromLTRB(14, 14, 14, 10),
- child: Row(
- children: [
- Text(
- appText('任务列表', 'Task list'),
- style: Theme.of(context).textTheme.titleSmall,
- ),
- const SizedBox(width: 8),
- Text(
- '${items.length}',
- style: Theme.of(
- context,
- ).textTheme.bodySmall?.copyWith(color: palette.textMuted),
- ),
- ],
- ),
- ),
- Container(height: 1, color: palette.strokeSoft),
- Expanded(
- child: items.isEmpty
- ? Center(
- child: Padding(
- padding: const EdgeInsets.all(20),
- child: Text(
- emptyLabel,
- textAlign: TextAlign.center,
- style: Theme.of(context).textTheme.bodyMedium?.copyWith(
- color: palette.textSecondary,
- ),
- ),
- ),
- )
- : ListView.separated(
- padding: const EdgeInsets.all(10),
- itemCount: items.length,
- separatorBuilder: (_, _) => const SizedBox(height: 8),
- itemBuilder: (context, index) {
- final task = items[index];
- final selected = task.id == selectedTaskId;
- return _TaskListTile(
- task: task,
- selected: selected,
- onTap: () => onSelectTask(task),
- );
- },
- ),
- ),
- ],
- );
- }
-}
-
-class _TaskListTile extends StatelessWidget {
- const _TaskListTile({
- required this.task,
- required this.selected,
- required this.onTap,
- });
-
- final DerivedTaskItem task;
- final bool selected;
- final VoidCallback onTap;
-
- @override
- Widget build(BuildContext context) {
- final palette = context.palette;
- return Material(
- color: selected ? palette.surfacePrimary : Colors.transparent,
- borderRadius: BorderRadius.circular(18),
- child: InkWell(
- key: ValueKey('tasks-list-item-${task.id}'),
- onTap: onTap,
- borderRadius: BorderRadius.circular(18),
- child: Container(
- padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(
- color: selected ? palette.surfaceSecondary : Colors.transparent,
- borderRadius: BorderRadius.circular(18),
- boxShadow: selected
- ? [
- BoxShadow(
- color: palette.shadow.withValues(alpha: 0.06),
- blurRadius: 8,
- offset: const Offset(0, 2),
- ),
- ]
- : const [],
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Expanded(
- child: Text(
- task.title,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- style: Theme.of(context).textTheme.titleSmall?.copyWith(
- fontWeight: FontWeight.w600,
- ),
- ),
- ),
- const SizedBox(width: 10),
- StatusBadge(status: _taskStatusInfo(task.status)),
- ],
- ),
- const SizedBox(height: 8),
- Text(
- task.summary,
- maxLines: 2,
- overflow: TextOverflow.ellipsis,
- style: Theme.of(context).textTheme.bodySmall?.copyWith(
- color: palette.textSecondary,
- height: 1.4,
- ),
- ),
- const SizedBox(height: 10),
- Wrap(
- spacing: 10,
- runSpacing: 6,
- children: [
- _InlineMeta(label: task.owner),
- _InlineMeta(label: task.startedAtLabel),
- _InlineMeta(label: task.surface),
- ],
- ),
- ],
- ),
- ),
- ),
- );
- }
-}
-
-class _TaskDetailPanel extends StatelessWidget {
- const _TaskDetailPanel({
- required this.controller,
- required this.tab,
- required this.selected,
- });
-
- final AppController controller;
- final TasksTab tab;
- final DerivedTaskItem? selected;
-
- @override
- Widget build(BuildContext context) {
- final palette = context.palette;
- if (selected == null) {
- return Center(
- child: Text(
- appText('选择左侧任务查看详情。', 'Select a task on the left.'),
- style: Theme.of(
- context,
- ).textTheme.bodyLarge?.copyWith(color: palette.textSecondary),
- ),
- );
- }
-
- return Padding(
- key: const Key('tasks-detail-panel'),
- padding: const EdgeInsets.all(18),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Wrap(
- spacing: 8,
- runSpacing: 8,
- crossAxisAlignment: WrapCrossAlignment.center,
- children: [
- Text(
- selected!.title,
- style: Theme.of(context).textTheme.headlineSmall?.copyWith(
- fontWeight: FontWeight.w600,
- ),
- ),
- StatusBadge(status: _taskStatusInfo(selected!.status)),
- ],
- ),
- const SizedBox(height: 8),
- Text(
- selected!.summary,
- style: Theme.of(context).textTheme.bodyMedium?.copyWith(
- color: palette.textSecondary,
- height: 1.5,
- ),
- ),
- const SizedBox(height: 18),
- Wrap(
- spacing: 12,
- runSpacing: 12,
- children: [
- _DetailStat(
- label: appText('任务来源', 'Surface'),
- value: selected!.surface,
- ),
- _DetailStat(
- label: appText('执行代理', 'Owner'),
- value: selected!.owner,
- ),
- _DetailStat(
- label: appText('开始时间', 'Started'),
- value: selected!.startedAtLabel,
- ),
- _DetailStat(
- label: appText('耗时', 'Duration'),
- value: selected!.durationLabel,
- ),
- ],
- ),
- const SizedBox(height: 18),
- Container(
- width: double.infinity,
- padding: const EdgeInsets.all(14),
- decoration: BoxDecoration(
- color: palette.surfaceSecondary,
- borderRadius: BorderRadius.circular(20),
- boxShadow: [
- BoxShadow(
- color: palette.shadow.withValues(alpha: 0.04),
- blurRadius: 8,
- offset: const Offset(0, 2),
- ),
- ],
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- appText('会话上下文', 'Conversation context'),
- style: Theme.of(context).textTheme.titleSmall,
- ),
- const SizedBox(height: 8),
- SelectableText(
- selected!.sessionKey,
- style: Theme.of(context).textTheme.bodyMedium,
- ),
- ],
- ),
- ),
- const Spacer(),
- Align(
- alignment: Alignment.centerRight,
- child: OutlinedButton.icon(
- onPressed: controller.refreshSessions,
- icon: const Icon(Icons.refresh_rounded),
- label: Text(appText('刷新', 'Refresh')),
- ),
- ),
- ],
- ),
- );
- }
-}
-
-class _DetailStat extends StatelessWidget {
- const _DetailStat({required this.label, required this.value});
-
- final String label;
- final String value;
-
- @override
- Widget build(BuildContext context) {
- final palette = context.palette;
- return Container(
- constraints: const BoxConstraints(minWidth: 160),
- padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
- decoration: BoxDecoration(
- color: palette.surfaceSecondary,
- borderRadius: BorderRadius.circular(18),
- boxShadow: [
- BoxShadow(
- color: palette.shadow.withValues(alpha: 0.04),
- blurRadius: 8,
- offset: const Offset(0, 2),
- ),
- ],
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- label,
- style: Theme.of(
- context,
- ).textTheme.bodySmall?.copyWith(color: palette.textMuted),
- ),
- const SizedBox(height: 4),
- Text(value, style: Theme.of(context).textTheme.labelLarge),
- ],
- ),
- );
- }
-}
-
-class _InlineMeta extends StatelessWidget {
- const _InlineMeta({required this.label});
-
- final String label;
-
- @override
- Widget build(BuildContext context) {
- return Text(
- label,
- style: Theme.of(
- context,
- ).textTheme.bodySmall?.copyWith(color: context.palette.textMuted),
- );
- }
-}
-
-StatusInfo _taskStatusInfo(String status) => switch (status) {
- 'running' ||
- 'Running' => StatusInfo(appText('运行中', 'Running'), StatusTone.accent),
- 'failed' ||
- 'Failed' => StatusInfo(appText('失败', 'Failed'), StatusTone.danger),
- 'queued' ||
- 'Queued' => StatusInfo(appText('排队中', 'Queued'), StatusTone.neutral),
- _ => StatusInfo(appText('可继续', 'Open'), StatusTone.success),
-};
diff --git a/lib/models/app_models.dart b/lib/models/app_models.dart
index 55afd0b8..d64740da 100644
--- a/lib/models/app_models.dart
+++ b/lib/models/app_models.dart
@@ -4,45 +4,18 @@ import '../i18n/app_language.dart';
enum WorkspaceDestination {
assistant,
- tasks,
- skills,
- nodes,
- agents,
- mcpServer,
- clawHub,
- secrets,
- aiGateway,
settings,
- account,
}
extension WorkspaceDestinationCopy on WorkspaceDestination {
String get label => switch (this) {
WorkspaceDestination.assistant => appText('助手', 'Assistant'),
- WorkspaceDestination.tasks => appText('任务', 'Tasks'),
- WorkspaceDestination.skills => appText('技能', 'Skills'),
- WorkspaceDestination.nodes => appText('节点', 'Nodes'),
- WorkspaceDestination.agents => appText('代理', 'Agents'),
- WorkspaceDestination.mcpServer => 'MCP Hub',
- WorkspaceDestination.clawHub => 'ClawHub',
- WorkspaceDestination.secrets => appText('密钥', 'Secrets'),
- WorkspaceDestination.aiGateway => 'LLM API',
WorkspaceDestination.settings => appText('设置', 'Settings'),
- WorkspaceDestination.account => appText('在线账户', 'Online Account'),
};
IconData get icon => switch (this) {
WorkspaceDestination.assistant => Icons.chat_bubble_outline_rounded,
- WorkspaceDestination.tasks => Icons.layers_rounded,
- WorkspaceDestination.skills => Icons.auto_awesome_rounded,
- WorkspaceDestination.nodes => Icons.developer_board_rounded,
- WorkspaceDestination.agents => Icons.hub_rounded,
- WorkspaceDestination.mcpServer => Icons.dns_rounded,
- WorkspaceDestination.clawHub => Icons.extension_rounded,
- WorkspaceDestination.secrets => Icons.key_rounded,
- WorkspaceDestination.aiGateway => Icons.smart_toy_rounded,
WorkspaceDestination.settings => Icons.tune_rounded,
- WorkspaceDestination.account => Icons.account_circle_rounded,
};
String get description => switch (this) {
@@ -50,45 +23,9 @@ extension WorkspaceDestinationCopy on WorkspaceDestination {
'AI 主入口,优先承接自然输入和高频工作发起。',
'Primary AI entry point for natural input and frequent task starts.',
),
- WorkspaceDestination.tasks => appText(
- '任务队列、运行态、失败项和调度历史的统一视图。',
- 'Unified view for queue, running, failed, and history.',
- ),
- WorkspaceDestination.skills => appText(
- '管理技能包与能力扩展,浏览和安装 ClawHub 技能。',
- 'Manage skill packages and extensions, browse and install from ClawHub.',
- ),
- WorkspaceDestination.nodes => appText(
- '管理边缘节点与实例,监控运行状态与负载。',
- 'Manage edge nodes and instances, monitor status and load.',
- ),
- WorkspaceDestination.agents => appText(
- '管理代理实例,配置行为与能力。',
- 'Manage agent instances, configure behaviors and capabilities.',
- ),
- WorkspaceDestination.mcpServer => appText(
- '管理 MCP Hub 连接与工具配置。',
- 'Manage MCP Hub connections and tool configurations.',
- ),
- WorkspaceDestination.clawHub => appText(
- '浏览和安装技能包、代理模板与连接器。',
- 'Browse and install skill packages, agent templates and connectors.',
- ),
- WorkspaceDestination.secrets => appText(
- '密钥与 Vault 配置统一收口到设置中心。',
- 'Secrets and Vault configuration now live in the Settings center.',
- ),
- WorkspaceDestination.aiGateway => appText(
- 'LLM API 配置统一收口到设置中心。',
- 'LLM API configuration now lives in the Settings center.',
- ),
WorkspaceDestination.settings => appText(
- '全局配置中心,只负责系统设置与诊断,不承担业务模块入口。',
- 'Global settings and diagnostics, separated from business modules.',
- ),
- WorkspaceDestination.account => appText(
- '在线账户、工作区切换、登录会话与 ACP Bridge Server 同步管理。',
- 'Online account, workspace switching, login sessions, and ACP Bridge Server sync.',
+ '桥接、账户与集成配置统一收口到设置中心。',
+ 'Bridge, account, and integration settings are consolidated in Settings.',
),
};
@@ -106,14 +43,6 @@ extension WorkspaceDestinationCopy on WorkspaceDestination {
}
enum AssistantFocusEntry {
- tasks,
- skills,
- nodes,
- agents,
- mcpServer,
- clawHub,
- secrets,
- aiGateway,
settings,
language,
theme,
@@ -121,69 +50,21 @@ enum AssistantFocusEntry {
extension AssistantFocusEntryCopy on AssistantFocusEntry {
String get label => switch (this) {
- AssistantFocusEntry.tasks => appText('任务', 'Tasks'),
- AssistantFocusEntry.skills => appText('技能', 'Skills'),
- AssistantFocusEntry.nodes => appText('节点', 'Nodes'),
- AssistantFocusEntry.agents => appText('代理', 'Agents'),
- AssistantFocusEntry.mcpServer => 'MCP Hub',
- AssistantFocusEntry.clawHub => 'ClawHub',
- AssistantFocusEntry.secrets => appText('密钥', 'Secrets'),
- AssistantFocusEntry.aiGateway => 'LLM API',
AssistantFocusEntry.settings => appText('设置', 'Settings'),
AssistantFocusEntry.language => appText('语言', 'Language'),
AssistantFocusEntry.theme => appText('主题/亮度', 'Theme / Brightness'),
};
IconData get icon => switch (this) {
- AssistantFocusEntry.tasks => Icons.layers_rounded,
- AssistantFocusEntry.skills => Icons.auto_awesome_rounded,
- AssistantFocusEntry.nodes => Icons.developer_board_rounded,
- AssistantFocusEntry.agents => Icons.hub_rounded,
- AssistantFocusEntry.mcpServer => Icons.dns_rounded,
- AssistantFocusEntry.clawHub => Icons.extension_rounded,
- AssistantFocusEntry.secrets => Icons.key_rounded,
- AssistantFocusEntry.aiGateway => Icons.smart_toy_rounded,
AssistantFocusEntry.settings => Icons.tune_rounded,
AssistantFocusEntry.language => Icons.translate_rounded,
AssistantFocusEntry.theme => Icons.brightness_6_rounded,
};
String get description => switch (this) {
- AssistantFocusEntry.tasks => appText(
- '任务队列、运行态、失败项和调度历史的统一视图。',
- 'Unified view for queue, running, failed, and history.',
- ),
- AssistantFocusEntry.skills => appText(
- '管理技能包与能力扩展,浏览和安装 ClawHub 技能。',
- 'Manage skill packages and extensions, browse and install from ClawHub.',
- ),
- AssistantFocusEntry.nodes => appText(
- '管理边缘节点与实例,监控运行状态与负载。',
- 'Manage edge nodes and instances, monitor status and load.',
- ),
- AssistantFocusEntry.agents => appText(
- '管理代理实例,配置行为与能力。',
- 'Manage agent instances, configure behaviors and capabilities.',
- ),
- AssistantFocusEntry.mcpServer => appText(
- '管理 MCP Hub 连接与工具配置。',
- 'Manage MCP Hub connections and tool configurations.',
- ),
- AssistantFocusEntry.clawHub => appText(
- '浏览和安装技能包、代理模板与连接器。',
- 'Browse and install skill packages, agent templates and connectors.',
- ),
- AssistantFocusEntry.secrets => appText(
- '密钥与 Vault 配置统一收口到设置中心。',
- 'Secrets and Vault configuration now live in the Settings center.',
- ),
- AssistantFocusEntry.aiGateway => appText(
- 'LLM API 配置统一收口到设置中心。',
- 'LLM API configuration now lives in the Settings center.',
- ),
AssistantFocusEntry.settings => appText(
- '全局配置中心,只负责系统设置与诊断,不承担业务模块入口。',
- 'Global settings and diagnostics, separated from business modules.',
+ '打开设置中心,管理 Bridge、账户与集成配置。',
+ 'Open Settings to manage bridge, account, and integration configuration.',
),
AssistantFocusEntry.language => appText(
'快速切换中英文界面语言,无需先进入设置页。',
@@ -196,14 +77,6 @@ extension AssistantFocusEntryCopy on AssistantFocusEntry {
};
WorkspaceDestination? get destination => switch (this) {
- AssistantFocusEntry.tasks => WorkspaceDestination.tasks,
- AssistantFocusEntry.skills => WorkspaceDestination.skills,
- AssistantFocusEntry.nodes => WorkspaceDestination.nodes,
- AssistantFocusEntry.agents => WorkspaceDestination.agents,
- AssistantFocusEntry.mcpServer => WorkspaceDestination.mcpServer,
- AssistantFocusEntry.clawHub => WorkspaceDestination.clawHub,
- AssistantFocusEntry.secrets => WorkspaceDestination.secrets,
- AssistantFocusEntry.aiGateway => WorkspaceDestination.aiGateway,
AssistantFocusEntry.settings => WorkspaceDestination.settings,
AssistantFocusEntry.language => null,
AssistantFocusEntry.theme => null,
@@ -228,17 +101,8 @@ extension AssistantFocusEntryCopy on AssistantFocusEntry {
static AssistantFocusEntry fromDestination(WorkspaceDestination destination) {
return switch (destination) {
- WorkspaceDestination.tasks => AssistantFocusEntry.tasks,
- WorkspaceDestination.skills => AssistantFocusEntry.skills,
- WorkspaceDestination.nodes => AssistantFocusEntry.nodes,
- WorkspaceDestination.agents => AssistantFocusEntry.agents,
- WorkspaceDestination.mcpServer => AssistantFocusEntry.mcpServer,
- WorkspaceDestination.clawHub => AssistantFocusEntry.clawHub,
- WorkspaceDestination.secrets => AssistantFocusEntry.secrets,
- WorkspaceDestination.aiGateway => AssistantFocusEntry.aiGateway,
WorkspaceDestination.settings => AssistantFocusEntry.settings,
- WorkspaceDestination.assistant ||
- WorkspaceDestination.account => throw ArgumentError.value(
+ WorkspaceDestination.assistant => throw ArgumentError.value(
destination,
'destination',
'Focused assistant entries only support pinnable workspace targets.',
@@ -252,14 +116,6 @@ const List kAssistantNavigationDestinationDefaults =
const List kAssistantNavigationDestinationCandidates =
[
- AssistantFocusEntry.tasks,
- AssistantFocusEntry.skills,
- AssistantFocusEntry.nodes,
- AssistantFocusEntry.agents,
- AssistantFocusEntry.mcpServer,
- AssistantFocusEntry.clawHub,
- AssistantFocusEntry.secrets,
- AssistantFocusEntry.aiGateway,
AssistantFocusEntry.settings,
AssistantFocusEntry.language,
AssistantFocusEntry.theme,
@@ -300,84 +156,15 @@ extension AssistantModeCopy on AssistantMode {
};
}
-enum TasksTab { queue, running, history, failed, scheduled }
-
-extension TasksTabCopy on TasksTab {
- String get label => switch (this) {
- TasksTab.queue => appText('队列', 'Queue'),
- TasksTab.running => appText('运行中', 'Running'),
- TasksTab.history => appText('历史', 'History'),
- TasksTab.failed => appText('失败', 'Failed'),
- TasksTab.scheduled => appText('计划中', 'Scheduled'),
- };
-}
-
-enum ModulesTab { gateway, nodes, agents, skills, clawHub, connectors }
-
-extension ModulesTabCopy on ModulesTab {
- String get label => switch (this) {
- ModulesTab.gateway => appText('网关', 'Gateway'),
- ModulesTab.nodes => appText('节点', 'Nodes'),
- ModulesTab.agents => appText('代理', 'Agents'),
- ModulesTab.skills => appText('技能', 'Skills'),
- ModulesTab.clawHub => 'ClawHub',
- ModulesTab.connectors => appText('连接器', 'Connectors'),
- };
-}
-
-enum SecretsTab { vault, localStore, providers, audit }
-
-extension SecretsTabCopy on SecretsTab {
- String get label => switch (this) {
- SecretsTab.vault => 'Vault',
- SecretsTab.localStore => appText('本地存储', 'Local Store'),
- SecretsTab.providers => appText('提供方', 'Providers'),
- SecretsTab.audit => appText('审计', 'Audit'),
- };
-}
-
-enum SettingsTab {
- general,
- workspace,
- gateway,
- agents,
- appearance,
- diagnostics,
- experimental,
- about,
-}
+enum SettingsTab { gateway }
extension SettingsTabCopy on SettingsTab {
String get label => switch (this) {
- SettingsTab.general => appText('通用', 'General'),
- SettingsTab.workspace => appText('工作区', 'Workspace'),
SettingsTab.gateway => appText('集成', 'Integrations'),
- SettingsTab.agents => appText('多 Agent', 'Multi-Agent'),
- SettingsTab.appearance => appText('外观', 'Appearance'),
- SettingsTab.diagnostics => appText('诊断', 'Diagnostics'),
- SettingsTab.experimental => appText('实验特性', 'Experimental'),
- SettingsTab.about => appText('关于', 'About'),
};
}
-enum AiGatewayTab { models, agents, endpoints, tools }
-
-extension AiGatewayTabCopy on AiGatewayTab {
- String get label => switch (this) {
- AiGatewayTab.models => appText('模型', 'Models'),
- AiGatewayTab.agents => appText('代理', 'Agents'),
- AiGatewayTab.endpoints => appText('端点', 'Endpoints'),
- AiGatewayTab.tools => appText('工具', 'Tools'),
- };
-}
-
-enum SettingsDetailPage {
- gatewayConnection,
- aiGatewayIntegration,
- vaultProvider,
- externalAgents,
- diagnosticsAdvanced,
-}
+enum SettingsDetailPage { gatewayConnection }
extension SettingsDetailPageCopy on SettingsDetailPage {
String get label => switch (this) {
@@ -385,30 +172,10 @@ extension SettingsDetailPageCopy on SettingsDetailPage {
'Gateway 连接参数',
'Gateway Connection',
),
- SettingsDetailPage.aiGatewayIntegration => appText(
- 'LLM 接入点',
- 'LLM Endpoints',
- ),
- SettingsDetailPage.vaultProvider => appText(
- 'Vault 提供方参数',
- 'Vault Provider',
- ),
- SettingsDetailPage.externalAgents => appText(
- '多 Agent 协作参数',
- 'External Agents',
- ),
- SettingsDetailPage.diagnosticsAdvanced => appText(
- '高级诊断参数',
- 'Advanced Diagnostics',
- ),
};
SettingsTab get tab => switch (this) {
- SettingsDetailPage.gatewayConnection ||
- SettingsDetailPage.aiGatewayIntegration ||
- SettingsDetailPage.vaultProvider => SettingsTab.gateway,
- SettingsDetailPage.externalAgents => SettingsTab.agents,
- SettingsDetailPage.diagnosticsAdvanced => SettingsTab.diagnostics,
+ SettingsDetailPage.gatewayConnection => SettingsTab.gateway,
};
}
@@ -418,9 +185,6 @@ class SettingsNavigationContext {
required this.rootLabel,
required this.destination,
this.sectionLabel,
- this.modulesTab,
- this.secretsTab,
- this.aiGatewayTab,
this.settingsTab,
this.gatewayProfileIndex,
this.prefersGatewaySetupCode,
@@ -429,9 +193,6 @@ class SettingsNavigationContext {
final String rootLabel;
final WorkspaceDestination destination;
final String? sectionLabel;
- final ModulesTab? modulesTab;
- final SecretsTab? secretsTab;
- final AiGatewayTab? aiGatewayTab;
final SettingsTab? settingsTab;
final int? gatewayProfileIndex;
final bool? prefersGatewaySetupCode;
diff --git a/lib/runtime/runtime_controllers_entities.dart b/lib/runtime/runtime_controllers_entities.dart
index 6639b3e4..7817eca8 100644
--- a/lib/runtime/runtime_controllers_entities.dart
+++ b/lib/runtime/runtime_controllers_entities.dart
@@ -11,40 +11,6 @@ import 'runtime_controllers_settings.dart';
import 'runtime_controllers_gateway.dart';
import 'runtime_controllers_derived_tasks.dart';
-class InstancesController extends ChangeNotifier {
- InstancesController(this.runtimeInternal);
-
- final GatewayRuntime runtimeInternal;
-
- List itemsInternal = const [];
- bool loadingInternal = false;
- String? errorInternal;
-
- List get items => itemsInternal;
- bool get loading => loadingInternal;
- String? get error => errorInternal;
-
- Future refresh() async {
- if (!runtimeInternal.isConnected) {
- itemsInternal = const [];
- errorInternal = null;
- notifyListeners();
- return;
- }
- loadingInternal = true;
- errorInternal = null;
- notifyListeners();
- try {
- itemsInternal = await runtimeInternal.listInstances();
- } catch (error) {
- errorInternal = error.toString();
- } finally {
- loadingInternal = false;
- notifyListeners();
- }
- }
-}
-
class SkillsController extends ChangeNotifier {
SkillsController(this.runtimeInternal);
@@ -79,41 +45,6 @@ class SkillsController extends ChangeNotifier {
}
}
-class ConnectorsController extends ChangeNotifier {
- ConnectorsController(this.runtimeInternal);
-
- final GatewayRuntime runtimeInternal;
-
- List itemsInternal =
- const [];
- bool loadingInternal = false;
- String? errorInternal;
-
- List get items => itemsInternal;
- bool get loading => loadingInternal;
- String? get error => errorInternal;
-
- Future refresh() async {
- if (!runtimeInternal.isConnected) {
- itemsInternal = const [];
- errorInternal = null;
- notifyListeners();
- return;
- }
- loadingInternal = true;
- errorInternal = null;
- notifyListeners();
- try {
- itemsInternal = await runtimeInternal.listConnectors();
- } catch (error) {
- errorInternal = error.toString();
- } finally {
- loadingInternal = false;
- notifyListeners();
- }
- }
-}
-
class ModelsController extends ChangeNotifier {
ModelsController(this.runtimeInternal, this.settingsControllerInternal);
diff --git a/lib/widgets/assistant_focus_panel_core.dart b/lib/widgets/assistant_focus_panel_core.dart
index 53eb0984..524ac4ac 100644
--- a/lib/widgets/assistant_focus_panel_core.dart
+++ b/lib/widgets/assistant_focus_panel_core.dart
@@ -318,30 +318,6 @@ class AssistantFocusPreviewInternal extends StatelessWidget {
@override
Widget build(BuildContext context) {
return switch (destination) {
- AssistantFocusEntry.tasks => TasksFocusPreviewInternal(
- controller: controller,
- ),
- AssistantFocusEntry.skills => SkillsFocusPreviewInternal(
- controller: controller,
- ),
- AssistantFocusEntry.nodes => NodesFocusPreviewInternal(
- controller: controller,
- ),
- AssistantFocusEntry.agents => AgentsFocusPreviewInternal(
- controller: controller,
- ),
- AssistantFocusEntry.mcpServer => McpFocusPreviewInternal(
- controller: controller,
- ),
- AssistantFocusEntry.clawHub => ClawHubFocusPreviewInternal(
- controller: controller,
- ),
- AssistantFocusEntry.secrets => SecretsFocusPreviewInternal(
- controller: controller,
- ),
- AssistantFocusEntry.aiGateway => AiGatewayFocusPreviewInternal(
- controller: controller,
- ),
AssistantFocusEntry.settings => SettingsFocusPreviewInternal(
controller: controller,
),
diff --git a/lib/widgets/assistant_focus_panel_previews.dart b/lib/widgets/assistant_focus_panel_previews.dart
index 03db1d42..d865980f 100644
--- a/lib/widgets/assistant_focus_panel_previews.dart
+++ b/lib/widgets/assistant_focus_panel_previews.dart
@@ -1,364 +1,17 @@
// ignore_for_file: unused_import, unnecessary_import
import 'package:flutter/material.dart';
+
import '../app/app_controller.dart';
import '../i18n/app_language.dart';
import '../models/app_models.dart';
import '../runtime/runtime_models.dart';
import '../theme/app_palette.dart';
+import 'assistant_focus_panel_core.dart';
+import 'assistant_focus_panel_support.dart';
import 'chrome_quick_action_buttons.dart';
import 'settings_focus_quick_actions.dart';
import 'surface_card.dart';
-import 'assistant_focus_panel_core.dart';
-import 'assistant_focus_panel_support.dart';
-
-class TasksFocusPreviewInternal extends StatelessWidget {
- const TasksFocusPreviewInternal({super.key, required this.controller});
-
- final AssistantFocusControllerInternal controller;
-
- @override
- Widget build(BuildContext context) {
- final typedController = castAssistantFocusControllerInternal(controller);
- final items = [
- ...typedController.tasksController.running.take(2),
- ...typedController.tasksController.queue.take(2),
- ...typedController.tasksController.history.take(1),
- ].take(4).toList(growable: false);
-
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Wrap(
- spacing: 8,
- runSpacing: 8,
- children: [
- FocusPillInternal(
- label: appText(
- '运行中 ${typedController.tasksController.running.length}',
- 'Running ${typedController.tasksController.running.length}',
- ),
- ),
- FocusPillInternal(
- label: appText(
- '队列 ${typedController.tasksController.queue.length}',
- 'Queue ${typedController.tasksController.queue.length}',
- ),
- ),
- FocusPillInternal(
- label: appText(
- '计划 ${typedController.tasksController.scheduled.length}',
- 'Scheduled ${typedController.tasksController.scheduled.length}',
- ),
- ),
- ],
- ),
- const SizedBox(height: 12),
- if (items.isEmpty)
- PreviewEmptyStateInternal(
- message:
- typedController.connection.status ==
- RuntimeConnectionStatus.connected
- ? appText('当前没有任务摘要。', 'No task summary yet.')
- : appText(
- '恢复 xworkmate-bridge 连接后这里会显示任务摘要。',
- 'Task summaries appear here after xworkmate-bridge reconnects.',
- ),
- )
- else
- ...items.map(
- (item) => Padding(
- padding: const EdgeInsets.only(bottom: 8),
- child: FocusListTileInternal(
- title: item.title,
- subtitle: item.summary,
- trailing: item.status,
- ),
- ),
- ),
- ],
- );
- }
-}
-
-class SkillsFocusPreviewInternal extends StatelessWidget {
- const SkillsFocusPreviewInternal({super.key, required this.controller});
-
- final AssistantFocusControllerInternal controller;
-
- @override
- Widget build(BuildContext context) {
- final typedController = castAssistantFocusControllerInternal(controller);
- final items = typedController.skills.take(4).toList(growable: false);
- if (items.isEmpty) {
- final bridgeEndpointMissing =
- typedController.resolveExternalAcpEndpointForTargetInternal(
- AssistantExecutionTarget.gateway,
- ) ==
- null;
- return PreviewEmptyStateInternal(
- message: bridgeEndpointMissing
- ? appText(
- 'Bridge Server 当前不可用。恢复后这里会显示线程技能摘要。',
- 'The bridge server is currently unavailable. Thread skill summaries will appear here after it recovers.',
- )
- : typedController.connection.status == RuntimeConnectionStatus.connected
- ? appText(
- '当前代理没有已加载技能。',
- 'No skills are loaded for the active agent.',
- )
- : appText(
- '恢复 xworkmate-bridge 连接后可查看技能摘要。',
- 'Skill summaries are available again after xworkmate-bridge reconnects.',
- ),
- );
- }
- return Column(
- children: items
- .map(
- (skill) => Padding(
- padding: const EdgeInsets.only(bottom: 8),
- child: FocusListTileInternal(
- title: skill.name,
- subtitle: skill.description,
- trailing: skill.disabled
- ? appText('已禁用', 'Disabled')
- : appText('已启用', 'Enabled'),
- ),
- ),
- )
- .toList(growable: false),
- );
- }
-}
-
-class NodesFocusPreviewInternal extends StatelessWidget {
- const NodesFocusPreviewInternal({super.key, required this.controller});
-
- final AssistantFocusControllerInternal controller;
-
- @override
- Widget build(BuildContext context) {
- final typedController = castAssistantFocusControllerInternal(controller);
- final items = typedController.instances.take(4).toList(growable: false);
- if (items.isEmpty) {
- return PreviewEmptyStateInternal(
- message: appText('当前没有节点可显示。', 'No nodes are available right now.'),
- );
- }
- return Column(
- children: items
- .map(
- (instance) => Padding(
- padding: const EdgeInsets.only(bottom: 8),
- child: FocusListTileInternal(
- title: instance.host?.trim().isNotEmpty == true
- ? instance.host!
- : instance.id,
- subtitle:
- [instance.platform, instance.deviceFamily, instance.ip]
- .whereType()
- .where((item) => item.trim().isNotEmpty)
- .join(' · '),
- trailing: instance.mode ?? appText('未知', 'Unknown'),
- ),
- ),
- )
- .toList(growable: false),
- );
- }
-}
-
-class AgentsFocusPreviewInternal extends StatelessWidget {
- const AgentsFocusPreviewInternal({super.key, required this.controller});
-
- final AssistantFocusControllerInternal controller;
-
- @override
- Widget build(BuildContext context) {
- final typedController = castAssistantFocusControllerInternal(controller);
- final items = typedController.agents.take(5).toList(growable: false);
- if (items.isEmpty) {
- return PreviewEmptyStateInternal(
- message: appText('当前没有代理摘要。', 'No agents are available right now.'),
- );
- }
- return Column(
- children: items
- .map(
- (agent) => Padding(
- padding: const EdgeInsets.only(bottom: 8),
- child: FocusListTileInternal(
- title: '${agent.emoji} ${agent.name}',
- subtitle: agent.id,
- trailing: agent.name == typedController.activeAgentName
- ? appText('当前', 'Active')
- : agent.theme,
- ),
- ),
- )
- .toList(growable: false),
- );
- }
-}
-
-class McpFocusPreviewInternal extends StatelessWidget {
- const McpFocusPreviewInternal({super.key, required this.controller});
-
- final AssistantFocusControllerInternal controller;
-
- @override
- Widget build(BuildContext context) {
- final typedController = castAssistantFocusControllerInternal(controller);
- final items = typedController.connectors.take(4).toList(growable: false);
- if (items.isEmpty) {
- return PreviewEmptyStateInternal(
- message: appText(
- '当前没有 MCP 连接器。恢复 xworkmate-bridge 连接后这里会显示工具摘要。',
- 'No MCP connectors yet. Tool summaries appear here after xworkmate-bridge reconnects.',
- ),
- );
- }
- return Column(
- children: items
- .map(
- (connector) => Padding(
- padding: const EdgeInsets.only(bottom: 8),
- child: FocusListTileInternal(
- title: connector.label,
- subtitle: connector.detailLabel,
- trailing: connector.status,
- ),
- ),
- )
- .toList(growable: false),
- );
- }
-}
-
-class ClawHubFocusPreviewInternal extends StatelessWidget {
- const ClawHubFocusPreviewInternal({super.key, required this.controller});
-
- final AssistantFocusControllerInternal controller;
-
- @override
- Widget build(BuildContext context) {
- final typedController = castAssistantFocusControllerInternal(controller);
- final skillCount = typedController.skills.length;
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Wrap(
- spacing: 8,
- runSpacing: 8,
- children: [
- FocusPillInternal(
- label: appText('已加载技能 $skillCount', 'Loaded skills $skillCount'),
- ),
- FocusPillInternal(
- label: appText(
- '关注入口 ${typedController.assistantNavigationDestinations.length}',
- 'Pinned ${typedController.assistantNavigationDestinations.length}',
- ),
- ),
- ],
- ),
- const SizedBox(height: 12),
- PreviewEmptyStateInternal(
- message: appText(
- 'ClawHub 适合放在侧板做快速搜索或安装入口;需要完整终端交互时,再打开全页。',
- 'Use ClawHub in the side panel for quick access. Open the full page when you need the terminal workflow.',
- ),
- ),
- ],
- );
- }
-}
-
-class SecretsFocusPreviewInternal extends StatelessWidget {
- const SecretsFocusPreviewInternal({super.key, required this.controller});
-
- final AssistantFocusControllerInternal controller;
-
- @override
- Widget build(BuildContext context) {
- final typedController = castAssistantFocusControllerInternal(controller);
- final items = typedController.secretReferences
- .take(4)
- .toList(growable: false);
- if (items.isEmpty) {
- return PreviewEmptyStateInternal(
- message: appText(
- '当前没有密钥引用摘要。',
- 'No masked secret references are available yet.',
- ),
- );
- }
- return Column(
- children: items
- .map(
- (secret) => Padding(
- padding: const EdgeInsets.only(bottom: 8),
- child: FocusListTileInternal(
- title: secret.name,
- subtitle: '${secret.provider} · ${secret.module}',
- trailing: secret.status,
- ),
- ),
- )
- .toList(growable: false),
- );
- }
-}
-
-class AiGatewayFocusPreviewInternal extends StatelessWidget {
- const AiGatewayFocusPreviewInternal({super.key, required this.controller});
-
- final AssistantFocusControllerInternal controller;
-
- @override
- Widget build(BuildContext context) {
- final typedController = castAssistantFocusControllerInternal(controller);
- final items = typedController.models.take(4).toList(growable: false);
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Wrap(
- spacing: 8,
- runSpacing: 8,
- children: [
- FocusPillInternal(label: typedController.connection.status.label),
- FocusPillInternal(
- label: appText(
- '模型 ${typedController.models.length}',
- 'Models ${typedController.models.length}',
- ),
- ),
- ],
- ),
- const SizedBox(height: 12),
- if (items.isEmpty)
- PreviewEmptyStateInternal(
- message: appText(
- '当前没有 LLM API 模型摘要。',
- 'No LLM API model summary is available yet.',
- ),
- )
- else
- ...items.map(
- (model) => Padding(
- padding: const EdgeInsets.only(bottom: 8),
- child: FocusListTileInternal(
- title: model.name,
- subtitle: model.provider,
- trailing: model.id,
- ),
- ),
- ),
- ],
- );
- }
-}
class SettingsFocusPreviewInternal extends StatelessWidget {
const SettingsFocusPreviewInternal({super.key, required this.controller});
@@ -524,42 +177,41 @@ class FocusListTileInternal extends StatelessWidget {
Widget build(BuildContext context) {
final palette = context.palette;
final theme = Theme.of(context);
-
return Container(
- width: double.infinity,
padding: const EdgeInsets.fromLTRB(12, 12, 12, 12),
decoration: BoxDecoration(
color: palette.surfaceSecondary,
- borderRadius: BorderRadius.circular(14),
+ borderRadius: BorderRadius.circular(12),
border: Border.all(color: palette.strokeSoft),
),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
+ child: Row(
children: [
- Text(
- title,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- style: theme.textTheme.titleSmall?.copyWith(
- fontWeight: FontWeight.w600,
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ title,
+ style: theme.textTheme.bodyMedium?.copyWith(
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ const SizedBox(height: 4),
+ Text(
+ subtitle,
+ style: theme.textTheme.bodySmall?.copyWith(
+ color: palette.textSecondary,
+ height: 1.35,
+ ),
+ ),
+ ],
),
),
- const SizedBox(height: 4),
- Text(
- subtitle,
- maxLines: 2,
- overflow: TextOverflow.ellipsis,
- style: theme.textTheme.bodySmall?.copyWith(
- color: palette.textSecondary,
- height: 1.3,
- ),
- ),
- const SizedBox(height: 8),
+ const SizedBox(width: 12),
Text(
trailing,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
style: theme.textTheme.labelLarge?.copyWith(
+ fontWeight: FontWeight.w700,
color: palette.textPrimary,
),
),
@@ -568,59 +220,3 @@ class FocusListTileInternal extends StatelessWidget {
);
}
}
-
-class FocusPillInternal extends StatelessWidget {
- const FocusPillInternal({super.key, required this.label});
-
- final String label;
-
- @override
- Widget build(BuildContext context) {
- final palette = context.palette;
- final theme = Theme.of(context);
-
- return Container(
- padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
- decoration: BoxDecoration(
- color: palette.surfaceSecondary,
- borderRadius: BorderRadius.circular(999),
- border: Border.all(color: palette.strokeSoft),
- ),
- child: Text(
- label,
- style: theme.textTheme.labelLarge?.copyWith(
- color: palette.textSecondary,
- ),
- ),
- );
- }
-}
-
-class PreviewEmptyStateInternal extends StatelessWidget {
- const PreviewEmptyStateInternal({super.key, required this.message});
-
- final String message;
-
- @override
- Widget build(BuildContext context) {
- final palette = context.palette;
- final theme = Theme.of(context);
-
- return Container(
- width: double.infinity,
- padding: const EdgeInsets.all(14),
- decoration: BoxDecoration(
- color: palette.surfaceSecondary,
- borderRadius: BorderRadius.circular(14),
- border: Border.all(color: palette.strokeSoft),
- ),
- child: Text(
- message,
- style: theme.textTheme.bodySmall?.copyWith(
- color: palette.textSecondary,
- height: 1.35,
- ),
- ),
- );
- }
-}
diff --git a/test/features/app/app_shell_surface_test.dart b/test/features/app/app_shell_surface_test.dart
new file mode 100644
index 00000000..f4771189
--- /dev/null
+++ b/test/features/app/app_shell_surface_test.dart
@@ -0,0 +1,63 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:xworkmate/app/app_controller.dart';
+import 'package:xworkmate/app/app_shell_desktop.dart';
+import 'package:xworkmate/theme/app_theme.dart';
+
+void main() {
+ group('AppShell surface cleanup', () {
+ testWidgets('mobile shell only exposes assistant and settings tabs', (
+ tester,
+ ) async {
+ tester.view.devicePixelRatio = 1;
+ tester.view.physicalSize = const Size(430, 932);
+ addTearDown(tester.view.resetPhysicalSize);
+ addTearDown(tester.view.resetDevicePixelRatio);
+
+ final controller = AppController();
+ addTearDown(controller.dispose);
+
+ await tester.pumpWidget(
+ MaterialApp(
+ theme: AppTheme.light().copyWith(platform: TargetPlatform.android),
+ home: AppShell(controller: controller),
+ ),
+ );
+ await tester.pumpAndSettle();
+
+ expect(find.text('助手'), findsOneWidget);
+ expect(find.text('设置'), findsOneWidget);
+ expect(find.text('任务'), findsNothing);
+ expect(find.text('工作区'), findsNothing);
+ expect(find.text('密钥'), findsNothing);
+ });
+
+ testWidgets('desktop shell switches between assistant and settings', (
+ tester,
+ ) async {
+ tester.view.devicePixelRatio = 1;
+ tester.view.physicalSize = const Size(1440, 960);
+ addTearDown(tester.view.resetPhysicalSize);
+ addTearDown(tester.view.resetDevicePixelRatio);
+
+ final controller = AppController();
+ addTearDown(controller.dispose);
+
+ await tester.pumpWidget(
+ MaterialApp(
+ theme: AppTheme.light().copyWith(platform: TargetPlatform.macOS),
+ home: AppShell(controller: controller),
+ ),
+ );
+ await tester.pumpAndSettle();
+
+ expect(find.byKey(const Key('assistant-conversation-shell')), findsOneWidget);
+ expect(find.byKey(const Key('settings-account-panel-card')), findsNothing);
+
+ controller.openSettings();
+ await tester.pumpAndSettle();
+
+ expect(find.byKey(const Key('settings-account-panel-card')), findsOneWidget);
+ });
+ });
+}
diff --git a/test/golden/goldens/assistant_lower_pane.png b/test/golden/goldens/assistant_lower_pane.png
index 630fb6d2..234954a9 100644
Binary files a/test/golden/goldens/assistant_lower_pane.png and b/test/golden/goldens/assistant_lower_pane.png differ
diff --git a/test/runtime/ui_feature_manifest_mobile_surface_test.dart b/test/runtime/ui_feature_manifest_mobile_surface_test.dart
new file mode 100644
index 00000000..1bee9c54
--- /dev/null
+++ b/test/runtime/ui_feature_manifest_mobile_surface_test.dart
@@ -0,0 +1,26 @@
+import 'dart:io';
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:xworkmate/app/ui_feature_manifest.dart';
+import 'package:xworkmate/models/app_models.dart';
+
+void main() {
+ group('Mobile feature manifest cleanup', () {
+ test('repo config only exposes assistant and settings on mobile', () {
+ final raw = File('config/feature_flags.yaml').readAsStringSync();
+ final manifest = UiFeatureManifest.fromYamlString(raw);
+ final mobile = manifest.forPlatform(
+ UiFeaturePlatform.mobile,
+ buildMode: UiFeatureBuildMode.debug,
+ );
+
+ expect(
+ mobile.allowedDestinations,
+ {
+ WorkspaceDestination.assistant,
+ WorkspaceDestination.settings,
+ },
+ );
+ });
+ });
+}