From 6cd5e3e534419d4fd59476938888a0978e528913 Mon Sep 17 00:00:00 2001 From: nyk <93952610+0xNyk@users.noreply.github.com> Date: Tue, 3 Mar 2026 21:07:02 +0700 Subject: [PATCH] feat: enrich agent config with workspace identity and tools --- src/app/api/agents/[id]/route.ts | 8 +-- src/app/api/agents/route.ts | 6 +-- src/lib/agent-sync.ts | 93 ++++++++++++++++++++++++++++++-- 3 files changed, 98 insertions(+), 9 deletions(-) diff --git a/src/app/api/agents/[id]/route.ts b/src/app/api/agents/[id]/route.ts index 9f7f477..00dfd46 100644 --- a/src/app/api/agents/[id]/route.ts +++ b/src/app/api/agents/[id]/route.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse } from 'next/server' import { getDatabase, db_helpers, logAuditEvent } from '@/lib/db' import { getUserFromRequest, requireRole } from '@/lib/auth' -import { writeAgentToConfig } from '@/lib/agent-sync' +import { writeAgentToConfig, enrichAgentConfigFromWorkspace } from '@/lib/agent-sync' import { eventBus } from '@/lib/event-bus' import { logger } from '@/lib/logger' @@ -32,7 +32,7 @@ export async function GET( const parsed = { ...(agent as any), - config: (agent as any).config ? JSON.parse((agent as any).config) : {}, + config: enrichAgentConfigFromWorkspace((agent as any).config ? JSON.parse((agent as any).config) : {}), } return NextResponse.json({ agent: parsed }) @@ -154,9 +154,11 @@ export async function PUT( updated_at: now, }) + const enrichedConfig = enrichAgentConfigFromWorkspace(newConfig) + return NextResponse.json({ success: true, - agent: { ...agent, config: newConfig, role: role || agent.role, updated_at: now }, + agent: { ...agent, config: enrichedConfig, role: role || agent.role, updated_at: now }, }) } catch (error: any) { logger.error({ err: error }, 'PUT /api/agents/[id] error') diff --git a/src/app/api/agents/route.ts b/src/app/api/agents/route.ts index 168b591..a42d780 100644 --- a/src/app/api/agents/route.ts +++ b/src/app/api/agents/route.ts @@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { getDatabase, Agent, db_helpers } from '@/lib/db'; import { eventBus } from '@/lib/event-bus'; import { getTemplate, buildAgentConfig } from '@/lib/agent-templates'; -import { writeAgentToConfig } from '@/lib/agent-sync'; +import { writeAgentToConfig, enrichAgentConfigFromWorkspace } from '@/lib/agent-sync'; import { logAuditEvent } from '@/lib/db'; import { getUserFromRequest, requireRole } from '@/lib/auth'; import { mutationLimiter } from '@/lib/rate-limit'; @@ -50,7 +50,7 @@ export async function GET(request: NextRequest) { // Parse JSON config field const agentsWithParsedData = agents.map(agent => ({ ...agent, - config: agent.config ? JSON.parse(agent.config) : {} + config: enrichAgentConfigFromWorkspace(agent.config ? JSON.parse(agent.config) : {}) })); // Get task counts for each agent (prepare once, reuse per agent) @@ -357,4 +357,4 @@ export async function PUT(request: NextRequest) { logger.error({ err: error }, 'PUT /api/agents error'); return NextResponse.json({ error: 'Failed to update agent' }, { status: 500 }); } -} \ No newline at end of file +} diff --git a/src/lib/agent-sync.ts b/src/lib/agent-sync.ts index d149628..57a91ae 100644 --- a/src/lib/agent-sync.ts +++ b/src/lib/agent-sync.ts @@ -62,6 +62,70 @@ export interface SyncDiff { onlyInMC: string[] } +function parseIdentityFromFile(content: string): { name?: string; theme?: string; emoji?: string; content?: string } { + if (!content.trim()) return {} + const lines = content.split('\n').map((line) => line.trim()).filter(Boolean) + let name: string | undefined + let theme: string | undefined + let emoji: string | undefined + + for (const line of lines) { + if (!name && line.startsWith('#')) { + name = line.replace(/^#+\s*/, '').trim() + continue + } + + if (!theme) { + const themeMatch = line.match(/^theme\s*:\s*(.+)$/i) + if (themeMatch?.[1]) { + theme = themeMatch[1].trim() + continue + } + } + + if (!emoji) { + const emojiMatch = line.match(/^emoji\s*:\s*(.+)$/i) + if (emojiMatch?.[1]) { + emoji = emojiMatch[1].trim() + } + } + } + + return { + ...(name ? { name } : {}), + ...(theme ? { theme } : {}), + ...(emoji ? { emoji } : {}), + content: lines.slice(0, 8).join('\n'), + } +} + +function parseToolsFromFile(content: string): { allow?: string[]; raw?: string } { + if (!content.trim()) return {} + + const parsedTools = new Set() + for (const line of content.split('\n')) { + const cleaned = line.trim() + if (!cleaned || cleaned.startsWith('#')) continue + + const listMatch = cleaned.match(/^[-*]\s+`?([^`]+?)`?\s*$/) + if (listMatch?.[1]) { + parsedTools.add(listMatch[1].trim()) + continue + } + + const inlineMatch = cleaned.match(/^`([^`]+)`$/) + if (inlineMatch?.[1]) { + parsedTools.add(inlineMatch[1].trim()) + } + } + + const allow = [...parsedTools].filter(Boolean) + return { + ...(allow.length > 0 ? { allow } : {}), + raw: content.split('\n').map((line) => line.trim()).filter(Boolean).slice(0, 24).join('\n'), + } +} + function getConfigPath(): string | null { if (!config.openclawHome) return null return join(config.openclawHome, 'openclaw.json') @@ -82,6 +146,30 @@ function readWorkspaceFile(workspace: string | undefined, filename: string): str return null } +export function enrichAgentConfigFromWorkspace(configData: any): any { + if (!configData || typeof configData !== 'object') return configData + const workspace = typeof configData.workspace === 'string' ? configData.workspace : undefined + if (!workspace) return configData + + const identityFile = readWorkspaceFile(workspace, 'identity.md') + const toolsFile = readWorkspaceFile(workspace, 'TOOLS.md') + + const mergedIdentity = { + ...parseIdentityFromFile(identityFile || ''), + ...((configData.identity && typeof configData.identity === 'object') ? configData.identity : {}), + } + const mergedTools = { + ...parseToolsFromFile(toolsFile || ''), + ...((configData.tools && typeof configData.tools === 'object') ? configData.tools : {}), + } + + return { + ...configData, + identity: Object.keys(mergedIdentity).length > 0 ? mergedIdentity : configData.identity, + tools: Object.keys(mergedTools).length > 0 ? mergedTools : configData.tools, + } +} + /** Read and parse openclaw.json agents list */ async function readOpenClawAgents(): Promise { const configPath = getConfigPath() @@ -102,9 +190,8 @@ function mapAgentToMC(agent: OpenClawAgent): { } { const name = agent.identity?.name || agent.name || agent.id const role = agent.identity?.theme || 'agent' - // Store the full config minus systemPrompt/soul (which can be large) - const configData = { + const configData = enrichAgentConfigFromWorkspace({ openclawId: agent.id, model: agent.model, identity: agent.identity, @@ -115,7 +202,7 @@ function mapAgentToMC(agent: OpenClawAgent): { workspace: agent.workspace, agentDir: agent.agentDir, isDefault: agent.default || false, - } + }) // Read soul.md from the agent's workspace if available const soul_content = readWorkspaceFile(agent.workspace, 'soul.md')