Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/v2/telegram #56

Open
wants to merge 79 commits into
base: feature/v2/groq
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
8370be0
update quick action links
madjin Mar 19, 2025
a613ac6
Merge branch 'v2-develop' of github.com:elizaOS/eliza into jin-docs-v2
madjin Mar 19, 2025
93bc999
add thumbnails, update frontpage links
madjin Mar 20, 2025
f252328
cleanup core docs
madjin Mar 20, 2025
da094d8
update sidebars and intro
madjin Mar 20, 2025
0bddf26
fetch latest news
madjin Mar 20, 2025
cb0b576
fix syntax highlighting
madjin Mar 20, 2025
11c697f
add redpill plugin
HashWarlock Mar 21, 2025
3599bf3
update readme
HashWarlock Mar 21, 2025
ba88aa5
update package json
HashWarlock Mar 21, 2025
647d3f3
Merge branch 'v2-develop' into jin-docs-v2
madjin Mar 21, 2025
558d41a
update llms.txt files
madjin Mar 22, 2025
1dec6bc
update analysis files
madjin Mar 22, 2025
4fc5b75
update repomix configs
madjin Mar 22, 2025
fea5f3a
update blog
madjin Mar 22, 2025
3de4c9a
update blog post
madjin Mar 22, 2025
a2da8a5
update post
madjin Mar 22, 2025
5f45553
remove extra
madjin Mar 22, 2025
bc50b39
update config
madjin Mar 22, 2025
e2e91b0
update news
madjin Mar 22, 2025
ad87dc1
Merge branch 'v2-develop' into jin-docs-v2
madjin Mar 22, 2025
f20951f
fixed add plugin command quickstart.md
antman1p Mar 22, 2025
4074cb5
Merge pull request #4047 from antman1p/patch-5
madjin Mar 22, 2025
7733821
Merge branch 'v2-develop' into jin-docs-v2
madjin Mar 22, 2025
f68c7fb
add new blog post
madjin Mar 22, 2025
28e1e3b
Merge pull request #4045 from elizaOS/hash/add-redpill
lalalune Mar 23, 2025
e002e38
Merge pull request #4044 from meta-introspector/feature/v2/groq
lalalune Mar 23, 2025
40f2fa1
Merge branch 'v2-develop' into jin-docs-v2
madjin Mar 23, 2025
16cd5c9
add llms.txt generator workflow
madjin Mar 23, 2025
ce75ea2
Merge branch 'jin-docs-v2' of github.com:elizaOS/eliza into jin-docs-v2
madjin Mar 23, 2025
5a18419
fix: tg negative id
Mar 23, 2025
0954a7e
feat: add partial agent update
Mar 20, 2025
2df73bd
feat: make partial updates by navbars in char settings
Mar 20, 2025
b927f2d
feat: restructure update by panels
Mar 20, 2025
f7ffc36
chore: remove debug logs
Mar 20, 2025
91fb23d
chore: remove logs & fix avatar size
Mar 20, 2025
a7bdd72
chore: optimize logic for update
Mar 20, 2025
8f3b643
fix: remove unused vars
Mar 20, 2025
45d3763
chore: ensure min req for agent start
Mar 20, 2025
86d2ece
chore: remove unused function
Mar 20, 2025
6ed42d3
chore: enahnce settings update on agent
Mar 21, 2025
eade2a5
chore: clean the code
Mar 22, 2025
bb9c3be
chore: temp disable usePlugins
Mar 22, 2025
d70c3e0
fix: send only text response
Mar 23, 2025
495577b
add mermaid / bun notes
madjin Mar 23, 2025
dc2ce5c
fix: env drag and drop
Mar 23, 2025
ef8982b
add new links at footer, add copy page button
madjin Mar 24, 2025
bab0c6d
remove artifacts
madjin Mar 24, 2025
dceef56
fix chatgpt link, and sidebars order
madjin Mar 24, 2025
d849b44
add some svgs of worlds / rooms / entities
madjin Mar 24, 2025
861a25b
Merge pull request #4032 from elizaOS/jin-docs-v2
madjin Mar 24, 2025
e688afa
test existing salting
wtfsayo Mar 24, 2025
769acf2
handle salt without agentId
wtfsayo Mar 24, 2025
28b2542
clean up
wtfsayo Mar 24, 2025
6756d41
Merge branch 'v2-develop' into ELI2-130/change-agent-update-to-send-o…
wtfsayo Mar 24, 2025
00e5102
Merge pull request #4026 from 0xbbjoker/ELI2-130/change-agent-update-…
wtfsayo Mar 24, 2025
ca8fc5f
Merge branch 'v2-develop' into test-secret-encryption
wtfsayo Mar 24, 2025
c9bd2b7
Merge branch 'v2-develop' into fix/telegram-negative-chatid-uuid
wtfsayo Mar 24, 2025
feaa91d
Merge pull request #4052 from 0xbbjoker/fix/telegram-negative-chatid-…
wtfsayo Mar 24, 2025
337e066
Merge branch 'v2-develop' into test-secret-encryption
wtfsayo Mar 24, 2025
41ee10d
show client on received messages memory
wtfsayo Mar 24, 2025
aedfcdb
Merge pull request #4058 from elizaOS/minor-memory-viewer
wtfsayo Mar 24, 2025
fbea9f2
chore: encrypt character secrets from GUI
Mar 24, 2025
b6314ce
Merge pull request #4059 from 0xbbjoker/chore/gui-secret-encryption
wtfsayo Mar 24, 2025
c135a27
Merge branch 'v2-develop' into test-secret-encryption
wtfsayo Mar 24, 2025
b556504
Merge pull request #4056 from elizaOS/test-secret-encryption
wtfsayo Mar 24, 2025
cbd1b76
fix agent details display on cli
wtfsayo Mar 25, 2025
42d8fd9
better error for connection
wtfsayo Mar 25, 2025
0f2ebb2
fix display banner
wtfsayo Mar 25, 2025
69f7873
fix remaining cli issues
wtfsayo Mar 25, 2025
905545a
Merge pull request #4061 from elizaOS/cli-fixes
wtfsayo Mar 25, 2025
734eed2
change default director for models and cache for localai
wtfsayo Mar 25, 2025
e20bd81
Merge pull request #4062 from elizaOS/v2-local-ai-default-dir
wtfsayo Mar 25, 2025
3b7cd02
adding in branch name
Mar 25, 2025
9efd7c8
try again
Mar 25, 2025
37158ce
revert changes
Mar 25, 2025
1f42919
only show text animation once
tcm390 Mar 25, 2025
2e6faa0
JMD: rebase again and again
Mar 26, 2025
5e8a26e
builds
Mar 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: add partial agent update
0xbbjoker committed Mar 23, 2025

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit 0954a7e03304200ae0725b8ae199e7b5612906a1
49 changes: 40 additions & 9 deletions packages/client/src/components/agent-settings.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import CharacterForm from '@/components/character-form';
import { useToast } from '@/hooks/use-toast';
import { useAgentUpdate } from '@/hooks/use-agent-update';
import { apiClient } from '@/lib/api';
import { deepMerge } from '@/lib/utils';
import type { Agent, UUID } from '@elizaos/core';
import { useQueryClient } from '@tanstack/react-query';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import AvatarPanel from './avatar-panel';
import PluginsPanel from './plugins-panel';
@@ -13,20 +15,40 @@ export default function AgentSettings({ agent, agentId }: { agent: Agent; agentI
const { toast } = useToast();
const navigate = useNavigate();
const queryClient = useQueryClient();

// Use our agent update hook for more intelligent handling of JSONb fields
const { agent: agentState, updateObject, reset } = useAgentUpdate(agent);
const [characterValue, setCharacterValue] = useState<Agent>(agent);

// Keep characterValue in sync with agentState (our source of truth)
useEffect(() => {
setCharacterValue(agentState);
}, [agentState]);

// When setCharacterValue is called, update our agentState
const handleCharacterValueChange = (updater: (prev: Agent) => Agent) => {
const newValue = updater(characterValue);
updateObject(newValue);
setCharacterValue(newValue);
};

const handleSubmit = async (updatedAgent: Agent) => {
try {
// Call the API to update the agent's character
if (!agentId) {
throw new Error('Agent ID is missing');
}

// Make sure plugins are preserved
const mergedAgent = {
// Make sure we're properly merging all JSONb fields
// Get the original agent to ensure we're starting with complete data
const { data: currentAgent } = await apiClient.getAgent(agentId);

// Create a properly merged agent object
// This ensures all JSONb fields are correctly handled
const mergedAgent = deepMerge(currentAgent, {
...updatedAgent,
plugins: characterValue.plugins, // Preserve the plugins from our local state
};
});

// Send the character update request to the agent endpoint
await apiClient.updateAgent(agentId, mergedAgent);
@@ -64,30 +86,39 @@ export default function AgentSettings({ agent, agentId }: { agent: Agent; agentI
return (
<CharacterForm
characterValue={characterValue}
setCharacterValue={setCharacterValue}
setCharacterValue={handleCharacterValueChange}
title="Character Settings"
description="Configure your AI character's behavior and capabilities"
onSubmit={handleSubmit}
onReset={() => setCharacterValue(agent)}
onReset={() => reset()}
onDelete={() => handleDelete(agent)}
isAgent={true}
customComponents={[
{
name: 'Plugins',
component: (
<PluginsPanel characterValue={characterValue} setCharacterValue={setCharacterValue} />
<PluginsPanel
characterValue={characterValue}
setCharacterValue={handleCharacterValueChange}
/>
),
},
{
name: 'Secret',
component: (
<SecretPanel characterValue={characterValue} setCharacterValue={setCharacterValue} />
<SecretPanel
characterValue={characterValue}
setCharacterValue={handleCharacterValueChange}
/>
),
},
{
name: 'Avatar',
component: (
<AvatarPanel characterValue={characterValue} setCharacterValue={setCharacterValue} />
<AvatarPanel
characterValue={characterValue}
setCharacterValue={handleCharacterValueChange}
/>
),
},
]}
22 changes: 14 additions & 8 deletions packages/client/src/components/secret-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { usePartialUpdate } from '@/hooks/use-partial-update';
import type { Agent } from '@elizaos/core';
import { Check, CloudUpload, Eye, EyeOff, MoreVertical, X } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
@@ -15,6 +16,9 @@ interface SecretPanelProps {
}

export default function EnvSettingsPanel({ characterValue, setCharacterValue }: SecretPanelProps) {
// Use our new hook to properly handle updates to nested JSONb fields
const [agentState, updateField] = usePartialUpdate(characterValue);

const [envs, setEnvs] = useState<EnvVariable[]>(
Object.entries(characterValue?.settings?.secrets || {}).map(([name, value]) => ({
name,
@@ -151,15 +155,17 @@ export default function EnvSettingsPanel({ characterValue, setCharacterValue }:
};
}, []);

// Update the agent's settings whenever envs change
useEffect(() => {
setCharacterValue((prev) => ({
...prev,
settings: {
...prev.settings,
secrets: Object.fromEntries(envs.map(({ name, value }) => [name, value])),
},
}));
}, [envs, setCharacterValue]);
// Create the secrets object from the envs array
const secrets = Object.fromEntries(envs.map(({ name, value }) => [name, value]));

// Update just the settings.secrets part without touching other settings
updateField('settings.secrets', secrets);

// Update the parent component's state
setCharacterValue(() => agentState);
}, [envs, setCharacterValue, updateField, agentState]);

return (
<div className="rounded-lg w-full flex flex-col gap-3">
92 changes: 92 additions & 0 deletions packages/client/src/hooks/use-agent-update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { usePartialUpdate } from '@/hooks/use-partial-update';
import type { Agent } from '@elizaos/core';
import { useCallback } from 'react';

/**
* A custom hook for handling Agent updates with specific handling for JSONb fields.
* This hook builds on usePartialUpdate but adds Agent-specific convenience methods.
*
* @param initialAgent The initial Agent object
* @returns Object with agent state and update methods
*/
export function useAgentUpdate(initialAgent: Agent) {
const [agent, updateField, updateObject, reset] = usePartialUpdate(initialAgent);

/**
* Updates a field in the Agent's settings object
*
* @param path Path within settings (e.g., 'voice.model')
* @param value New value
*/
const updateSetting = useCallback(
<T>(path: string, value: T) => {
updateField(`settings.${path}`, value);
},
[updateField]
);

/**
* Updates a secret in the Agent's settings.secrets object
*
* @param key Secret key
* @param value Secret value
*/
const updateSecret = useCallback(
(key: string, value: string) => {
updateField(`settings.secrets.${key}`, value);
},
[updateField]
);

/**
* Updates or replaces an array field in the Agent
*
* @param fieldName Array field name (e.g., 'plugins', 'bio', etc.)
* @param value New array value
*/
const updateArrayField = useCallback(
<T>(fieldName: string, value: T[]) => {
updateField(fieldName, value);
},
[updateField]
);

/**
* Updates a value in one of the style arrays
*
* @param styleType Type of style ('all', 'chat', 'post')
* @param index Index in the array
* @param value New value
*/
const updateStyleItem = useCallback(
(styleType: 'all' | 'chat' | 'post', index: number, value: string) => {
updateField(`style.${styleType}.${index}`, value);
},
[updateField]
);

/**
* Sets a style array
*
* @param styleType Type of style ('all', 'chat', 'post')
* @param values Array of style values
*/
const setStyleArray = useCallback(
(styleType: 'all' | 'chat' | 'post', values: string[]) => {
updateField(`style.${styleType}`, values);
},
[updateField]
);

return {
agent,
updateField,
updateObject,
updateSetting,
updateSecret,
updateArrayField,
updateStyleItem,
setStyleArray,
reset,
};
}
112 changes: 112 additions & 0 deletions packages/client/src/hooks/use-partial-update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { deepMerge } from '@/lib/utils';
import { useState, useCallback } from 'react';

/**
* A custom hook for handling partial updates of objects with nested JSONb fields.
* This hook ensures that updates to nested objects and arrays are properly
* managed when sending updates to the server.
*
* @param initialValue The initial state object
* @returns A tuple containing:
* - The current state object
* - A function to update a specific field (handles nested paths)
* - A function to set the entire object
* - A function to reset to initial state
*/
export function usePartialUpdate<T extends object>(initialValue: T) {
const [value, setValue] = useState<T>(initialValue);

/**
* Updates a specific field in the object, handling nested paths
*
* @param path The path to the field to update (e.g., 'settings.voice.model')
* @param newValue The new value for the field
*/
const updateField = useCallback(<K>(path: string, newValue: K) => {
setValue((prevValue) => {
// Handle simple (non-nested) case
if (!path.includes('.')) {
return {
...prevValue,
[path]: newValue,
} as T;
}

// Handle nested paths
const pathParts = path.split('.');
const fieldToUpdate = pathParts[0];
const remainingPath = pathParts.slice(1).join('.');

// Handle arrays in path (e.g., 'style.all.0')
const isArrayIndex = !isNaN(Number(pathParts[1]));

if (isArrayIndex) {
const arrayName = pathParts[0];
const index = Number(pathParts[1]);
// Ensure we're working with an array and handle it safely
const currentValue = prevValue[arrayName as keyof T];
const array = Array.isArray(currentValue) ? [...currentValue] : [];

if (pathParts.length === 2) {
// Direct array item update
array[index] = newValue;
} else {
// Updating a property of an object in an array
const deeperPath = pathParts.slice(2).join('.');
array[index] = updateNestedObject(array[index], deeperPath, newValue);
}

return {
...prevValue,
[arrayName]: array,
} as T;
}

// Handle regular nested objects
return {
...prevValue,
[fieldToUpdate]: updateNestedObject(
prevValue[fieldToUpdate as keyof T],
remainingPath,
newValue
),
} as T;
});
}, []);

/**
* Helper function to update a nested object
*/
const updateNestedObject = <K, V>(obj: K, path: string, value: V): K => {
if (!path.includes('.')) {
return {
...obj,
[path]: value,
} as unknown as K;
}

const [field, ...remainingPath] = path.split('.');
const nextPath = remainingPath.join('.');

return {
...obj,
[field]: updateNestedObject((obj as any)[field] || {}, nextPath, value),
} as unknown as K;
};

/**
* Updates the entire object using deep merge
*/
const updateObject = useCallback((newPartialValue: Partial<T>) => {
setValue((prev) => deepMerge(prev, newPartialValue));
}, []);

/**
* Resets to the initial state
*/
const reset = useCallback(() => {
setValue(initialValue);
}, [initialValue]);

return [value, updateField, updateObject, reset] as const;
}
32 changes: 26 additions & 6 deletions packages/client/src/lib/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Agent, Character, UUID, Memory } from '@elizaos/core';
import { WorldManager } from './world-manager';
import { deepMerge } from './utils';

const API_PREFIX = '/api';

@@ -254,12 +255,31 @@ export const apiClient = {
},
deleteAgent: (agentId: string): Promise<{ success: boolean }> =>
fetcher({ url: `/agents/${agentId}`, method: 'DELETE' }),
updateAgent: (agentId: string, agent: Agent) =>
fetcher({
url: `/agents/${agentId}`,
method: 'PATCH',
body: agent,
}),
updateAgent: async (agentId: string, agent: Agent) => {
// First get the current agent to ensure we have complete data
try {
const currentAgentResponse = await fetcher({ url: `/agents/${agentId}` });
const currentAgent = currentAgentResponse.data;

// If we have the current agent, merge the updates with it
// This ensures all JSONb fields are properly handled
const mergedAgent = currentAgent ? deepMerge(currentAgent, agent) : agent;

return fetcher({
url: `/agents/${agentId}`,
method: 'PATCH',
body: mergedAgent,
});
} catch (error) {
// If we can't get the current agent for some reason, just send the update
console.warn('Could not fetch current agent data before update:', error);
return fetcher({
url: `/agents/${agentId}`,
method: 'PATCH',
body: agent,
});
}
},
createAgent: (params: { characterPath?: string; characterJson?: Character }) =>
fetcher({
url: '/agents/',
71 changes: 70 additions & 1 deletion packages/client/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@ export function urlToCharacterName(urlName: string): string {

// crypto.randomUUID only works in https context in firefox
export function randomUUID(): UUID {
return URL.createObjectURL(new Blob()).split('/').pop();
return URL.createObjectURL(new Blob()).split('/').pop() as UUID;
}

export function getEntityId(): UUID {
@@ -104,3 +104,72 @@ export const compressImage = (
reader.readAsDataURL(file);
});
};

/**
* Deeply merges multiple objects together
* - Arrays are completely replaced (not merged)
* - Null values explicitly overwrite existing values
* - Undefined values are ignored (don't overwrite)
*
* @param target The base object to merge into
* @param sources One or more source objects to merge from
* @returns A new merged object
*/
export function deepMerge<T>(target: T, ...sources: Partial<T>[]): T {
if (!sources.length) return target;

const result = { ...target };

sources.forEach((source) => {
if (!source) return;

Object.keys(source).forEach((key) => {
const sourceValue = source[key as keyof typeof source];

// Skip undefined values - they shouldn't overwrite existing values
if (sourceValue === undefined) return;

// Handle null values - they should explicitly overwrite
if (sourceValue === null) {
result[key as keyof T] = null as any;
return;
}

// For arrays, completely replace them
if (Array.isArray(sourceValue)) {
result[key as keyof T] = [...sourceValue] as any;
return;
}

// For objects, recursively merge
if (
typeof sourceValue === 'object' &&
!Array.isArray(sourceValue) &&
sourceValue !== null &&
typeof result[key as keyof T] === 'object' &&
result[key as keyof T] !== null &&
!Array.isArray(result[key as keyof T])
) {
result[key as keyof T] = deepMerge(result[key as keyof T] as any, sourceValue as any);
return;
}

// For all other values, just replace them
result[key as keyof T] = sourceValue as any;
});
});

return result;
}

/**
* Prepares agent data for update by ensuring all JSONb fields are properly
* merged with the existing agent data
*
* @param existingAgent The current agent data from the database
* @param updates The partial updates to be applied
* @returns A merged agent object ready for database update
*/
export function prepareAgentUpdate<T>(existingAgent: T, updates: Partial<T>): T {
return deepMerge(existingAgent, updates);
}
103 changes: 94 additions & 9 deletions packages/plugin-sql/src/base.ts
Original file line number Diff line number Diff line change
@@ -247,27 +247,112 @@ export abstract class BaseDrizzleAdapter<
});
}

/**
* Validates the agent update request
* @param agentId The ID of the agent to update
* @param agent The agent data to validate
* @throws Error if validation fails
*/
private validateAgentUpdate(agentId: UUID, agent: Partial<Agent>): void {
if (!agent.id) {
throw new Error('Agent ID is required for update');
}
}

/**
* Merges nested JSONb objects within the agent settings
* @param existingAgent The current agent data
* @param updates The updates to apply
* @returns Merged settings object
*/
private mergeAgentSettings(existingAgent: Agent, updates: Partial<Agent>): Agent['settings'] {
if (!updates.settings || !existingAgent.settings) {
return updates.settings || existingAgent.settings;
}

const mergedSettings = {
...existingAgent.settings,
...updates.settings,
};

// Handle nested secrets within settings
if (updates.settings.secrets && existingAgent.settings.secrets) {
mergedSettings.secrets = {
...existingAgent.settings.secrets,
...updates.settings.secrets,
};
}

return mergedSettings;
}

/**
* Merges style-related fields, handling arrays appropriately
* @param existingAgent The current agent data
* @param updates The updates to apply
* @returns Merged style object
*/
private mergeAgentStyle(existingAgent: Agent, updates: Partial<Agent>): Agent['style'] {
if (!updates.style) {
return existingAgent.style;
}

return {
...existingAgent.style,
...updates.style,
};
}

/**
* Merges array fields, replacing them entirely if provided
* @param existingAgent The current agent data
* @param updates The updates to apply
* @returns Object containing merged array fields
*/
private mergeArrayFields(existingAgent: Agent, updates: Partial<Agent>): Partial<Agent> {
const mergedFields: Partial<Agent> = {};

// Handle array JSONb fields - these should be replaced entirely if provided
if (updates.plugins !== undefined) mergedFields.plugins = updates.plugins;
if (updates.bio !== undefined) mergedFields.bio = updates.bio;
if (updates.topics !== undefined) mergedFields.topics = updates.topics;
if (updates.adjectives !== undefined) mergedFields.adjectives = updates.adjectives;
if (updates.knowledge !== undefined) mergedFields.knowledge = updates.knowledge;

return mergedFields;
}

/**
* Updates an agent in the database with the provided agent ID and data.
* Properly handles merging of nested JSONb fields.
*
* @param {UUID} agentId - The unique identifier of the agent to update.
* @param {Partial<Agent>} agent - The partial agent object containing the fields to update.
* @returns {Promise<boolean>} - A boolean indicating if the agent was successfully updated.
*/
async updateAgent(agentId: UUID, agent: Partial<Agent>): Promise<boolean> {
return this.withDatabase(async () => {
try {
if (!agent.id) {
throw new Error('Agent ID is required for update');
this.validateAgentUpdate(agentId, agent);

// Get the existing agent to properly merge JSONb fields
const existingAgent = await this.getAgent(agentId);
if (!existingAgent) {
throw new Error(`Agent with ID ${agentId} not found`);
}

// Merge all fields using helper functions
const mergedAgent: Partial<Agent> = {
...existingAgent,
...agent,
updatedAt: Date.now(),
settings: this.mergeAgentSettings(existingAgent, agent),
style: this.mergeAgentStyle(existingAgent, agent),
...this.mergeArrayFields(existingAgent, agent),
};

await this.db.transaction(async (tx) => {
await tx
.update(agentTable)
.set({
...agent,
updatedAt: Date.now(),
})
.where(eq(agentTable.id, agentId));
await tx.update(agentTable).set(mergedAgent).where(eq(agentTable.id, agentId));
});

logger.debug('Agent updated successfully:', {