From c6021cedf4ff5ebbd0c4fcf4944447c45aa5a01b Mon Sep 17 00:00:00 2001 From: paisley <8197966+su8su@users.noreply.github.com> Date: Mon, 23 Mar 2026 18:00:35 +0800 Subject: [PATCH] feat(agents): add option to inherit main agent workspace when creating new agent (#639) --- electron/api/routes/agents.ts | 4 ++-- electron/utils/agent-config.ts | 21 ++++++++++++++++----- src/i18n/locales/en/agents.json | 8 +++++--- src/i18n/locales/ja/agents.json | 8 +++++--- src/i18n/locales/zh/agents.json | 8 +++++--- src/pages/Agents/index.tsx | 21 +++++++++++++++++---- src/stores/agents.ts | 6 +++--- 7 files changed, 53 insertions(+), 23 deletions(-) diff --git a/electron/api/routes/agents.ts b/electron/api/routes/agents.ts index 13f9823fa..dd0973959 100644 --- a/electron/api/routes/agents.ts +++ b/electron/api/routes/agents.ts @@ -117,8 +117,8 @@ export async function handleAgentRoutes( if (url.pathname === '/api/agents' && req.method === 'POST') { try { - const body = await parseJsonBody<{ name: string }>(req); - const snapshot = await createAgent(body.name); + const body = await parseJsonBody<{ name: string; inheritWorkspace?: boolean }>(req); + const snapshot = await createAgent(body.name, { inheritWorkspace: body.inheritWorkspace }); // Sync provider API keys to the new agent's auth-profiles.json so the // embedded runner can authenticate with LLM providers when messages // arrive via channel bots (e.g. Feishu). Without this, the copied diff --git a/electron/utils/agent-config.ts b/electron/utils/agent-config.ts index 3fdc7b9a6..a6c38c9b0 100644 --- a/electron/utils/agent-config.ts +++ b/electron/utils/agent-config.ts @@ -386,7 +386,11 @@ async function copyRuntimeFiles(sourceAgentDir: string, targetAgentDir: string): } } -async function provisionAgentFilesystem(config: AgentConfigDocument, agent: AgentListEntry): Promise { +async function provisionAgentFilesystem( + config: AgentConfigDocument, + agent: AgentListEntry, + options?: { inheritWorkspace?: boolean }, +): Promise { const { entries } = normalizeAgentsConfig(config); const mainEntry = entries.find((entry) => entry.id === MAIN_AGENT_ID) ?? createImplicitMainEntry(config); const sourceWorkspace = expandPath(mainEntry.workspace || getDefaultWorkspacePath(config)); @@ -399,7 +403,11 @@ async function provisionAgentFilesystem(config: AgentConfigDocument, agent: Agen await ensureDir(targetAgentDir); await ensureDir(targetSessionsDir); - if (targetWorkspace !== sourceWorkspace) { + // When inheritWorkspace is true, copy the main agent's workspace bootstrap + // files (SOUL.md, AGENTS.md, etc.) so the new agent inherits the same + // personality / instructions. When false (default), leave the workspace + // empty and let OpenClaw Gateway seed the default bootstrap files on startup. + if (options?.inheritWorkspace && targetWorkspace !== sourceWorkspace) { await copyBootstrapFiles(sourceWorkspace, targetWorkspace); } if (targetAgentDir !== sourceAgentDir) { @@ -521,7 +529,10 @@ export async function listConfiguredAgentIds(): Promise { return ids.length > 0 ? ids : [MAIN_AGENT_ID]; } -export async function createAgent(name: string): Promise { +export async function createAgent( + name: string, + options?: { inheritWorkspace?: boolean }, +): Promise { return withConfigLock(async () => { const config = await readOpenClawConfig() as AgentConfigDocument; const { agentsConfig, entries, syntheticMain } = normalizeAgentsConfig(config); @@ -554,9 +565,9 @@ export async function createAgent(name: string): Promise { list: nextEntries, }; - await provisionAgentFilesystem(config, newAgent); + await provisionAgentFilesystem(config, newAgent, { inheritWorkspace: options?.inheritWorkspace }); await writeOpenClawConfig(config); - logger.info('Created agent config entry', { agentId: nextId }); + logger.info('Created agent config entry', { agentId: nextId, inheritWorkspace: !!options?.inheritWorkspace }); return buildSnapshotFromConfig(config); }); } diff --git a/src/i18n/locales/en/agents.json b/src/i18n/locales/en/agents.json index f5abab7c0..ba9be5360 100644 --- a/src/i18n/locales/en/agents.json +++ b/src/i18n/locales/en/agents.json @@ -1,6 +1,6 @@ { "title": "Agents", - "subtitle": "When adding a new Agent, ClawX will copy the main Agent's workspace files and runtime auth setup. The configuration can be modified through a dialog.", + "subtitle": "Create a new Agent to route specific channels to a separate personality or workspace.", "refresh": "Refresh", "addAgent": "Add Agent", "gatewayWarning": "Gateway service is not running. Agent/channel changes may take a moment to apply.", @@ -14,9 +14,11 @@ "creating": "Creating...", "createDialog": { "title": "Add Agent", - "description": "Create a new agent by name. ClawX will copy the main agent's workspace bootstrap files and runtime auth setup.", + "description": "Create a new agent by name. You can optionally inherit the main agent's workspace bootstrap files.", "nameLabel": "Agent Name", - "namePlaceholder": "Coding Helper" + "namePlaceholder": "Coding Helper", + "inheritWorkspaceLabel": "Inherit main agent workspace", + "inheritWorkspaceDescription": "Copy SOUL.md, AGENTS.md, etc. from the main agent" }, "deleteDialog": { "title": "Delete Agent", diff --git a/src/i18n/locales/ja/agents.json b/src/i18n/locales/ja/agents.json index 0bd89e91b..cbfcc1df0 100644 --- a/src/i18n/locales/ja/agents.json +++ b/src/i18n/locales/ja/agents.json @@ -1,6 +1,6 @@ { "title": "Agents", - "subtitle": "新しい Agent を追加すると、ClawX はメイン Agent のワークスペース初期ファイルと実行時認証設定をコピーします。これらの設定は対話形式で編集できます", + "subtitle": "新しい Agent を作成し、特定のチャンネルを異なるパーソナリティやワークスペースにルーティングできます。", "refresh": "更新", "addAgent": "Agent を追加", "gatewayWarning": "Gateway サービスが停止しています。Agent または Channel の変更が反映されるまで少し時間がかかる場合があります。", @@ -14,9 +14,11 @@ "creating": "作成中...", "createDialog": { "title": "Agent を追加", - "description": "名前だけで新しい Agent を作成できます。ClawX はメイン Agent のワークスペース初期ファイルと認証設定をコピーします。", + "description": "名前だけで新しい Agent を作成できます。メイン Agent のワークスペース初期ファイルを引き継ぐかどうかも選択できます。", "nameLabel": "Agent 名", - "namePlaceholder": "Coding Helper" + "namePlaceholder": "Coding Helper", + "inheritWorkspaceLabel": "メイン Agent のワークスペースを引き継ぐ", + "inheritWorkspaceDescription": "SOUL.md、AGENTS.md などの初期ファイルをコピーします" }, "deleteDialog": { "title": "Agent を削除", diff --git a/src/i18n/locales/zh/agents.json b/src/i18n/locales/zh/agents.json index 8212029bb..d1347aa92 100644 --- a/src/i18n/locales/zh/agents.json +++ b/src/i18n/locales/zh/agents.json @@ -1,6 +1,6 @@ { "title": "Agents", - "subtitle": "添加新的 Agent 时,ClawX 会复制主 Agent 的工作区引导文件和运行时认证配置, 配置可以通过以对话的形式进行修改", + "subtitle": "创建新的 Agent,可以将特定频道路由到不同的人格配置或工作区。", "refresh": "刷新", "addAgent": "添加 Agent", "gatewayWarning": "Gateway 服务未运行。Agent 或频道变更可能需要一点时间生效。", @@ -14,9 +14,11 @@ "creating": "创建中...", "createDialog": { "title": "添加 Agent", - "description": "只需输入名称即可创建新 Agent。ClawX 会复制主 Agent 的工作区引导文件和运行时认证配置。", + "description": "输入名称即可创建新 Agent,可选择是否继承主 Agent 的工作区引导文件。", "nameLabel": "Agent 名称", - "namePlaceholder": "Coding Helper" + "namePlaceholder": "Coding Helper", + "inheritWorkspaceLabel": "继承主 Agent 工作区", + "inheritWorkspaceDescription": "从主 Agent 复制 SOUL.md、AGENTS.md 等引导文件" }, "deleteDialog": { "title": "删除 Agent", diff --git a/src/pages/Agents/index.tsx b/src/pages/Agents/index.tsx index fa8a242b2..52f718877 100644 --- a/src/pages/Agents/index.tsx +++ b/src/pages/Agents/index.tsx @@ -6,6 +6,7 @@ import { Label } from '@/components/ui/label'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { ConfirmDialog } from '@/components/ui/confirm-dialog'; +import { Switch } from '@/components/ui/switch'; import { LoadingSpinner } from '@/components/common/LoadingSpinner'; import { useAgentsStore } from '@/stores/agents'; import { useGatewayStore } from '@/stores/gateway'; @@ -179,8 +180,8 @@ export function Agents() { {showAddDialog && ( setShowAddDialog(false)} - onCreate={async (name) => { - await createAgent(name); + onCreate={async (name, options) => { + await createAgent(name, options); setShowAddDialog(false); toast.success(t('toast.agentCreated')); }} @@ -345,17 +346,18 @@ function AddAgentDialog({ onCreate, }: { onClose: () => void; - onCreate: (name: string) => Promise; + onCreate: (name: string, options: { inheritWorkspace: boolean }) => Promise; }) { const { t } = useTranslation('agents'); const [name, setName] = useState(''); + const [inheritWorkspace, setInheritWorkspace] = useState(false); const [saving, setSaving] = useState(false); const handleSubmit = async () => { if (!name.trim()) return; setSaving(true); try { - await onCreate(name.trim()); + await onCreate(name.trim(), { inheritWorkspace }); } catch (error) { toast.error(t('toast.agentCreateFailed', { error: String(error) })); setSaving(false); @@ -386,6 +388,17 @@ function AddAgentDialog({ className={inputClasses} /> +
+
+ +

{t('createDialog.inheritWorkspaceDescription')}

+
+ +