nyk commited on
Commit
5be7cdc
·
unverified ·
1 Parent(s): c630ac6

feat: enrich agent config with workspace identity and tools

Browse files
src/app/api/agents/[id]/route.ts CHANGED
@@ -1,7 +1,7 @@
1
  import { NextRequest, NextResponse } from 'next/server'
2
  import { getDatabase, db_helpers, logAuditEvent } from '@/lib/db'
3
  import { getUserFromRequest, requireRole } from '@/lib/auth'
4
- import { writeAgentToConfig } from '@/lib/agent-sync'
5
  import { eventBus } from '@/lib/event-bus'
6
  import { logger } from '@/lib/logger'
7
 
@@ -32,7 +32,7 @@ export async function GET(
32
 
33
  const parsed = {
34
  ...(agent as any),
35
- config: (agent as any).config ? JSON.parse((agent as any).config) : {},
36
  }
37
 
38
  return NextResponse.json({ agent: parsed })
@@ -154,9 +154,11 @@ export async function PUT(
154
  updated_at: now,
155
  })
156
 
 
 
157
  return NextResponse.json({
158
  success: true,
159
- agent: { ...agent, config: newConfig, role: role || agent.role, updated_at: now },
160
  })
161
  } catch (error: any) {
162
  logger.error({ err: error }, 'PUT /api/agents/[id] error')
 
1
  import { NextRequest, NextResponse } from 'next/server'
2
  import { getDatabase, db_helpers, logAuditEvent } from '@/lib/db'
3
  import { getUserFromRequest, requireRole } from '@/lib/auth'
4
+ import { writeAgentToConfig, enrichAgentConfigFromWorkspace } from '@/lib/agent-sync'
5
  import { eventBus } from '@/lib/event-bus'
6
  import { logger } from '@/lib/logger'
7
 
 
32
 
33
  const parsed = {
34
  ...(agent as any),
35
+ config: enrichAgentConfigFromWorkspace((agent as any).config ? JSON.parse((agent as any).config) : {}),
36
  }
37
 
38
  return NextResponse.json({ agent: parsed })
 
154
  updated_at: now,
155
  })
156
 
157
+ const enrichedConfig = enrichAgentConfigFromWorkspace(newConfig)
158
+
159
  return NextResponse.json({
160
  success: true,
161
+ agent: { ...agent, config: enrichedConfig, role: role || agent.role, updated_at: now },
162
  })
163
  } catch (error: any) {
164
  logger.error({ err: error }, 'PUT /api/agents/[id] error')
src/app/api/agents/route.ts CHANGED
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
2
  import { getDatabase, Agent, db_helpers } from '@/lib/db';
3
  import { eventBus } from '@/lib/event-bus';
4
  import { getTemplate, buildAgentConfig } from '@/lib/agent-templates';
5
- import { writeAgentToConfig } from '@/lib/agent-sync';
6
  import { logAuditEvent } from '@/lib/db';
7
  import { getUserFromRequest, requireRole } from '@/lib/auth';
8
  import { mutationLimiter } from '@/lib/rate-limit';
@@ -50,7 +50,7 @@ export async function GET(request: NextRequest) {
50
  // Parse JSON config field
51
  const agentsWithParsedData = agents.map(agent => ({
52
  ...agent,
53
- config: agent.config ? JSON.parse(agent.config) : {}
54
  }));
55
 
56
  // Get task counts for each agent (prepare once, reuse per agent)
@@ -357,4 +357,4 @@ export async function PUT(request: NextRequest) {
357
  logger.error({ err: error }, 'PUT /api/agents error');
358
  return NextResponse.json({ error: 'Failed to update agent' }, { status: 500 });
359
  }
360
- }
 
2
  import { getDatabase, Agent, db_helpers } from '@/lib/db';
3
  import { eventBus } from '@/lib/event-bus';
4
  import { getTemplate, buildAgentConfig } from '@/lib/agent-templates';
5
+ import { writeAgentToConfig, enrichAgentConfigFromWorkspace } from '@/lib/agent-sync';
6
  import { logAuditEvent } from '@/lib/db';
7
  import { getUserFromRequest, requireRole } from '@/lib/auth';
8
  import { mutationLimiter } from '@/lib/rate-limit';
 
50
  // Parse JSON config field
51
  const agentsWithParsedData = agents.map(agent => ({
52
  ...agent,
53
+ config: enrichAgentConfigFromWorkspace(agent.config ? JSON.parse(agent.config) : {})
54
  }));
55
 
56
  // Get task counts for each agent (prepare once, reuse per agent)
 
357
  logger.error({ err: error }, 'PUT /api/agents error');
358
  return NextResponse.json({ error: 'Failed to update agent' }, { status: 500 });
359
  }
