diff --git a/CHANGELOG.md b/CHANGELOG.md index e3756ad8..6a5b48ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,14 +17,14 @@ ### Highlights - 本地配置、Gateway 凭证和 Assistant 任务会话改为以 secure storage 管理的密钥做加密持久化,重启和覆盖安装后不再丢失。 -- `仅 AI Gateway` 线程补齐本地技能自动发现和当前线程可选技能列表恢复,线程状态与模型选择继续保持隔离。 +- `单机智能体` 线程补齐本地技能自动发现和当前线程可选技能列表恢复,线程状态与模型选择继续保持隔离。 - Flutter Web assistant shell、Web Chrome 会话持久化和移动端安全控件一起补齐,多端可用性明显提升。 - Assistant composer 高度自适应、执行目标切换即时刷新、侧栏默认宽度等桌面交互问题已收敛。 - Windows / Linux parity、macOS DMG 打包和多平台构建发布流程持续补强。 ### Current Delivery Scope - 已交付:加密后的本地 settings snapshot、assistant threads 和 sealed backup 恢复链路。 -- 已交付:Gateway-only 线程技能自动发现、线程状态清理和重启恢复。 +- 已交付:Single Agent 线程技能自动发现、线程状态清理和重启恢复。 - 已交付:Flutter Web assistant shell、Web 持久化修复、移动端安全壳控件和桌面布局微调。 - 已交付:Windows / Linux parity 修复、多平台 build and release workflow、macOS 安装与分发产物。 @@ -45,13 +45,13 @@ ### Highlights - Assistant 任务线程升级为持续会话:支持流式回复、继续追问、线程归档和重启恢复。 -- 任务列表按 `仅 AI Gateway / 本地 OpenClaw Gateway / 远程 OpenClaw Gateway` 分组,保持极简列表布局。 +- 任务列表按 `单机智能体 / 本地 OpenClaw Gateway / 远程 OpenClaw Gateway` 分组,保持极简列表布局。 - Multi-Agent 协作正式升级为 `Architect / Engineer / Tester`,并可选 `ARIS` 作为最强协作框架。 - ARIS bundle 作为只读资产内嵌进 App,`skills/` 直接复用 upstream,`llm-chat` 与 `claude-review` 切到 Go bridge。 - `Ollama Cloud` 文案与默认地址统一,打包后的 `.app` 会随同分发 `xworkmate-aris-bridge` helper。 ### Current Delivery Scope -- 已交付:AI Gateway-only streaming threads、OpenClaw 本地/远程任务线程、手动归档与持续会话恢复。 +- 已交付:Single Agent streaming threads、OpenClaw 本地/远程任务线程、手动归档与持续会话恢复。 - 已交付:Multi-Agent managed runtime、ARIS framework preset、本地优先 Ollama 回退、Go bridge runtime 和打包分发。 - 已交付:Settings / Assistant 里的 ARIS 轻量状态展示、任务分组、Ollama Cloud 设置迁移。 - 保持 truth-first:Scheduled Tasks 仍是 `cron.list` 只读视图;Memory 仍是 `memory/sync` 同步能力,不宣传 CRUD。 diff --git a/README.md b/README.md index 1e5520ed..cd089e17 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ XWorkmate is an AI workspace shell built with Flutter. ## v0.5 Highlights - Assistant 任务线程支持流式回复、继续追问和手动归档,不再是一问一答即结束。 -- 任务列表按 `仅 AI Gateway / 本地 OpenClaw Gateway / 远程 OpenClaw Gateway` 分组显示。 +- 任务列表按 `单机智能体 / 本地 OpenClaw Gateway / 远程 OpenClaw Gateway` 分组显示。 - Multi-Agent 协作支持 `Architect / Engineer / Tester`,并可切换 `Native / ARIS` 框架。 - ARIS `skills/` 直接随 App 内置,`llm-chat` 与 `claude-review` 统一由 Go bridge 驱动。 - `Ollama Cloud` 设置、ARIS helper bundling、macOS DMG 打包与安装链路已打通。 @@ -14,12 +14,12 @@ XWorkmate is an AI workspace shell built with Flutter. ## Current Scope ### Shipping in v0.5 -- AI Gateway-only streaming assistant threads +- Single Agent streaming assistant threads - OpenClaw local/remote task threads with persistent context - Multi-Agent orchestration with optional ARIS preset - Bundled ARIS skills, Go bridge helper, `llm-chat` reviewer, and `claude-review` - Ollama Cloud settings, task grouping, and macOS packaged delivery -- Flutter Web shell with `Assistant` + `Settings` only, supporting `Direct AI Gateway` and `Relay OpenClaw Gateway` +- Flutter Web shell with `Assistant` + `Settings` only, supporting `Single Agent` and `Relay OpenClaw Gateway` ### Not Yet Implemented - Built-in Codex runtime through Rust FFI @@ -58,7 +58,7 @@ Web keeps the Assistant-first entry flow, but only exposes: - `Assistant` - `Settings` -- `Direct AI Gateway` +- `Single Agent` - `Relay OpenClaw Gateway` Web does not expose local CLI, workspace file access, native runtime orchestration, or desktop-only diagnostics. diff --git a/docs/architecture/assistant-thread-information-architecture.md b/docs/architecture/assistant-thread-information-architecture.md index 886536d8..123aebd7 100644 --- a/docs/architecture/assistant-thread-information-architecture.md +++ b/docs/architecture/assistant-thread-information-architecture.md @@ -37,7 +37,7 @@ flowchart LR C["会话头部"] --> B D["底部 composer 工具栏"] --> B - A1["分组:仅 AI Gateway / 本地 / 远程"] + A1["分组:单机智能体 / 本地 / 远程"] A2["新对话"] A3["任务卡片"] A4["归档动作"] @@ -133,7 +133,7 @@ flowchart LR | 维度 | 当前状态 | 说明 | | --- | --- | --- | | 消息历史 | 是 | 每个线程独立保存 / 解析历史 | -| 执行模式 | 是 | `AI Gateway Only / Local / Remote` 跟线程绑定 | +| 执行模式 | 是 | `Single Agent / Local / Remote` 跟线程绑定 | | Skills | 是 | 已导入 / 已选 skills 跟线程绑定 | | 模型 | 是 | `assistantModelId` 跟线程绑定,没设时回退到默认模型 | | 顶部连接状态 | 是 | 只显示当前线程解析出的连接状态 | diff --git a/docs/architecture/xworkmate-internal-state-architecture.md b/docs/architecture/xworkmate-internal-state-architecture.md index 944136b6..058f33b5 100644 --- a/docs/architecture/xworkmate-internal-state-architecture.md +++ b/docs/architecture/xworkmate-internal-state-architecture.md @@ -318,14 +318,14 @@ state. 3.1 Execution target / work mode Meaning: -- AI Gateway only +- Single Agent - Local OpenClaw Gateway - Remote OpenClaw Gateway Platform availability: -- Desktop: aiGatewayOnly, local, remote +- Desktop: singleAgent, local, remote - Mobile: remote -- Web: aiGatewayOnly, remote +- Web: singleAgent, remote Primary resolver: assistantExecutionTargetForSession(sessionKey) @@ -345,7 +345,7 @@ has changed unless the current thread record is also synchronized. Important separation: - `assistantExecutionTarget` is the work-mode default / thread override axis - it is not a pointer into `gatewayProfiles` -- AI Gateway only has no OpenClaw profile +- Single Agent has no OpenClaw profile - there is no implicit local-to-remote or AI-to-remote profile fallback 3.1.1 OpenClaw gateway profile list @@ -388,7 +388,7 @@ Resolution order: 2. resolved model for current execution target Fallback rules: -- If target is aiGatewayOnly, use resolvedAiGatewayModel +- If target is singleAgent, use resolvedAiGatewayModel - If target is local or remote, use resolvedDefaultModel Interpretation: @@ -589,7 +589,7 @@ switchSession(sessionKey) must synchronize: 6.4 What must never happen implicitly - local OpenClaw selectedAgentId must not silently fall back to remote -- AI Gateway only mode must not silently borrow a gateway profile +- Single Agent mode must not silently borrow a gateway profile - gatewayProfiles changes must not silently overwrite the current thread mode - platform capability filtering must not invent unsupported work modes diff --git a/docs/cases/README.md b/docs/cases/README.md index 4aa28dee..af0d5e84 100644 --- a/docs/cases/README.md +++ b/docs/cases/README.md @@ -2,7 +2,7 @@ 这组案例用于手动验证 `XWorkmate` 当前的多 Agent 协作链路,覆盖: -- `仅 AI Gateway` +- `单机智能体` - `本地 OpenClaw Gateway` - `远程 OpenClaw Gateway` - `ARIS + 本地 Ollama` @@ -34,7 +34,7 @@ ## 建议记录项 - 当前使用的框架:`原生` 或 `ARIS` -- 当前执行模式:`仅 AI Gateway` / `本地 OpenClaw Gateway` / `远程 OpenClaw Gateway` +- 当前执行模式:`单机智能体` / `本地 OpenClaw Gateway` / `远程 OpenClaw Gateway` - 参与角色的 CLI 组合 - 是否看到流式输出 - 是否发生自动回退 diff --git a/docs/cases/aris_local_ollama_feature_delivery.md b/docs/cases/aris_local_ollama_feature_delivery.md index 6c21b8b4..bcc32baf 100644 --- a/docs/cases/aris_local_ollama_feature_delivery.md +++ b/docs/cases/aris_local_ollama_feature_delivery.md @@ -7,7 +7,7 @@ ## 推荐配置 - 框架:`ARIS` -- 执行模式:`仅 AI Gateway` 或 `本地 OpenClaw Gateway` +- 执行模式:`单机智能体` 或 `本地 OpenClaw Gateway` - Ollama 端点:`http://127.0.0.1:11434` - Architect:`gemini` - Engineer:`opencode` diff --git a/docs/cases/thread_mode_switch_followup.md b/docs/cases/thread_mode_switch_followup.md index 951881f8..6110b154 100644 --- a/docs/cases/thread_mode_switch_followup.md +++ b/docs/cases/thread_mode_switch_followup.md @@ -10,13 +10,13 @@ ## 需要覆盖的三种模式 -- `仅 AI Gateway` +- `单机智能体` - `本地 OpenClaw Gateway` - `远程 OpenClaw Gateway` ## 建议步骤 -### 场景 A:仅 AI Gateway +### 场景 A:单机智能体 发送: @@ -26,7 +26,7 @@ 确认: -- 顶部状态显示 `仅 AI Gateway` +- 顶部状态显示 `单机智能体` - 不显示 `已连接 openclaw ...` - 模型标签来自 AI Gateway 当前模型 @@ -64,7 +64,7 @@ ## 通过标准 - 切换模式后,模型显示会跟着变 -- `仅 AI Gateway` 不会错误显示 OpenClaw 已连接 +- `单机智能体` 不会错误显示 OpenClaw 已连接 - 三种模式下线程都能继续追问 - 任务列表分组归属与实际提交模式一致 - 右上角状态只反映当前线程,不沿用别的线程连接结果 diff --git a/docs/web-deployment.md b/docs/web-deployment.md index bd3902e9..ea8243c0 100644 --- a/docs/web-deployment.md +++ b/docs/web-deployment.md @@ -10,7 +10,7 @@ The Web app keeps only: - `Assistant` - `Settings` -- `Direct AI Gateway` +- `Single Agent` - `Relay OpenClaw Gateway` The following remain desktop-only: @@ -48,7 +48,7 @@ flutter build web --release --base-href / ## Network Requirements -- `Direct AI Gateway` must be browser-reachable from the end user device. +- `Single Agent` must be browser-reachable from the end user device. - Direct gateway endpoints must allow the Web origin with correct CORS headers. - If a provider cannot satisfy browser reachability or CORS constraints, users must use `Relay OpenClaw Gateway` instead. - Relay endpoints should stay on TLS in production and must not silently downgrade to insecure transport for remote usage. diff --git a/lib/app/app_controller_desktop.dart b/lib/app/app_controller_desktop.dart index a429beb2..2546e389 100644 --- a/lib/app/app_controller_desktop.dart +++ b/lib/app/app_controller_desktop.dart @@ -239,7 +239,7 @@ class AppController extends ChangeNotifier { String get settingsDraftStatusMessage => _settingsDraftStatusMessage; LegacyRecoveryReport get legacyRecoveryReport => _store.lastRecoveryReport; List get agents => _agentsController.agents; - List get sessions => isAiGatewayOnlyMode + List get sessions => isSingleAgentMode ? _assistantSessionSummaries() : _sessionsController.sessions; List get assistantSessions => _assistantSessions(); @@ -275,8 +275,8 @@ class AppController extends ChangeNotifier { String get aiGatewayUrl => settings.aiGateway.baseUrl.trim(); bool get hasStoredAiGatewayApiKey => _settingsController.secureRefs.containsKey('ai_gateway_api_key'); - bool get isAiGatewayOnlyMode => - currentAssistantExecutionTarget == AssistantExecutionTarget.aiGatewayOnly; + bool get isSingleAgentMode => + currentAssistantExecutionTarget == AssistantExecutionTarget.singleAgent; bool get isCodexBridgeBusy => _isCodexBridgeBusy; String? get codexBridgeError => _codexBridgeError; String? get codexRuntimeWarning => _codexRuntimeWarning; @@ -344,7 +344,7 @@ class AppController extends ChangeNotifier { } String _resolvedAssistantModelForTarget(AssistantExecutionTarget target) { - if (target == AssistantExecutionTarget.aiGatewayOnly) { + if (target == AssistantExecutionTarget.singleAgent) { return resolvedAiGatewayModel; } final resolved = resolvedDefaultModel.trim(); @@ -397,7 +397,7 @@ class AppController extends ChangeNotifier { } String get assistantConversationOwnerLabel { - if (!isAiGatewayOnlyMode) { + if (!isSingleAgentMode) { return activeAgentName; } final model = resolvedAssistantModel; @@ -412,7 +412,7 @@ class AppController extends ChangeNotifier { ) { final normalizedSessionKey = _normalizedAssistantSessionKey(sessionKey); final target = assistantExecutionTargetForSession(normalizedSessionKey); - if (target == AssistantExecutionTarget.aiGatewayOnly) { + if (target == AssistantExecutionTarget.singleAgent) { final model = assistantModelForSession(normalizedSessionKey); final host = _aiGatewayHostLabel(settings.aiGateway.baseUrl); final detail = _joinConnectionParts([model, host]); @@ -674,7 +674,7 @@ class AppController extends ChangeNotifier { List _assistantModelChoicesForSession(String sessionKey) { final target = assistantExecutionTargetForSession(sessionKey); - if (target == AssistantExecutionTarget.aiGatewayOnly) { + if (target == AssistantExecutionTarget.singleAgent) { return aiGatewayConversationModelChoices; } final runtimeModels = connectedGatewayModelChoices; @@ -714,7 +714,7 @@ class AppController extends ChangeNotifier { bool get canQuickConnectGateway { final target = currentAssistantExecutionTarget; - if (target == AssistantExecutionTarget.aiGatewayOnly) { + if (target == AssistantExecutionTarget.singleAgent) { return false; } final profile = _gatewayProfileForAssistantExecutionTarget(target); @@ -729,7 +729,7 @@ class AppController extends ChangeNotifier { return true; } final defaults = switch (target) { - AssistantExecutionTarget.aiGatewayOnly => + AssistantExecutionTarget.singleAgent => GatewayConnectionProfile.emptySlot(index: kGatewayRemoteProfileIndex), AssistantExecutionTarget.local => GatewayConnectionProfile.defaultsLocal(), @@ -773,11 +773,11 @@ class AppController extends ChangeNotifier { _sessionsController.currentSessionKey, ); final items = List.from( - isAiGatewayOnlyMode + isSingleAgentMode ? (_gatewayHistoryCache[sessionKey] ?? const []) : _chatController.messages, ); - final threadItems = isAiGatewayOnlyMode + final threadItems = isSingleAgentMode ? _assistantThreadMessages[sessionKey] : null; if (threadItems != null && threadItems.isNotEmpty) { @@ -787,7 +787,7 @@ class AppController extends ChangeNotifier { if (localItems != null && localItems.isNotEmpty) { items.addAll(localItems); } - final streaming = isAiGatewayOnlyMode + final streaming = isSingleAgentMode ? (_aiGatewayStreamingTextBySession[sessionKey]?.trim() ?? '') : (_chatController.streamingAssistantText?.trim() ?? ''); if (streaming.isNotEmpty) { @@ -876,7 +876,7 @@ class AppController extends ChangeNotifier { bool assistantSessionHasPendingRun(String sessionKey) { final normalized = _normalizedAssistantSessionKey(sessionKey); if (assistantExecutionTargetForSession(normalized) == - AssistantExecutionTarget.aiGatewayOnly) { + AssistantExecutionTarget.singleAgent) { return _aiGatewayPendingSessionKeys.contains(normalized); } return (_chatController.hasPendingRun || _multiAgentRunPending) && @@ -1246,7 +1246,7 @@ class AppController extends ChangeNotifier { Future connectSavedGateway() async { final target = currentAssistantExecutionTarget; - if (target == AssistantExecutionTarget.aiGatewayOnly) { + if (target == AssistantExecutionTarget.singleAgent) { return; } await _connectProfile(_gatewayProfileForAssistantExecutionTarget(target)); @@ -1322,7 +1322,7 @@ class AppController extends ChangeNotifier { Future selectAgent(String? agentId) async { _agentsController.selectAgent(agentId); if (currentAssistantExecutionTarget != - AssistantExecutionTarget.aiGatewayOnly) { + AssistantExecutionTarget.singleAgent) { final target = currentAssistantExecutionTarget; final nextProfile = _gatewayProfileForAssistantExecutionTarget( target, @@ -1368,7 +1368,7 @@ class AppController extends ChangeNotifier { final nextTarget = assistantExecutionTargetForSession(nextSessionKey); final nextViewMode = assistantMessageViewModeForSession(nextSessionKey); - if (!isAiGatewayOnlyMode) { + if (!isSingleAgentMode) { _preserveGatewayHistoryForSession(previousSessionKey); } @@ -1384,7 +1384,7 @@ class AppController extends ChangeNotifier { sessionKey: nextSessionKey, persistDefaultSelection: false, ); - if (nextTarget == AssistantExecutionTarget.aiGatewayOnly) { + if (nextTarget == AssistantExecutionTarget.singleAgent) { await discoverGatewayOnlySkillsForSession(nextSessionKey); } else { await dismissDiscoveredSkillsForSession(nextSessionKey); @@ -1398,7 +1398,7 @@ class AppController extends ChangeNotifier { List attachments = const [], }) async { - if (isAiGatewayOnlyMode) { + if (isSingleAgentMode) { await _sendAiGatewayMessage( message, thinking: thinking, @@ -1436,7 +1436,7 @@ class AppController extends ChangeNotifier { _notifyIfActive(); return; } - if (isAiGatewayOnlyMode) { + if (isSingleAgentMode) { await _abortAiGatewayRun(_sessionsController.currentSessionKey); return; } @@ -1466,7 +1466,7 @@ class AppController extends ChangeNotifier { sessionKey: _sessionsController.currentSessionKey, persistDefaultSelection: true, ); - if (resolvedTarget == AssistantExecutionTarget.aiGatewayOnly) { + if (resolvedTarget == AssistantExecutionTarget.singleAgent) { await discoverGatewayOnlySkillsForSession( _sessionsController.currentSessionKey, ); @@ -1531,7 +1531,7 @@ class AppController extends ChangeNotifier { ); } - if (resolvedTarget == AssistantExecutionTarget.aiGatewayOnly) { + if (resolvedTarget == AssistantExecutionTarget.singleAgent) { if (_runtime.isConnected) { _preserveGatewayHistoryForSession(normalizedSessionKey); } @@ -1641,7 +1641,7 @@ class AppController extends ChangeNotifier { Future discoverGatewayOnlySkillsForSession(String sessionKey) async { final normalizedSessionKey = _normalizedAssistantSessionKey(sessionKey); if (assistantExecutionTargetForSession(normalizedSessionKey) != - AssistantExecutionTarget.aiGatewayOnly) { + AssistantExecutionTarget.singleAgent) { _upsertAssistantThreadRecord( normalizedSessionKey, discoveredSkills: const [], @@ -2138,13 +2138,13 @@ class AppController extends ChangeNotifier { String tokenOverride = '', String passwordOverride = '', }) async { - if (executionTarget == AssistantExecutionTarget.aiGatewayOnly || + if (executionTarget == AssistantExecutionTarget.singleAgent || profile.mode == RuntimeConnectionMode.unconfigured) { return ( state: 'inactive', message: appText( - '当前模式仅使用 AI Gateway,不建立 OpenClaw Gateway 会话。', - 'The current mode uses AI Gateway only and does not open an OpenClaw Gateway session.', + '当前模式使用单机智能体,不建立 OpenClaw Gateway 会话。', + 'The current mode uses Single Agent and does not open an OpenClaw Gateway session.', ), endpoint: '', ); @@ -2389,7 +2389,7 @@ class AppController extends ChangeNotifier { ); await _restoreInitialAssistantSessionSelection(); await _ensureActiveAssistantThread(); - if (isAiGatewayOnlyMode) { + if (isSingleAgentMode) { await discoverGatewayOnlySkillsForSession(currentSessionKey); } _runtimeEventsSubscription = _runtimeCoordinator.gateway.events.listen( @@ -2399,7 +2399,7 @@ class AppController extends ChangeNotifier { startupTarget, ); final shouldAutoConnect = - startupTarget != AssistantExecutionTarget.aiGatewayOnly && + startupTarget != AssistantExecutionTarget.singleAgent && startupProfile != null && startupProfile.useSetupCode && startupProfile.setupCode.trim().isNotEmpty; @@ -2596,7 +2596,7 @@ class AppController extends ChangeNotifier { sessionKey: sessionKey, persistDefaultSelection: false, ); - if (target == AssistantExecutionTarget.aiGatewayOnly) { + if (target == AssistantExecutionTarget.singleAgent) { await discoverGatewayOnlySkillsForSession(sessionKey); } else { await dismissDiscoveredSkillsForSession(sessionKey); @@ -2620,7 +2620,7 @@ class AppController extends ChangeNotifier { } Future _ensureActiveAssistantThread() async { - if (!isAiGatewayOnlyMode || + if (!isSingleAgentMode || !isAssistantTaskArchived(_sessionsController.currentSessionKey)) { return; } @@ -3857,7 +3857,7 @@ class AppController extends ChangeNotifier { return GatewayMode.offline; } return switch (currentAssistantExecutionTarget) { - AssistantExecutionTarget.aiGatewayOnly => GatewayMode.offline, + AssistantExecutionTarget.singleAgent => GatewayMode.offline, AssistantExecutionTarget.local => GatewayMode.local, AssistantExecutionTarget.remote => GatewayMode.remote, }; @@ -4011,7 +4011,7 @@ class AppController extends ChangeNotifier { ) { return switch (mode) { RuntimeConnectionMode.unconfigured => - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, RuntimeConnectionMode.local => AssistantExecutionTarget.local, RuntimeConnectionMode.remote => AssistantExecutionTarget.remote, }; @@ -4023,8 +4023,8 @@ class AppController extends ChangeNotifier { return switch (target) { AssistantExecutionTarget.local => settings.primaryLocalGatewayProfile, AssistantExecutionTarget.remote => settings.primaryRemoteGatewayProfile, - AssistantExecutionTarget.aiGatewayOnly => throw StateError( - 'AI Gateway only target has no OpenClaw gateway profile.', + AssistantExecutionTarget.singleAgent => throw StateError( + 'Single Agent target has no OpenClaw gateway profile.', ), }; } @@ -4033,8 +4033,8 @@ class AppController extends ChangeNotifier { return switch (target) { AssistantExecutionTarget.local => kGatewayLocalProfileIndex, AssistantExecutionTarget.remote => kGatewayRemoteProfileIndex, - AssistantExecutionTarget.aiGatewayOnly => throw StateError( - 'AI Gateway only target has no OpenClaw gateway profile index.', + AssistantExecutionTarget.singleAgent => throw StateError( + 'Single Agent target has no OpenClaw gateway profile index.', ), }; } diff --git a/lib/app/app_controller_web.dart b/lib/app/app_controller_web.dart index e90e3164..26c3c204 100644 --- a/lib/app/app_controller_web.dart +++ b/lib/app/app_controller_web.dart @@ -136,8 +136,8 @@ class AppController extends ChangeNotifier { _currentRecord.executionTarget ?? _settings.assistantExecutionTarget; AssistantExecutionTarget get currentAssistantExecutionTarget => assistantExecutionTarget; - bool get isAiGatewayOnlyMode => - assistantExecutionTarget == AssistantExecutionTarget.aiGatewayOnly; + bool get isSingleAgentMode => + assistantExecutionTarget == AssistantExecutionTarget.singleAgent; List get chatMessages { final base = List.from(_currentRecord.messages); final streaming = _streamingTextBySession[_currentSessionKey]?.trim() ?? ''; @@ -172,7 +172,7 @@ class AppController extends ChangeNotifier { DateTime.now().millisecondsSinceEpoch.toDouble(), executionTarget: _sanitizeTarget(record.executionTarget) ?? - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, pending: _pendingSessionKeys.contains(record.sessionKey), current: record.sessionKey == _currentSessionKey, ), @@ -233,7 +233,7 @@ class AppController extends ChangeNotifier { AssistantThreadConnectionState get currentAssistantConnectionState { final target = currentAssistantExecutionTarget; - if (target == AssistantExecutionTarget.aiGatewayOnly) { + if (target == AssistantExecutionTarget.singleAgent) { final host = _hostLabel(_settings.aiGateway.baseUrl); final model = resolvedAiGatewayModel; final detail = _joinConnectionParts([model, host]); @@ -244,7 +244,7 @@ class AppController extends ChangeNotifier { : RuntimeConnectionStatus.offline, primaryLabel: target.label, detailLabel: detail.isEmpty - ? appText('Direct AI 未配置', 'Direct AI not configured') + ? appText('单机智能体未配置', 'Single Agent not configured') : detail, ready: canUseAiGatewayConversation, pairingRequired: false, @@ -287,8 +287,8 @@ class AppController extends ChangeNotifier { ); } return appText( - '当前会话列表会在浏览器本地保存,刷新后仍可恢复 Direct AI / Relay 的历史入口。', - 'Conversation history is stored in this browser so Direct AI and Relay entries remain available after reload.', + '当前会话列表会在浏览器本地保存,刷新后仍可恢复单机智能体 / Relay 的历史入口。', + 'Conversation history is stored in this browser so Single Agent and Relay entries remain available after reload.', ); } @@ -301,7 +301,7 @@ class AppController extends ChangeNotifier { } final target = _sanitizeTarget(_settings.assistantExecutionTarget) ?? - AssistantExecutionTarget.aiGatewayOnly; + AssistantExecutionTarget.singleAgent; final record = _newRecord(target: target); _threadRecords[record.sessionKey] = record; _currentSessionKey = record.sessionKey; @@ -548,7 +548,7 @@ class AppController extends ChangeNotifier { AssistantExecutionTarget target, ) async { final resolvedTarget = - _sanitizeTarget(target) ?? AssistantExecutionTarget.aiGatewayOnly; + _sanitizeTarget(target) ?? AssistantExecutionTarget.singleAgent; _settings = _settings.copyWith(assistantExecutionTarget: resolvedTarget); _replaceCurrentRecord( _currentRecord.copyWith(executionTarget: resolvedTarget), @@ -570,7 +570,7 @@ class AppController extends ChangeNotifier { defaultProvider: provider.trim().isEmpty ? 'gateway' : provider.trim(), defaultModel: defaultModel.trim(), aiGateway: _settings.aiGateway.copyWith( - name: name.trim().isEmpty ? 'Direct AI' : name.trim(), + name: name.trim().isEmpty ? 'Single Agent' : name.trim(), baseUrl: normalizedBaseUrl?.toString() ?? baseUrl.trim(), ), ); @@ -625,7 +625,7 @@ class AppController extends ChangeNotifier { defaultProvider: provider.trim().isEmpty ? 'gateway' : provider.trim(), defaultModel: resolvedDefaultModel, aiGateway: _settings.aiGateway.copyWith( - name: name.trim().isEmpty ? 'Direct AI' : name.trim(), + name: name.trim().isEmpty ? 'Single Agent' : name.trim(), baseUrl: _aiGatewayClient.normalizeBaseUrl(baseUrl)?.toString() ?? baseUrl.trim(), @@ -836,12 +836,12 @@ class AppController extends ChangeNotifier { notifyListeners(); try { - if (target == AssistantExecutionTarget.aiGatewayOnly) { + if (target == AssistantExecutionTarget.singleAgent) { if (!canUseAiGatewayConversation) { throw Exception( appText( - '请先在 Settings 配置 Direct AI 的地址、API Key 和默认模型。', - 'Configure Direct AI endpoint, API key, and default model first.', + '请先在 Settings 配置单机智能体所需的 AI Gateway 地址、API Key 和默认模型。', + 'Configure the Single Agent AI Gateway endpoint, API key, and default model first.', ), ); } @@ -915,7 +915,7 @@ class AppController extends ChangeNotifier { SettingsSnapshot _sanitizeSettings(SettingsSnapshot snapshot) { final target = _sanitizeTarget(snapshot.assistantExecutionTarget) ?? - AssistantExecutionTarget.aiGatewayOnly; + AssistantExecutionTarget.singleAgent; final normalizedSessionBaseUrl = RemoteWebSessionRepository.normalizeBaseUrl( snapshot.webSessionPersistence.remoteBaseUrl, @@ -942,7 +942,7 @@ class AppController extends ChangeNotifier { AssistantThreadRecord _sanitizeRecord(AssistantThreadRecord record) { final target = _sanitizeTarget(record.executionTarget) ?? - AssistantExecutionTarget.aiGatewayOnly; + AssistantExecutionTarget.singleAgent; return record.copyWith( executionTarget: target, title: record.title.trim().isEmpty @@ -954,9 +954,9 @@ class AppController extends ChangeNotifier { AssistantExecutionTarget? _sanitizeTarget(AssistantExecutionTarget? target) { return switch (target) { AssistantExecutionTarget.remote => AssistantExecutionTarget.remote, - AssistantExecutionTarget.aiGatewayOnly => - AssistantExecutionTarget.aiGatewayOnly, - _ => AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent => + AssistantExecutionTarget.singleAgent, + _ => AssistantExecutionTarget.singleAgent, }; } diff --git a/lib/app/ui_feature_manifest.dart b/lib/app/ui_feature_manifest.dart index c3f8cbff..c3289d6d 100644 --- a/lib/app/ui_feature_manifest.dart +++ b/lib/app/ui_feature_manifest.dart @@ -960,7 +960,7 @@ class UiFeatureAccess { List get availableExecutionTargets { final targets = []; if (supportsDirectAi) { - targets.add(AssistantExecutionTarget.aiGatewayOnly); + targets.add(AssistantExecutionTarget.singleAgent); } if (supportsLocalGateway) { targets.add(AssistantExecutionTarget.local); @@ -980,12 +980,12 @@ class UiFeatureAccess { } final preferredOrder = platform == UiFeaturePlatform.web ? const [ - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, AssistantExecutionTarget.remote, ] : const [ AssistantExecutionTarget.local, - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, AssistantExecutionTarget.remote, ]; for (final candidate in preferredOrder) { @@ -994,7 +994,7 @@ class UiFeatureAccess { } } return platform == UiFeaturePlatform.web - ? AssistantExecutionTarget.aiGatewayOnly + ? AssistantExecutionTarget.singleAgent : AssistantExecutionTarget.local; } } diff --git a/lib/features/assistant/assistant_page.dart b/lib/features/assistant/assistant_page.dart index 762097dc..3f07004f 100644 --- a/lib/features/assistant/assistant_page.dart +++ b/lib/features/assistant/assistant_page.dart @@ -668,7 +668,7 @@ class _AssistantPageState extends State { } final shouldUseGatewayAgent = - executionTarget != AssistantExecutionTarget.aiGatewayOnly; + executionTarget != AssistantExecutionTarget.singleAgent; final autoAgent = shouldUseGatewayAgent ? _pickAutoAgent(controller, rawPrompt) : null; @@ -706,7 +706,7 @@ class _AssistantPageState extends State { preview: rawPrompt, status: controller.hasAssistantPendingRun || - executionTarget == AssistantExecutionTarget.aiGatewayOnly || + executionTarget == AssistantExecutionTarget.singleAgent || connectionState.connected ? 'running' : 'queued', @@ -826,7 +826,7 @@ class _AssistantPageState extends State { } List<_ComposerSkillOption> _availableSkillOptions(AppController controller) { - if (controller.isAiGatewayOnlyMode) { + if (controller.isSingleAgentMode) { return controller .assistantImportedSkillsForSession(controller.currentSessionKey) .map(_skillOptionFromThreadSkill) @@ -1275,7 +1275,7 @@ class _AssistantPageState extends State { String _buildDraftSessionKey(AppController controller) { final stamp = DateTime.now().millisecondsSinceEpoch; - if (controller.isAiGatewayOnlyMode) { + if (controller.isSingleAgentMode) { return 'draft:$stamp'; } final selectedAgentId = controller.selectedAgentId.trim(); @@ -2329,27 +2329,27 @@ class _AssistantEmptyState extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); final connectionState = controller.currentAssistantConnectionState; - final aiGatewayOnly = connectionState.isAiGatewayOnly; + final singleAgent = connectionState.isSingleAgent; final connected = connectionState.connected; final reconnectAvailable = controller.canQuickConnectGateway; - final title = aiGatewayOnly + final title = singleAgent ? connected - ? appText('开始 AI 对话', 'Start an AI conversation') + ? appText('开始单机智能体任务', 'Start a single-agent task') : appText('先配置 AI Gateway', 'Configure AI Gateway first') : connected ? appText('开始对话或运行任务', 'Start a chat or run a task') : connectionState.status == RuntimeConnectionStatus.error ? appText('Gateway 连接失败', 'Gateway connection failed') : appText('先连接 Gateway', 'Connect a gateway first'); - final description = aiGatewayOnly + final description = singleAgent ? connected ? appText( - '当前模式只通过 AI Gateway 处理当前任务,不会建立 OpenClaw Gateway 会话。', - 'This mode handles the current task through AI Gateway only and does not open an OpenClaw Gateway session.', + '当前模式使用单机智能体处理当前任务,不会建立 OpenClaw Gateway 会话。', + 'This mode uses a single agent for the current task and does not open an OpenClaw Gateway session.', ) : appText( - '请先在 设置 -> 集成 中配置 AI Gateway 地址、API Key 和默认模型,然后继续当前任务。', - 'Set the AI Gateway URL, API key, and default model in Settings -> Integrations, then continue this task.', + '请先在 设置 -> 集成 中配置 AI Gateway 地址、API Key 和默认模型,然后以单机智能体模式继续当前任务。', + 'Set the AI Gateway URL, API key, and default model in Settings -> Integrations, then continue this task in Single Agent mode.', ) : connected ? appText( @@ -2402,7 +2402,7 @@ class _AssistantEmptyState extends StatelessWidget { FilledButton.icon( onPressed: connected ? onFocusComposer - : aiGatewayOnly + : singleAgent ? onOpenAiGatewaySettings : reconnectAvailable ? () async { @@ -2412,7 +2412,7 @@ class _AssistantEmptyState extends StatelessWidget { icon: Icon( connected ? Icons.edit_rounded - : aiGatewayOnly + : singleAgent ? Icons.tune_rounded : reconnectAvailable ? Icons.refresh_rounded @@ -2421,7 +2421,7 @@ class _AssistantEmptyState extends StatelessWidget { label: Text( connected ? appText('开始输入', 'Start typing') - : aiGatewayOnly + : singleAgent ? appText('打开配置中心', 'Open settings') : reconnectAvailable ? appText('重新连接', 'Reconnect') @@ -2440,16 +2440,16 @@ class _AssistantEmptyState extends StatelessWidget { ), if (!connected) OutlinedButton.icon( - onPressed: aiGatewayOnly + onPressed: singleAgent ? onOpenAiGatewaySettings : onOpenGateway, icon: Icon( - aiGatewayOnly + singleAgent ? Icons.hub_outlined : Icons.settings_rounded, ), label: Text( - aiGatewayOnly + singleAgent ? appText('打开设置中心', 'Open settings') : appText('编辑连接', 'Edit connection'), ), @@ -2570,7 +2570,7 @@ class _ComposerBarState extends State<_ComposerBar> { resolveUiFeaturePlatformFromContext(context), ); final connectionState = controller.currentAssistantConnectionState; - final aiGatewayOnly = connectionState.isAiGatewayOnly; + final singleAgent = connectionState.isSingleAgent; final connected = connectionState.connected; final reconnectAvailable = controller.canQuickConnectGateway; final connecting = connectionState.connecting; @@ -2582,7 +2582,7 @@ class _ComposerBarState extends State<_ComposerBar> { final discoveredCount = widget.discoveredSkills.length; final submitLabel = connected ? appText('提交', 'Submit') - : aiGatewayOnly + : singleAgent ? appText('配置 AI Gateway', 'Configure AI Gateway') : connecting ? appText('连接中…', 'Connecting…') @@ -2801,7 +2801,7 @@ class _ComposerBarState extends State<_ComposerBar> { child: Row( mainAxisSize: MainAxisSize.min, children: [ - if (aiGatewayOnly && discoveredCount > 0) ...[ + if (singleAgent && discoveredCount > 0) ...[ InkWell( key: const Key('assistant-discovered-skills-button'), borderRadius: BorderRadius.circular(AppRadius.chip), @@ -2953,7 +2953,7 @@ class _ComposerBarState extends State<_ComposerBar> { ? null : connected ? widget.onSend - : aiGatewayOnly + : singleAgent ? widget.onOpenAiGatewaySettings : reconnectAvailable ? () async { @@ -2976,7 +2976,7 @@ class _ComposerBarState extends State<_ComposerBar> { Icon( connected ? Icons.arrow_upward_rounded - : aiGatewayOnly + : singleAgent ? Icons.hub_outlined : reconnectAvailable ? Icons.refresh_rounded @@ -3427,7 +3427,7 @@ class _ComposerToolbarChipState extends State<_ComposerToolbarChip> { extension on AssistantExecutionTarget { IconData get icon => switch (this) { - AssistantExecutionTarget.aiGatewayOnly => Icons.hub_outlined, + AssistantExecutionTarget.singleAgent => Icons.hub_outlined, AssistantExecutionTarget.local => Icons.computer_outlined, AssistantExecutionTarget.remote => Icons.cloud_outlined, }; @@ -3933,7 +3933,7 @@ class _ConnectionChip extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); final connectionState = controller.currentAssistantConnectionState; - final color = connectionState.isAiGatewayOnly + final color = connectionState.isSingleAgent ? (connectionState.connected ? context.palette.accentMuted : context.palette.surfaceSecondary) diff --git a/lib/runtime/runtime_models.dart b/lib/runtime/runtime_models.dart index 8be9d667..6aceedc3 100644 --- a/lib/runtime/runtime_models.dart +++ b/lib/runtime/runtime_models.dart @@ -31,13 +31,13 @@ extension RuntimeConnectionStatusCopy on RuntimeConnectionStatus { }; } -enum AssistantExecutionTarget { aiGatewayOnly, local, remote } +enum AssistantExecutionTarget { singleAgent, local, remote } extension AssistantExecutionTargetCopy on AssistantExecutionTarget { String get label => switch (this) { - AssistantExecutionTarget.aiGatewayOnly => appText( - '仅 AI Gateway', - 'AI Gateway Only', + AssistantExecutionTarget.singleAgent => appText( + '单机智能体', + 'Single Agent', ), AssistantExecutionTarget.local => appText( '本地 OpenClaw Gateway', @@ -50,16 +50,26 @@ extension AssistantExecutionTargetCopy on AssistantExecutionTarget { }; String get promptValue => switch (this) { - AssistantExecutionTarget.aiGatewayOnly => 'ai-gateway-only', + AssistantExecutionTarget.singleAgent => 'single-agent', AssistantExecutionTarget.local => 'local', AssistantExecutionTarget.remote => 'remote', }; static AssistantExecutionTarget fromJsonValue(String? value) { - return AssistantExecutionTarget.values.firstWhere( - (item) => item.name == value, - orElse: () => AssistantExecutionTarget.local, - ); + final normalized = value?.trim() ?? ''; + switch (normalized) { + case 'singleAgent': + case 'aiGatewayOnly': + case 'single-agent': + case 'ai-gateway-only': + return AssistantExecutionTarget.singleAgent; + case 'local': + return AssistantExecutionTarget.local; + case 'remote': + return AssistantExecutionTarget.remote; + default: + return AssistantExecutionTarget.local; + } } } @@ -84,13 +94,13 @@ class AssistantThreadConnectionState { final bool gatewayTokenMissing; final String? lastError; - bool get isAiGatewayOnly => - executionTarget == AssistantExecutionTarget.aiGatewayOnly; + bool get isSingleAgent => + executionTarget == AssistantExecutionTarget.singleAgent; bool get connected => ready; bool get connecting => - !isAiGatewayOnly && status == RuntimeConnectionStatus.connecting; + !isSingleAgent && status == RuntimeConnectionStatus.connecting; } enum AssistantMessageViewMode { rendered, raw } @@ -1493,7 +1503,7 @@ class SettingsSnapshot { AssistantExecutionTarget target, ) { return switch (target) { - AssistantExecutionTarget.aiGatewayOnly => null, + AssistantExecutionTarget.singleAgent => null, AssistantExecutionTarget.local => primaryLocalGatewayProfile, AssistantExecutionTarget.remote => primaryRemoteGatewayProfile, }; @@ -1515,7 +1525,7 @@ class SettingsSnapshot { final index = switch (target) { AssistantExecutionTarget.local => kGatewayLocalProfileIndex, AssistantExecutionTarget.remote => kGatewayRemoteProfileIndex, - AssistantExecutionTarget.aiGatewayOnly => null, + AssistantExecutionTarget.singleAgent => null, }; if (index == null) { return this; @@ -2084,6 +2094,17 @@ class AssistantThreadRecord { return keys; } + String? normalizeGatewayEntryState(Object? value) { + final normalized = value?.toString().trim() ?? ''; + if (normalized.isEmpty) { + return null; + } + if (normalized == 'ai-gateway-only') { + return 'single-agent'; + } + return normalized; + } + return AssistantThreadRecord( sessionKey: json['sessionKey']?.toString() ?? '', messages: messages, @@ -2102,7 +2123,7 @@ class AssistantThreadRecord { importedSkills: normalizeSkillEntries(json['importedSkills']), selectedSkillKeys: normalizeSkillKeys(json['selectedSkillKeys']), assistantModelId: json['assistantModelId']?.toString() ?? '', - gatewayEntryState: json['gatewayEntryState']?.toString(), + gatewayEntryState: normalizeGatewayEntryState(json['gatewayEntryState']), ); } } diff --git a/lib/web/web_ai_gateway_client.dart b/lib/web/web_ai_gateway_client.dart index 1c211471..7332338c 100644 --- a/lib/web/web_ai_gateway_client.dart +++ b/lib/web/web_ai_gateway_client.dart @@ -123,7 +123,7 @@ class WebAiGatewayClient { provider: _stringValue(map['provider']) ?? _stringValue(map['owned_by']) ?? - 'Direct AI', + 'Single Agent', contextWindow: _intValue(map['contextWindow']) ?? _intValue(map['context_window']), diff --git a/lib/web/web_assistant_page.dart b/lib/web/web_assistant_page.dart index a39b8d06..217b62d1 100644 --- a/lib/web/web_assistant_page.dart +++ b/lib/web/web_assistant_page.dart @@ -42,7 +42,7 @@ class _WebAssistantPageState extends State { builder: (context, _) { final uiFeatures = controller.featuresFor(UiFeaturePlatform.web); final allDirect = controller.conversationsForTarget( - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, ); final allRelay = controller.conversationsForTarget( AssistantExecutionTarget.remote, @@ -53,12 +53,12 @@ class _WebAssistantPageState extends State { final availableTargets = uiFeatures.availableExecutionTargets .where( (target) => - target == AssistantExecutionTarget.aiGatewayOnly || + target == AssistantExecutionTarget.singleAgent || target == AssistantExecutionTarget.remote, ) .toList(growable: false); final connected = - currentTarget == AssistantExecutionTarget.aiGatewayOnly + currentTarget == AssistantExecutionTarget.singleAgent ? controller.canUseAiGatewayConversation : controller.connection.status == RuntimeConnectionStatus.connected; final currentMessages = controller.chatMessages; @@ -83,8 +83,8 @@ class _WebAssistantPageState extends State { eyebrow: appText('Web Workspace', 'Web Workspace'), title: appText('助手', 'Assistant'), subtitle: appText( - 'Direct AI 与 Relay Gateway 共用一个入口,左侧保留会话/任务历史。', - 'Use one Assistant surface for Direct AI and Relay Gateway, with embedded conversation history on the left.', + '单机智能体与 Relay Gateway 共用一个入口,左侧保留会话/任务历史。', + 'Use one Assistant surface for Single Agent and Relay Gateway, with embedded conversation history on the left.', ), toolbar: Wrap( spacing: 10, @@ -232,12 +232,12 @@ class _ConversationRail extends StatelessWidget { children: [ if (showDirect) _ConversationGroup( - title: appText('Direct AI Gateway', 'Direct AI Gateway'), + title: appText('Single Agent', 'Single Agent'), icon: Icons.hub_rounded, items: direct, emptyLabel: appText( - '还没有 Direct AI 对话', - 'No Direct AI conversations yet', + '还没有单机智能体对话', + 'No Single Agent conversations yet', ), onSelect: controller.switchConversation, ), @@ -381,7 +381,7 @@ class _ConversationPanel extends StatelessWidget { Widget build(BuildContext context) { final palette = context.palette; final currentTarget = controller.assistantExecutionTarget; - final targetReady = currentTarget == AssistantExecutionTarget.aiGatewayOnly + final targetReady = currentTarget == AssistantExecutionTarget.singleAgent ? controller.canUseAiGatewayConversation : controller.connection.status == RuntimeConnectionStatus.connected; @@ -431,10 +431,10 @@ class _ConversationPanel extends StatelessWidget { const SizedBox(width: 12), Expanded( child: Text( - currentTarget == AssistantExecutionTarget.aiGatewayOnly + currentTarget == AssistantExecutionTarget.singleAgent ? appText( - '当前 Direct AI 配置还不完整,请先在 Settings 中保存地址、API Key 和默认模型。', - 'Direct AI is not ready yet. Save the endpoint, API key, and default model in Settings first.', + '当前单机智能体配置还不完整,请先在 Settings 中保存 AI Gateway 地址、API Key 和默认模型。', + 'Single Agent is not ready yet. Save the AI Gateway endpoint, API key, and default model in Settings first.', ) : appText( '当前 Relay Gateway 尚未连接,请先在 Settings 中保存配置并连接。', @@ -506,10 +506,10 @@ class _ConversationPanel extends StatelessWidget { Expanded( child: Text( currentTarget == - AssistantExecutionTarget.aiGatewayOnly + AssistantExecutionTarget.singleAgent ? appText( - 'Web 端 Direct AI 只保留纯网络能力,不提供本地文件和 CLI。', - 'Direct AI on web keeps network-only capabilities and does not expose local files or CLI.', + 'Web 端单机智能体只保留纯网络能力,不提供本地文件和 CLI。', + 'Single Agent on web keeps network-only capabilities and does not expose local files or CLI.', ) : appText( 'Web 端 Relay 模式使用远程 OpenClaw Gateway,不区分 local / remote。', @@ -637,9 +637,9 @@ class _TargetChip extends StatelessWidget { String _targetLabel(AssistantExecutionTarget target) { return switch (target) { - AssistantExecutionTarget.aiGatewayOnly => appText( - 'Direct AI Gateway', - 'Direct AI Gateway', + AssistantExecutionTarget.singleAgent => appText( + 'Single Agent', + 'Single Agent', ), AssistantExecutionTarget.remote => appText( 'Relay OpenClaw Gateway', diff --git a/lib/web/web_settings_page.dart b/lib/web/web_settings_page.dart index 93be1050..e5b0dfd9 100644 --- a/lib/web/web_settings_page.dart +++ b/lib/web/web_settings_page.dart @@ -143,8 +143,8 @@ class _WebSettingsPageState extends State { eyebrow: appText('Web Preferences', 'Web Preferences'), title: appText('设置', 'Settings'), subtitle: appText( - 'Web 版只保留 Direct AI / Relay Gateway、界面偏好和基础信息。', - 'The web app keeps only Direct AI, Relay Gateway, appearance preferences, and basic product info.', + 'Web 版只保留单机智能体 / Relay Gateway、界面偏好和基础信息。', + 'The web app keeps only Single Agent, Relay Gateway, appearance preferences, and basic product info.', ), toolbar: Wrap( spacing: 10, @@ -227,7 +227,7 @@ class _WebSettingsPageState extends State { .availableExecutionTargets .where( (target) => - target == AssistantExecutionTarget.aiGatewayOnly || + target == AssistantExecutionTarget.singleAgent || target == AssistantExecutionTarget.remote, ) .toList(growable: false); @@ -401,7 +401,7 @@ class _WebSettingsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - appText('Direct AI', 'Direct AI'), + appText('单机智能体', 'Single Agent'), style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 12), @@ -750,8 +750,8 @@ class _WebSettingsPageState extends State { const SizedBox(height: 8), Text( appText( - 'Root SPA 目标部署到 https://xworkmate.svc.plus/ 。Direct AI 需要浏览器可达且支持 CORS;否则请使用 Relay 模式。', - 'The root SPA targets https://xworkmate.svc.plus/ . Direct AI endpoints must be browser-reachable and CORS-compatible; otherwise use relay mode.', + 'Root SPA 目标部署到 https://xworkmate.svc.plus/ 。单机智能体依赖的 AI Gateway endpoint 需要浏览器可达且支持 CORS;否则请使用 Relay 模式。', + 'The root SPA targets https://xworkmate.svc.plus/ . Single Agent AI Gateway endpoints must be browser-reachable and CORS-compatible; otherwise use relay mode.', ), ), ], @@ -782,9 +782,9 @@ String _themeLabel(ThemeMode mode) { String _targetLabel(AssistantExecutionTarget target) { return switch (target) { - AssistantExecutionTarget.aiGatewayOnly => appText( - 'Direct AI Gateway', - 'Direct AI Gateway', + AssistantExecutionTarget.singleAgent => appText( + 'Single Agent', + 'Single Agent', ), AssistantExecutionTarget.remote => appText( 'Relay OpenClaw Gateway', diff --git a/releases/v0.5/README.md b/releases/v0.5/README.md index 6f10bc07..0a704ba6 100644 --- a/releases/v0.5/README.md +++ b/releases/v0.5/README.md @@ -9,7 +9,7 @@ ## Release Focus - 持续 Assistant 任务线程与流式 AI Gateway 对话 -- `仅 AI Gateway / 本地 OpenClaw Gateway / 远程 OpenClaw Gateway` 三模式统一 +- `单机智能体 / 本地 OpenClaw Gateway / 远程 OpenClaw Gateway` 三模式统一 - `Architect / Engineer / Tester` 多 Agent 协作 - 可选 `ARIS` 框架、内嵌 skills、Go bridge runtime - `Ollama Cloud` 文案和默认地址统一 diff --git a/test/app/ui_feature_manifest_test.dart b/test/app/ui_feature_manifest_test.dart index ff0da335..90d4fef8 100644 --- a/test/app/ui_feature_manifest_test.dart +++ b/test/app/ui_feature_manifest_test.dart @@ -70,7 +70,7 @@ void main() { expect( desktopAccess.availableExecutionTargets, equals([ - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, AssistantExecutionTarget.local, AssistantExecutionTarget.remote, ]), @@ -82,7 +82,7 @@ void main() { expect( webAccess.availableExecutionTargets, equals([ - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, AssistantExecutionTarget.remote, ]), ); diff --git a/test/features/assistant_page_suite.dart b/test/features/assistant_page_suite.dart index 55119c68..4443e6cb 100644 --- a/test/features/assistant_page_suite.dart +++ b/test/features/assistant_page_suite.dart @@ -196,7 +196,7 @@ void main() { await _pumpForUiSync(tester); await controller.setAssistantExecutionTarget( - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, ); await _pumpForUiSync(tester); @@ -212,7 +212,7 @@ void main() { await _pumpForUiSync(tester); final aiGroup = find.byKey( - const ValueKey('assistant-task-group-aiGatewayOnly'), + const ValueKey('assistant-task-group-singleAgent'), ); final localGroup = find.byKey( const ValueKey('assistant-task-group-local'), @@ -246,7 +246,7 @@ void main() { ); expect( - find.byKey(const ValueKey('assistant-task-group-aiGatewayOnly')), + find.byKey(const ValueKey('assistant-task-group-singleAgent')), findsOneWidget, ); expect( @@ -450,7 +450,7 @@ void main() { ); await _pumpForUiSync(tester); - expect(find.text('仅 AI Gateway'), findsWidgets); + expect(find.text('单机智能体'), findsWidgets); expect(find.text('本地 OpenClaw Gateway'), findsWidgets); expect(find.text('远程 OpenClaw Gateway'), findsWidgets); }); @@ -586,7 +586,7 @@ void main() { await _pumpForUiSync(tester); await controller.setAssistantExecutionTarget( - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, ); await _pumpForUiSync(tester); @@ -619,11 +619,11 @@ void main() { expect( find.descendant( of: find.byKey(const Key('assistant-execution-target-button')), - matching: find.text('仅 AI Gateway'), + matching: find.text('单机智能体'), ), findsOneWidget, ); - expect(find.textContaining('仅 AI Gateway'), findsWidgets); + expect(find.textContaining('单机智能体'), findsWidgets); }, skip: true, ); @@ -655,7 +655,7 @@ void main() { sessionKey: 'main', title: '研发任务', archived: false, - executionTarget: AssistantExecutionTarget.aiGatewayOnly, + executionTarget: AssistantExecutionTarget.singleAgent, messageViewMode: AssistantMessageViewMode.rendered, updatedAtMs: 1700000000000, messages: [ @@ -711,7 +711,7 @@ void main() { // Known flutter_tester host-exit hang in this widget scenario. testWidgets( - 'AssistantPage shows AI Gateway-only chip and keeps task rows minimal', + 'AssistantPage shows Single Agent chip and keeps task rows minimal', (WidgetTester tester) async { final controller = await createTestController(tester); await controller.settingsController.saveAiGatewayApiKey('live-key'); @@ -723,7 +723,7 @@ void main() { selectedModels: const ['qwen2.5-coder:latest'], ), defaultModel: 'qwen2.5-coder:latest', - assistantExecutionTarget: AssistantExecutionTarget.aiGatewayOnly, + assistantExecutionTarget: AssistantExecutionTarget.singleAgent, ), refreshAfterSave: false, ); @@ -738,7 +738,7 @@ void main() { findsOneWidget, ); expect( - find.text('仅 AI Gateway · qwen2.5-coder:latest · 127.0.0.1:11434'), + find.text('单机智能体 · qwen2.5-coder:latest · 127.0.0.1:11434'), findsOneWidget, ); expect(find.text('等待描述这个任务的第一条消息'), findsNothing); @@ -773,7 +773,7 @@ Future _createControllerWithThreadRecords({ availableModels: const ['qwen2.5-coder:latest'], selectedModels: const ['qwen2.5-coder:latest'], ), - assistantExecutionTarget: AssistantExecutionTarget.aiGatewayOnly, + assistantExecutionTarget: AssistantExecutionTarget.singleAgent, defaultModel: 'qwen2.5-coder:latest', ), ); diff --git a/test/runtime/app_controller_ai_gateway_chat_suite.dart b/test/runtime/app_controller_ai_gateway_chat_suite.dart index 7bd521e1..63f9d2cf 100644 --- a/test/runtime/app_controller_ai_gateway_chat_suite.dart +++ b/test/runtime/app_controller_ai_gateway_chat_suite.dart @@ -17,7 +17,7 @@ import 'package:xworkmate/runtime/secure_config_store.dart'; void main() { test( - 'AppController streams and restores persistent AI Gateway-only conversation turns', + 'AppController streams and restores persistent Single Agent conversation turns', () async { SharedPreferences.setMockInitialValues({}); final tempDirectory = await Directory.systemTemp.createTemp( @@ -62,12 +62,12 @@ void main() { refreshAfterSave: false, ); await controller.setAssistantExecutionTarget( - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, ); const firstQuestion = 'Execution context:\n' - '- target: ai-gateway-only\n' + '- target: single-agent\n' '- workspace_root: /opt/data/workspace\n' '- permission: full-access\n\n' '今天聊点什么'; @@ -114,7 +114,7 @@ void main() { expect(secondController.chatMessages.last.text, 'FIRST_REPLY'); expect( secondController.settings.assistantExecutionTarget, - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, ); final secondTurn = secondController.sendChatMessage( @@ -152,7 +152,7 @@ void main() { secondController.connection.status, RuntimeConnectionStatus.offline, ); - expect(secondController.assistantConnectionStatusLabel, '仅 AI Gateway'); + expect(secondController.assistantConnectionStatusLabel, '单机智能体'); expect( secondController.assistantConnectionTargetLabel, 'qwen2.5-coder:latest · 127.0.0.1:${server.port}', @@ -208,7 +208,7 @@ void main() { refreshAfterSave: false, ); await controller.setAssistantExecutionTarget( - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, ); await controller.sendChatMessage('你好', thinking: 'low'); @@ -226,7 +226,7 @@ void main() { ); test( - 'AppController abortRun stops AI Gateway-only streaming requests', + 'AppController abortRun stops Single Agent streaming requests', () async { SharedPreferences.setMockInitialValues({}); final tempDirectory = await Directory.systemTemp.createTemp( @@ -270,7 +270,7 @@ void main() { refreshAfterSave: false, ); await controller.setAssistantExecutionTarget( - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, ); final pendingTurn = controller.sendChatMessage('今天聊点什么', thinking: 'low'); diff --git a/test/runtime/app_controller_ai_gateway_models_suite.dart b/test/runtime/app_controller_ai_gateway_models_suite.dart index 6fd84fc1..b7750812 100644 --- a/test/runtime/app_controller_ai_gateway_models_suite.dart +++ b/test/runtime/app_controller_ai_gateway_models_suite.dart @@ -66,7 +66,7 @@ void main() { selectedModels: const ['qwen2.5-coder:latest'], ), defaultModel: 'gpt-5.4', - assistantExecutionTarget: AssistantExecutionTarget.aiGatewayOnly, + assistantExecutionTarget: AssistantExecutionTarget.singleAgent, ), ); diff --git a/test/runtime/app_controller_execution_target_switch_suite.dart b/test/runtime/app_controller_execution_target_switch_suite.dart index 7fc6e3c3..6a94a631 100644 --- a/test/runtime/app_controller_execution_target_switch_suite.dart +++ b/test/runtime/app_controller_execution_target_switch_suite.dart @@ -192,7 +192,7 @@ void main() { await controller.saveSettings( _withRemoteGatewayProfile( controller.settings.copyWith( - assistantExecutionTarget: AssistantExecutionTarget.aiGatewayOnly, + assistantExecutionTarget: AssistantExecutionTarget.singleAgent, aiGateway: controller.settings.aiGateway.copyWith( baseUrl: 'http://127.0.0.1:11434/v1', availableModels: const ['qwen2.5-coder:latest'], @@ -262,18 +262,18 @@ void main() { ); await controller.setAssistantExecutionTarget( - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, ); expect( controller.settings.assistantExecutionTarget, - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, ); expect( controller.settings.primaryRemoteGatewayProfile.host, 'gateway.example.com', reason: - 'AI Gateway-only mode should preserve the saved remote endpoint.', + 'Single Agent mode should preserve the saved remote endpoint.', ); expect(controller.settings.primaryRemoteGatewayProfile.port, 9443); expect(controller.settings.primaryRemoteGatewayProfile.tls, isTrue); @@ -282,7 +282,7 @@ void main() { RuntimeConnectionMode.remote, ); expect(gateway.disconnectCount, 1); - expect(controller.assistantConnectionStatusLabel, '仅 AI Gateway'); + expect(controller.assistantConnectionStatusLabel, '单机智能体'); expect( controller.assistantConnectionTargetLabel, 'qwen2.5-coder:latest · 127.0.0.1:11434', @@ -290,7 +290,7 @@ void main() { expect( gateway.connectedProfiles, hasLength(2), - reason: 'AI Gateway-only mode should not open another gateway session.', + reason: 'Single Agent mode should not open another gateway session.', ); await controller.setAssistantExecutionTarget( @@ -539,7 +539,7 @@ void main() { ); test( - 'AppController notifies aiGatewayOnly target changes before disconnect completes', + 'AppController notifies singleAgent target changes before disconnect completes', () async { SharedPreferences.setMockInitialValues({}); final tempDirectory = await Directory.systemTemp.createTemp( @@ -567,7 +567,7 @@ void main() { await controller.saveSettings( _withRemoteGatewayProfile( controller.settings.copyWith( - assistantExecutionTarget: AssistantExecutionTarget.aiGatewayOnly, + assistantExecutionTarget: AssistantExecutionTarget.singleAgent, aiGateway: controller.settings.aiGateway.copyWith( baseUrl: 'http://127.0.0.1:11434/v1', availableModels: const ['qwen2.5-coder:latest'], @@ -598,7 +598,7 @@ void main() { gateway.holdNextDisconnect(disconnectGate); final switchFuture = controller.setAssistantExecutionTarget( - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, ); var completed = false; switchFuture.then((_) { @@ -611,9 +611,9 @@ void main() { expect(notificationCount, greaterThan(0)); expect( controller.assistantExecutionTarget, - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, ); - expect(controller.assistantConnectionStatusLabel, '仅 AI Gateway'); + expect(controller.assistantConnectionStatusLabel, '单机智能体'); expect(completed, isFalse); } finally { if (!disconnectGate.isCompleted) { @@ -625,13 +625,13 @@ void main() { expect( controller.settings.assistantExecutionTarget, - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, ); expect( controller.assistantExecutionTarget, - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, ); - expect(controller.assistantConnectionStatusLabel, '仅 AI Gateway'); + expect(controller.assistantConnectionStatusLabel, '单机智能体'); }, ); @@ -683,7 +683,7 @@ void main() { controller.initializeAssistantThreadContext( 'main', - executionTarget: AssistantExecutionTarget.aiGatewayOnly, + executionTarget: AssistantExecutionTarget.singleAgent, ); controller.initializeAssistantThreadContext( 'remote-thread', @@ -707,10 +707,10 @@ void main() { expect( controller.assistantExecutionTarget, - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, ); expect(gateway.disconnectCount, 1); - expect(controller.assistantConnectionStatusLabel, '仅 AI Gateway'); + expect(controller.assistantConnectionStatusLabel, '单机智能体'); expect( controller.settings.assistantExecutionTarget, AssistantExecutionTarget.local, @@ -798,11 +798,11 @@ void main() { controller.initializeAssistantThreadContext( 'main', - executionTarget: AssistantExecutionTarget.aiGatewayOnly, + executionTarget: AssistantExecutionTarget.singleAgent, ); await controller.switchSession('main'); - expect(controller.assistantConnectionStatusLabel, '仅 AI Gateway'); + expect(controller.assistantConnectionStatusLabel, '单机智能体'); expect( controller.assistantConnectionTargetLabel, 'qwen2.5-coder:latest · 127.0.0.1:11434', @@ -897,7 +897,7 @@ void main() { firstController.initializeAssistantThreadContext( 'draft:alpha', title: 'Alpha', - executionTarget: AssistantExecutionTarget.aiGatewayOnly, + executionTarget: AssistantExecutionTarget.singleAgent, ); firstController.initializeAssistantThreadContext( 'draft:beta', diff --git a/test/runtime/app_controller_thread_skills_suite.dart b/test/runtime/app_controller_thread_skills_suite.dart index 385ca73c..0a531e4d 100644 --- a/test/runtime/app_controller_thread_skills_suite.dart +++ b/test/runtime/app_controller_thread_skills_suite.dart @@ -56,7 +56,7 @@ void main() { await _waitFor(() => !controller.initializing); await controller.setAssistantExecutionTarget( - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, ); expect( @@ -116,7 +116,7 @@ void main() { await _waitFor(() => !controller.initializing); await controller.setAssistantExecutionTarget( - AssistantExecutionTarget.aiGatewayOnly, + AssistantExecutionTarget.singleAgent, ); final firstSessionKey = controller.currentSessionKey; expect( @@ -138,7 +138,7 @@ void main() { controller.initializeAssistantThreadContext( 'draft:thread-2', title: 'Thread 2', - executionTarget: AssistantExecutionTarget.aiGatewayOnly, + executionTarget: AssistantExecutionTarget.singleAgent, messageViewMode: AssistantMessageViewMode.rendered, ); await controller.switchSession('draft:thread-2'); diff --git a/test/runtime/secure_config_store_suite.dart b/test/runtime/secure_config_store_suite.dart index 99c8c748..d111c9b3 100644 --- a/test/runtime/secure_config_store_suite.dart +++ b/test/runtime/secure_config_store_suite.dart @@ -699,7 +699,7 @@ void main() { ], selectedSkillKeys: ['/tmp/imported-skill'], assistantModelId: 'gpt-5.4-mini', - gatewayEntryState: 'ai-gateway-only', + gatewayEntryState: 'single-agent', updatedAtMs: 1700000000000, messages: [ GatewayChatMessage( @@ -757,7 +757,7 @@ void main() { '/tmp/imported-skill', ]); expect(reloadedRecords.first.assistantModelId, 'gpt-5.4-mini'); - expect(reloadedRecords.first.gatewayEntryState, 'ai-gateway-only'); + expect(reloadedRecords.first.gatewayEntryState, 'single-agent'); expect(reloadedRecords.first.messages, hasLength(2)); expect(reloadedRecords.first.messages.last.text, '第一条回复'); }, @@ -782,18 +782,29 @@ void main() { 'updatedAtMs': 1700000000000, 'title': 'Legacy', 'archived': false, - 'executionTarget': 'local', + 'executionTarget': 'aiGatewayOnly', 'messageViewMode': 'rendered', + 'gatewayEntryState': 'ai-gateway-only', }); + expect(decoded.executionTarget, AssistantExecutionTarget.singleAgent); expect(decoded.discoveredSkills, isEmpty); expect(decoded.importedSkills, isEmpty); expect(decoded.selectedSkillKeys, isEmpty); expect(decoded.assistantModelId, isEmpty); - expect(decoded.gatewayEntryState, isNull); + expect(decoded.gatewayEntryState, 'single-agent'); }, ); + test('SettingsSnapshot keeps compatibility with legacy target json values', () { + final decoded = SettingsSnapshot.fromJson({ + ...SettingsSnapshot.defaults().toJson(), + 'assistantExecutionTarget': 'aiGatewayOnly', + }); + + expect(decoded.assistantExecutionTarget, AssistantExecutionTarget.singleAgent); + }); + test( 'SecureConfigStore restores assistant state from durable files when sqlite entries are missing', () async { @@ -820,7 +831,7 @@ void main() { sessionKey: 'draft:backup-1', title: '备份线程', archived: false, - executionTarget: AssistantExecutionTarget.aiGatewayOnly, + executionTarget: AssistantExecutionTarget.singleAgent, messageViewMode: AssistantMessageViewMode.rendered, updatedAtMs: 1700000000000, messages: [ diff --git a/test/web/web_remote_session_repository_browser_test.dart b/test/web/web_remote_session_repository_browser_test.dart index 53836f8d..62dfb87e 100644 --- a/test/web/web_remote_session_repository_browser_test.dart +++ b/test/web/web_remote_session_repository_browser_test.dart @@ -62,7 +62,7 @@ void main() { updatedAtMs: 1, title: 'hello', archived: false, - executionTarget: AssistantExecutionTarget.aiGatewayOnly, + executionTarget: AssistantExecutionTarget.singleAgent, messageViewMode: AssistantMessageViewMode.rendered, ), ]; diff --git a/test/web/web_settings_persistence_browser_test.dart b/test/web/web_settings_persistence_browser_test.dart index fbb2bcf8..7b096c08 100644 --- a/test/web/web_settings_persistence_browser_test.dart +++ b/test/web/web_settings_persistence_browser_test.dart @@ -12,7 +12,7 @@ import 'package:xworkmate/web/web_store.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - test('web controller persists direct and relay configuration', () async { + test('web controller persists single-agent and relay configuration', () async { SharedPreferences.setMockInitialValues({}); final remoteRecords = []; @@ -24,7 +24,7 @@ void main() { await _waitForReady(controller); await controller.saveAiGatewayConfiguration( - name: 'Direct AI', + name: 'Single Agent', baseUrl: 'https://api.example.com/v1', provider: 'openai-compatible', apiKey: 'sk-test-web', @@ -46,7 +46,7 @@ void main() { AssistantExecutionTarget.remote, ); await controller.createConversation( - target: AssistantExecutionTarget.aiGatewayOnly, + target: AssistantExecutionTarget.singleAgent, ); final reloaded = AppController( @@ -133,7 +133,7 @@ void main() { updatedAtMs: 1, title: 'stale browser cache', archived: false, - executionTarget: AssistantExecutionTarget.aiGatewayOnly, + executionTarget: AssistantExecutionTarget.singleAgent, messageViewMode: AssistantMessageViewMode.rendered, ), ]); diff --git a/web/index.html b/web/index.html index a2cfb23b..bade6f0a 100644 --- a/web/index.html +++ b/web/index.html @@ -20,7 +20,7 @@ diff --git a/web/manifest.json b/web/manifest.json index 5773a97a..e020c894 100644 --- a/web/manifest.json +++ b/web/manifest.json @@ -5,7 +5,7 @@ "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", - "description": "Assistant-first Flutter Web shell for Direct AI Gateway and Relay OpenClaw Gateway.", + "description": "Assistant-first Flutter Web shell for Single Agent and Relay OpenClaw Gateway.", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [