Clean repo skills config and support custom ACP endpoints

This commit is contained in:
Haitao Pan 2026-03-26 11:59:28 +08:00
parent 068312394c
commit 6aa87e2ff5
9 changed files with 86 additions and 65 deletions

View File

@ -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.
"""

View File

@ -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.

View File

@ -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

View File

@ -914,8 +914,8 @@ class _SettingsPageState extends State<SettingsPage> {
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<SettingsPage> {
_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<SettingsPage> {
),
),
_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<SettingsPage> {
...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,

View File

@ -262,6 +262,8 @@ const List<SingleAgentProvider> kKnownSingleAgentProviders =
SingleAgentProvider.gemini,
];
const Set<String> kLegacyExternalAcpProviderIds = <String>{'claude', 'gemini'};
class ExternalAcpEndpointProfile {
const ExternalAcpEndpointProfile({
required this.providerKey,
@ -369,11 +371,29 @@ List<ExternalAcpEndpointProfile> normalizeExternalAcpEndpoints({
final incoming =
profiles?.toList(growable: false) ?? const <ExternalAcpEndpointProfile>[];
final byKey = <String, ExternalAcpEndpointProfile>{};
final migratedCustomProfiles = <ExternalAcpEndpointProfile>[];
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<ExternalAcpEndpointProfile> normalizeExternalAcpEndpoints({
for (final provider in kBuiltinExternalAcpProviders)
byKey.remove(provider.providerId) ??
ExternalAcpEndpointProfile.defaultsForProvider(provider),
...migratedCustomProfiles,
...byKey.values,
];
return List<ExternalAcpEndpointProfile>.unmodifiable(normalized);

View File

@ -926,8 +926,8 @@ class _WebSettingsPageState extends State<WebSettingsPage> {
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<WebSettingsPage> {
);
},
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<WebSettingsPage> {
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<WebSettingsPage> {
...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,

View File

@ -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);

View File

@ -62,5 +62,40 @@ void main() {
isTrue,
);
});
test('legacy claude and gemini entries migrate into custom endpoints', () {
final normalized = normalizeExternalAcpEndpoints(
profiles: const <ExternalAcpEndpointProfile>[
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 <String>['codex', 'opencode'],
);
expect(
normalized
.where((item) => item.providerKey.startsWith('custom-agent-'))
.map((item) => item.label)
.toList(growable: false),
const <String>['Claude', 'Gemini'],
);
expect(normalized.any((item) => item.providerKey == 'claude'), isFalse);
expect(normalized.any((item) => item.providerKey == 'gemini'), isFalse);
});
});
}

View File

@ -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);
});