360
+ }
src/lib/agent-sync.ts CHANGED
@@ -62,6 +62,70 @@ export interface SyncDiff {
62
  onlyInMC: string[]
63
  }
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  function getConfigPath(): string | null {
66
  if (!config.openclawHome) return null
67
  return join(config.openclawHome, 'openclaw.json')
@@ -82,6 +146,30 @@ function readWorkspaceFile(workspace: string | undefined, filename: string): str
82
  return null
83
  }
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  /** Read and parse openclaw.json agents list */
86
  async function readOpenClawAgents(): Promise<OpenClawAgent[]> {
87
  const configPath = getConfigPath()
@@ -102,9 +190,8 @@ function mapAgentToMC(agent: OpenClawAgent): {
102
  } {
103
  const name = agent.identity?.name || agent.name || agent.id
104
  const role = agent.identity?.theme || 'agent'
105
-
106
  // Store the full config minus systemPrompt/soul (which can be large)
107
- const configData = {
108
  openclawId: agent.id,
109
  model: agent.model,
110
  identity: agent.identity,
@@ -115,7 +202,7 @@ function mapAgentToMC(agent: OpenClawAgent): {
115
  workspace: agent.workspace,
116
  agentDir: agent.agentDir,
117
  isDefault: agent.default || false,
118
- }
119
 
120
  // Read soul.md from the agent's workspace if available
121
  const soul_content = readWorkspaceFile(agent.workspace, 'soul.md')
 
62
  onlyInMC: string[]
63
  }
64
 
65
+ function parseIdentityFromFile(content: string): { name?: string; theme?: string; emoji?: string; content?: string } {
66
+ if (!content.trim()) return {}
67
+ const lines = content.split('\n').map((line) => line.trim()).filter(Boolean)
68
+ let name: string | undefined
69
+ let theme: string | undefined
70
+ let emoji: string | undefined
71
+
72
+ for (const line of lines) {
73
+ if (!name && line.startsWith('#')) {
74
+ name = line.replace(/^#+\s*/, '').trim()
75
+ continue
76
+ }
77
+
78
+ if (!theme) {
79
+ const themeMatch = line.match(/^theme\s*:\s*(.+)$/i)
80
+ if (themeMatch?.[1]) {
81
+ theme = themeMatch[1].trim()
82
+ continue
83
+ }
84
+ }
85
+
86
+ if (!emoji) {
87
+ const emojiMatch = line.match(/^emoji\s*:\s*(.+)$/i)
88
+ if (emojiMatch?.[1]) {
89
+ emoji = emojiMatch[1].trim()
90
+ }
91
+ }
92
+ }
93
+
94
+ return {
95
+ ...(name ? { name } : {}),
96
+ ...(theme ? { theme } : {}),
97
+ ...(emoji ? { emoji } : {}),
98
+ content: lines.slice(0, 8).join('\n'),
99
+ }
100
+ }
101
+
102
+ function parseToolsFromFile(content: string): { allow?: string[]; raw?: string } {
103
+ if (!content.trim()) return {}
104
+
105
+ const parsedTools = new Set<string>()
106
+ for (const line of content.split('\n')) {
107
+ const cleaned = line.trim()
108
+ if (!cleaned || cleaned.startsWith('#')) continue
109
+
110
+ const listMatch = cleaned.match(/^[-*]\s+`?([^`]+?)`?\s*$/)
111
+ if (listMatch?.[1]) {
112
+ parsedTools.add(listMatch[1].trim())
113
+ continue
114
+ }
115
+
116
+ const inlineMatch = cleaned.match(/^`([^`]+)`$/)
117
+ if (inlineMatch?.[1]) {
118
+ parsedTools.add(inlineMatch[1].trim())
119
+ }
120
+ }
121
+
122
+ const allow = [...parsedTools].filter(Boolean)
123
+ return {
124
+ ...(allow.length > 0 ? { allow } : {}),
125
+ raw: content.split('\n').map((line) => line.trim()).filter(Boolean).slice(0, 24).join('\n'),
126
+ }
127
+ }
128
+
129
  function getConfigPath(): string | null {
130
  if (!config.openclawHome) return null
131
  return join(config.openclawHome, 'openclaw.json')
 
146
  return null
147
  }
148
 
149
+ export function enrichAgentConfigFromWorkspace(configData: any): any {
150
+ if (!configData || typeof configData !== 'object') return configData
151
+ const workspace = typeof configData.workspace === 'string' ? configData.workspace : undefined
152
+ if (!workspace) return configData
153
+
154
+ const identityFile = readWorkspaceFile(workspace, 'identity.md')
155
+ const toolsFile = readWorkspaceFile(workspace, 'TOOLS.md')
156
+
157
+ const mergedIdentity = {
158
+ ...parseIdentityFromFile(identityFile || ''),
159
+ ...((configData.identity && typeof configData.identity === 'object') ? configData.identity : {}),
160
+ }
161
+ const mergedTools = {
162
+ ...parseToolsFromFile(toolsFile || ''),
163
+ ...((configData.tools && typeof configData.tools === 'object') ? configData.tools : {}),
164
+ }
165
+
166
+ return {
167
+ ...configData,
168
+ identity: Object.keys(mergedIdentity).length > 0 ? mergedIdentity : configData.identity,
169
+ tools: Object.keys(mergedTools).length > 0 ? mergedTools : configData.tools,
170
+ }
171
+ }
172
+
173
  /** Read and parse openclaw.json agents list */
174
  async function readOpenClawAgents(): Promise<OpenClawAgent[]> {
175
  const configPath = getConfigPath()
 
190
  } {
191
  const name = agent.identity?.name || agent.name || agent.id
192
  const role = agent.identity?.theme || 'agent'
 
193
  // Store the full config minus systemPrompt/soul (which can be large)
194
+ const configData = enrichAgentConfigFromWorkspace({
195
  openclawId: agent.id,
196
  model: agent.model,
197
  identity: agent.identity,
 
202
  workspace: agent.workspace,
203
  agentDir: agent.agentDir,
204
  isDefault: agent.default || false,
205
+ })
206
 
207
  // Read soul.md from the agent's workspace if available
208
  const soul_content = readWorkspaceFile(agent.workspace, 'soul.md')