From 6aa87e2ff548305b0dd7eaf05174c7c5cd86d6bc Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Thu, 26 Mar 2026 11:59:28 +0800 Subject: [PATCH] Clean repo skills config and support custom ACP endpoints --- .codex/config.toml | 8 ---- .../xworkmate-worktree-task-mode/SKILL.md | 43 ------------------- AGENTS.md | 4 +- lib/features/settings/settings_page.dart | 18 +++++--- lib/runtime/runtime_models.dart | 21 +++++++++ lib/web/web_settings_page.dart | 18 +++++--- test/features/settings_page_suite.dart | 2 +- .../external_acp_endpoint_settings_suite.dart | 35 +++++++++++++++ test/web/web_ui_browser_test.dart | 2 +- 9 files changed, 86 insertions(+), 65 deletions(-) delete mode 100644 .codex/config.toml delete mode 100644 .codex/skills/xworkmate-worktree-task-mode/SKILL.md diff --git a/.codex/config.toml b/.codex/config.toml deleted file mode 100644 index 2d9c7822..00000000 --- a/.codex/config.toml +++ /dev/null @@ -1,8 +0,0 @@ -developer_instructions = """ -For this repository, the default execution mode is worktree-first and concurrency-friendly. - -- For non-trivial implementation or debugging tasks, start from `main`, create a temporary git worktree, and do the task in that isolated worktree. -- When tasks have independent side work, split them into concurrent lanes while keeping the main agent on the critical path. -- When the task is complete, verify the change, merge back to `main`, and clean up the temporary worktree. -- Do not repeatedly ask the user to re-enable this workflow for this repository unless they explicitly override it. -""" diff --git a/.codex/skills/xworkmate-worktree-task-mode/SKILL.md b/.codex/skills/xworkmate-worktree-task-mode/SKILL.md deleted file mode 100644 index 52cf7319..00000000 --- a/.codex/skills/xworkmate-worktree-task-mode/SKILL.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -name: xworkmate-worktree-task-mode -description: Mandatory XWorkmate task execution mode: every task starts in an isolated git worktree, implementation is verified and committed there, then merged back into main and cleaned up. ---- - -# XWorkmate Worktree Task Mode - -Use this skill as the required execution path for work in this repository unless the user explicitly asks for a different flow. - -## Goals - -- Keep `main` clean at all times. -- Start every task from a temporary worktree created from `main`. -- Finish coding, verification, and commit(s) inside that worktree. -- Return to `main`, merge the finished worktree branch, then remove the temporary worktree. - -## Required Flow - -Every task follows this sequence: - -1. Inspect the current repo state from the main checkout. -2. Create a temporary branch and `git worktree` from `main`. -3. Do all coding work inside the worktree. -4. Run the required verification inside the worktree. -5. Commit the task result inside the worktree. -6. Return to the main checkout and switch to `main`. -7. Merge the worktree branch into `main`. -8. If the task completed successfully, remove the temporary worktree and delete the temporary branch. - -## Guardrails - -- Do not skip the worktree step because the task seems small. The default is still to start in a worktree. -- Do not ask the user to re-confirm this mode on each task. -- Do not merge unverified work back into `main`. -- Do not leave temporary worktrees behind after a successful task unless the user explicitly asks to keep them. -- Preserve user changes and do not revert unrelated work. - -## Operational Notes - -- The worktree branch should be created from `main`, not from the current feature branch. -- Verification should match the task scope, but it must happen before the final merge. -- The final integration step is not complete until the worktree branch is merged into `main`. -- Successful completion means the full lifecycle is closed: worktree created, changes implemented, verification run, commit created, merged into `main`, worktree cleaned up. diff --git a/AGENTS.md b/AGENTS.md index e74cc656..fe67c5eb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,8 +1,8 @@ ## Skills -- Use `xworkmate-secure-development` for any change that touches gateway auth, `.env`, secure storage, tokens, passwords, TLS, file upload, native entitlements, packaging, or release-sensitive settings. - Use `xworkmate-acceptance` before claiming build, packaging, installation, or release readiness for this repo. -- For non-trivial implementation work, default to the repo skill `xworkmate-worktree-task-mode` and follow its worktree-first execution flow without asking the user to restate that preference each time. +- For any change that touches gateway auth, `.env`, secure storage, tokens, passwords, TLS, file upload, native entitlements, packaging, or release-sensitive settings, follow the security rules in this file and [docs/security/secure-development-rules.md](/Users/shenlan/workspaces/cloud-neutral-toolkit/XWorkmate.svc.plus/docs/security/secure-development-rules.md). +- For non-trivial implementation work, default to the worktree-first execution flow in this file without asking the user to restate that preference each time. ## Default Task Mode diff --git a/lib/features/settings/settings_page.dart b/lib/features/settings/settings_page.dart index f0747e86..5cda36a4 100644 --- a/lib/features/settings/settings_page.dart +++ b/lib/features/settings/settings_page.dart @@ -914,8 +914,8 @@ class _SettingsPageState extends State { const SizedBox(height: 8), Text( appText( - '预设保留 Codex、OpenCode;其余 provider 通过“自定义添加更多”扩展。每条记录可自定义显示名称和 ACP Server Endpoint,协议支持 ws / wss / http / https。', - 'Codex and OpenCode stay as presets. Add more custom providers as needed. Each entry can define its own display name and ACP server endpoint with ws / wss / http / https.', + '这里仅保留 Codex、OpenCode 预设接入。历史上的 Claude / Gemini 预配置会迁移为自定义 ACP Server Endpoint。你可以继续添加多个自定义 Endpoint,协议支持 ws / wss / http / https。', + 'Only Codex and OpenCode stay as preset integrations here. Legacy Claude and Gemini entries are migrated into custom ACP server endpoints. You can add multiple custom endpoints with ws / wss / http / https.', ), style: theme.textTheme.bodyMedium, ), @@ -929,7 +929,12 @@ class _SettingsPageState extends State { _appendExternalAcpProvider(settings), ), icon: const Icon(Icons.add_rounded), - label: Text(appText('自定义添加更多', 'Add more custom providers')), + label: Text( + appText( + '添加自定义 ACP Server Endpoint', + 'Add custom ACP server endpoint', + ), + ), ), ), const SizedBox(height: 16), @@ -1013,7 +1018,7 @@ class _SettingsPageState extends State { ), ), _EditableField( - label: appText('ACP Endpoint', 'ACP Endpoint'), + label: appText('ACP Server Endpoint', 'ACP Server Endpoint'), value: endpoint, onSubmitted: (value) => _saveSettings( controller, @@ -1049,7 +1054,10 @@ class _SettingsPageState extends State { ...settings.externalAcpEndpoints, ExternalAcpEndpointProfile( providerKey: providerKey(), - label: appText('自定义添加更多 $suffix', 'Custom Add More $suffix'), + label: appText( + '自定义 ACP Endpoint $suffix', + 'Custom ACP Endpoint $suffix', + ), badge: '', endpoint: '', enabled: true, diff --git a/lib/runtime/runtime_models.dart b/lib/runtime/runtime_models.dart index b38d870c..beeed64c 100644 --- a/lib/runtime/runtime_models.dart +++ b/lib/runtime/runtime_models.dart @@ -262,6 +262,8 @@ const List kKnownSingleAgentProviders = SingleAgentProvider.gemini, ]; +const Set kLegacyExternalAcpProviderIds = {'claude', 'gemini'}; + class ExternalAcpEndpointProfile { const ExternalAcpEndpointProfile({ required this.providerKey, @@ -369,11 +371,29 @@ List normalizeExternalAcpEndpoints({ final incoming = profiles?.toList(growable: false) ?? const []; final byKey = {}; + final migratedCustomProfiles = []; + var customSuffix = 1; + + String nextCustomKey() { + while (true) { + final key = 'custom-agent-$customSuffix'; + customSuffix += 1; + if (!byKey.containsKey(key) && + !migratedCustomProfiles.any((item) => item.providerKey == key)) { + return key; + } + } + } + for (final item in incoming) { final key = item.providerKey.trim().toLowerCase(); if (key.isEmpty || byKey.containsKey(key)) { continue; } + if (kLegacyExternalAcpProviderIds.contains(key)) { + migratedCustomProfiles.add(item.copyWith(providerKey: nextCustomKey())); + continue; + } byKey[key] = item.copyWith(providerKey: key); } @@ -381,6 +401,7 @@ List normalizeExternalAcpEndpoints({ for (final provider in kBuiltinExternalAcpProviders) byKey.remove(provider.providerId) ?? ExternalAcpEndpointProfile.defaultsForProvider(provider), + ...migratedCustomProfiles, ...byKey.values, ]; return List.unmodifiable(normalized); diff --git a/lib/web/web_settings_page.dart b/lib/web/web_settings_page.dart index c696f81d..4b653796 100644 --- a/lib/web/web_settings_page.dart +++ b/lib/web/web_settings_page.dart @@ -926,8 +926,8 @@ class _WebSettingsPageState extends State { const SizedBox(height: 8), Text( appText( - '预设保留 Codex、OpenCode;其余 provider 通过“自定义添加更多”扩展。每条记录可自定义显示名称和 ACP Server Endpoint,协议支持 ws / wss / http / https。', - 'Codex and OpenCode stay as presets. Add more custom providers as needed. Each entry can define its own display name and ACP server endpoint with ws / wss / http / https.', + '这里仅保留 Codex、OpenCode 预设接入。历史上的 Claude / Gemini 预配置会迁移为自定义 ACP Server Endpoint。你可以继续添加多个自定义 Endpoint,协议支持 ws / wss / http / https。', + 'Only Codex and OpenCode stay as preset integrations here. Legacy Claude and Gemini entries are migrated into custom ACP server endpoints. You can add multiple custom endpoints with ws / wss / http / https.', ), style: theme.textTheme.bodyMedium, ), @@ -944,7 +944,12 @@ class _WebSettingsPageState extends State { ); }, icon: const Icon(Icons.add_rounded), - label: Text(appText('自定义添加更多', 'Add more custom providers')), + label: Text( + appText( + '添加自定义 ACP Server Endpoint', + 'Add custom ACP server endpoint', + ), + ), ), ), const SizedBox(height: 16), @@ -1029,7 +1034,7 @@ class _WebSettingsPageState extends State { TextField( controller: endpointController, decoration: InputDecoration( - labelText: appText('ACP Endpoint', 'ACP Endpoint'), + labelText: appText('ACP Server Endpoint', 'ACP Server Endpoint'), ), onChanged: (_) => _stageExternalAcpDraft(controller), ), @@ -1092,7 +1097,10 @@ class _WebSettingsPageState extends State { ...settings.externalAcpEndpoints, ExternalAcpEndpointProfile( providerKey: providerKey(), - label: appText('自定义 Provider $suffix', 'Custom Provider $suffix'), + label: appText( + '自定义 ACP Endpoint $suffix', + 'Custom ACP Endpoint $suffix', + ), badge: '$suffix', endpoint: '', enabled: true, diff --git a/test/features/settings_page_suite.dart b/test/features/settings_page_suite.dart index bb2e2cf5..8a5ec9c7 100644 --- a/test/features/settings_page_suite.dart +++ b/test/features/settings_page_suite.dart @@ -309,7 +309,7 @@ void main() { find.byKey(const ValueKey('external-acp-provider-add-button')), findsOneWidget, ); - expect(find.text('自定义添加更多'), findsOneWidget); + expect(find.text('添加自定义 ACP Server Endpoint'), findsOneWidget); expect(find.textContaining('ws://127.0.0.1:9001'), findsWidgets); expect(find.text('标志'), findsNothing); expect(find.text('Badge'), findsNothing); diff --git a/test/runtime/external_acp_endpoint_settings_suite.dart b/test/runtime/external_acp_endpoint_settings_suite.dart index 62b0ef4a..5cd7aa1d 100644 --- a/test/runtime/external_acp_endpoint_settings_suite.dart +++ b/test/runtime/external_acp_endpoint_settings_suite.dart @@ -62,5 +62,40 @@ void main() { isTrue, ); }); + + test('legacy claude and gemini entries migrate into custom endpoints', () { + final normalized = normalizeExternalAcpEndpoints( + profiles: const [ + ExternalAcpEndpointProfile( + providerKey: 'claude', + label: 'Claude', + badge: 'Cl', + endpoint: 'ws://127.0.0.1:9011', + enabled: true, + ), + ExternalAcpEndpointProfile( + providerKey: 'gemini', + label: 'Gemini', + badge: 'G', + endpoint: 'ws://127.0.0.1:9012', + enabled: true, + ), + ], + ); + + expect( + normalized.take(2).map((item) => item.providerKey).toList(), + const ['codex', 'opencode'], + ); + expect( + normalized + .where((item) => item.providerKey.startsWith('custom-agent-')) + .map((item) => item.label) + .toList(growable: false), + const ['Claude', 'Gemini'], + ); + expect(normalized.any((item) => item.providerKey == 'claude'), isFalse); + expect(normalized.any((item) => item.providerKey == 'gemini'), isFalse); + }); }); } diff --git a/test/web/web_ui_browser_test.dart b/test/web/web_ui_browser_test.dart index 3fd3bf7f..46b2bac2 100644 --- a/test/web/web_ui_browser_test.dart +++ b/test/web/web_ui_browser_test.dart @@ -178,7 +178,7 @@ void main() { find.byKey(const ValueKey('web-external-acp-provider-add-button')), findsOneWidget, ); - expect(find.text('自定义添加更多'), findsOneWidget); + expect(find.text('添加自定义 ACP Server Endpoint'), findsOneWidget); expect(find.text('标志'), findsNothing); expect(find.text('Badge'), findsNothing); });