machinaos 0.0.21 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +32 -6
  2. package/bin/cli.js +0 -0
  3. package/client/dist/assets/index-5BWZnM6b.js +703 -0
  4. package/client/dist/index.html +1 -1
  5. package/client/package.json +1 -1
  6. package/client/src/Dashboard.tsx +12 -5
  7. package/client/src/ParameterPanel.tsx +6 -5
  8. package/client/src/components/AIAgentNode.tsx +35 -16
  9. package/client/src/components/CredentialsModal.tsx +450 -5
  10. package/client/src/components/TeamMonitorNode.tsx +269 -0
  11. package/client/src/components/parameterPanel/InputSection.tsx +25 -0
  12. package/client/src/contexts/WebSocketContext.tsx +38 -0
  13. package/client/src/hooks/useApiKeys.ts +44 -0
  14. package/client/src/nodeDefinitions/specializedAgentNodes.ts +59 -3
  15. package/client/src/nodeDefinitions/twitterNodes.ts +441 -0
  16. package/client/src/nodeDefinitions/utilityNodes.ts +45 -1
  17. package/client/src/nodeDefinitions.ts +7 -1
  18. package/client/src/services/executionService.ts +4 -1
  19. package/install.sh +63 -1
  20. package/package.json +5 -2
  21. package/scripts/build.js +0 -0
  22. package/scripts/clean.js +0 -0
  23. package/scripts/daemon.js +0 -0
  24. package/scripts/docker.js +0 -0
  25. package/scripts/install.js +0 -0
  26. package/scripts/postinstall.js +29 -0
  27. package/scripts/preinstall.js +67 -0
  28. package/scripts/serve-client.js +0 -0
  29. package/scripts/start.js +0 -0
  30. package/scripts/stop.js +0 -0
  31. package/scripts/sync-version.js +0 -0
  32. package/server/Dockerfile +10 -15
  33. package/server/constants.py +20 -0
  34. package/server/core/database.py +443 -3
  35. package/server/main.py +9 -1
  36. package/server/models/database.py +112 -2
  37. package/server/pyproject.toml +3 -0
  38. package/server/requirements.txt +3 -0
  39. package/server/routers/twitter.py +390 -0
  40. package/server/routers/websocket.py +320 -0
  41. package/server/services/agent_team.py +266 -0
  42. package/server/services/ai.py +43 -0
  43. package/server/services/compaction.py +39 -4
  44. package/server/services/event_waiter.py +41 -0
  45. package/server/services/handlers/__init__.py +13 -0
  46. package/server/services/handlers/ai.py +66 -2
  47. package/server/services/handlers/tools.py +84 -0
  48. package/server/services/handlers/twitter.py +297 -0
  49. package/server/services/handlers/utility.py +91 -0
  50. package/server/services/node_executor.py +15 -1
  51. package/server/services/pricing.py +270 -0
  52. package/server/services/status_broadcaster.py +79 -0
  53. package/server/services/twitter_oauth.py +410 -0
  54. package/server/skills/social_agent/twitter-search-skill/SKILL.md +146 -0
  55. package/server/skills/social_agent/twitter-send-skill/SKILL.md +142 -0
  56. package/server/skills/social_agent/twitter-user-skill/SKILL.md +165 -0
  57. package/workflows/Zeenie_full.json +459 -0
  58. package/workflows/Zeenie_small.json +459 -0
  59. package/client/dist/assets/index-YVvAiByx.js +0 -703
  60. package/server/requirements-docker.txt +0 -86
@@ -0,0 +1,269 @@
1
+ import React, { useState, useEffect, useCallback, useMemo } from 'react';
2
+ import { Handle, Position, NodeProps, useEdges, useNodes } from 'reactflow';
3
+ import { NodeData } from '../types/NodeTypes';
4
+ import { useAppStore } from '../store/useAppStore';
5
+ import { nodeDefinitions } from '../nodeDefinitions';
6
+ import { useAppTheme } from '../hooks/useAppTheme';
7
+ import { useWebSocket } from '../contexts/WebSocketContext';
8
+
9
+ // AI Employee node types that can have teams
10
+ const TEAM_LEAD_TYPES = ['ai_employee', 'orchestrator_agent'];
11
+
12
+ interface TeamTask {
13
+ id: string;
14
+ title: string;
15
+ status: 'pending' | 'in_progress' | 'completed' | 'failed' | 'skipped';
16
+ assigned_to?: string;
17
+ }
18
+
19
+ interface TeamMember {
20
+ agent_node_id: string;
21
+ agent_type: string;
22
+ status: string;
23
+ }
24
+
25
+ interface TeamStatus {
26
+ team_id: string;
27
+ members: TeamMember[];
28
+ tasks: {
29
+ total: number;
30
+ completed: number;
31
+ active: number;
32
+ pending: number;
33
+ failed: number;
34
+ };
35
+ active_tasks: TeamTask[];
36
+ }
37
+
38
+ const TeamMonitorNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnectable, selected }) => {
39
+ const theme = useAppTheme();
40
+ const { setSelectedNode } = useAppStore();
41
+ const { sendRequest } = useWebSocket();
42
+ const edges = useEdges();
43
+ const nodes = useNodes();
44
+
45
+ const [teamStatus, setTeamStatus] = useState<TeamStatus | null>(null);
46
+ const [isLoading, setIsLoading] = useState(false);
47
+
48
+ const definition = nodeDefinitions[type as keyof typeof nodeDefinitions];
49
+ const nodeColor = definition?.defaults?.color || '#8b5cf6';
50
+
51
+ // Find connected AI Employee/Orchestrator node to get workflow context
52
+ const connectedTeamLead = useMemo(() => {
53
+ // Find edges where this node is the target (input)
54
+ const incomingEdges = edges.filter(e => e.target === id);
55
+ for (const edge of incomingEdges) {
56
+ const sourceNode = nodes.find(n => n.id === edge.source);
57
+ if (sourceNode && TEAM_LEAD_TYPES.includes(sourceNode.type || '')) {
58
+ return sourceNode;
59
+ }
60
+ }
61
+ return null;
62
+ }, [edges, nodes, id]);
63
+
64
+ // Fetch team status
65
+ const fetchTeamStatus = useCallback(async () => {
66
+ // Use explicit teamId from data, or get from connected node's workflow context
67
+ const teamId = data?.teamId;
68
+ if (!teamId && !connectedTeamLead) return;
69
+
70
+ setIsLoading(true);
71
+ try {
72
+ // Request team status - backend will find the active team for the workflow
73
+ const response = await sendRequest<{ status?: any }>('get_team_status', {
74
+ team_id: teamId,
75
+ team_lead_node_id: connectedTeamLead?.id
76
+ });
77
+ if (response?.status) {
78
+ setTeamStatus({
79
+ team_id: response.status.team_id || teamId || '',
80
+ members: response.status.members || [],
81
+ tasks: {
82
+ total: response.status.task_count || 0,
83
+ completed: response.status.completed_count || 0,
84
+ active: response.status.active_count || 0,
85
+ pending: response.status.pending_count || 0,
86
+ failed: response.status.failed_count || 0,
87
+ },
88
+ active_tasks: response.status.active_tasks || [],
89
+ });
90
+ }
91
+ } catch (error) {
92
+ console.error('Failed to fetch team status:', error);
93
+ } finally {
94
+ setIsLoading(false);
95
+ }
96
+ }, [data?.teamId, connectedTeamLead, sendRequest]);
97
+
98
+ // Auto-refresh when connected to a team lead
99
+ useEffect(() => {
100
+ const interval = data?.refreshInterval || 2000;
101
+ if (interval > 0 && (data?.teamId || connectedTeamLead)) {
102
+ fetchTeamStatus();
103
+ const timer = setInterval(fetchTeamStatus, interval);
104
+ return () => clearInterval(timer);
105
+ }
106
+ }, [data?.refreshInterval, data?.teamId, connectedTeamLead, fetchTeamStatus]);
107
+
108
+ const handleClick = (e: React.MouseEvent) => {
109
+ e.stopPropagation();
110
+ setSelectedNode({ id, type, data, position: { x: 0, y: 0 } });
111
+ };
112
+
113
+ const getStatusColor = (status: string) => {
114
+ switch (status) {
115
+ case 'completed': return theme.dracula.green;
116
+ case 'in_progress': return theme.dracula.cyan;
117
+ case 'failed': return theme.dracula.red;
118
+ case 'pending': return theme.dracula.orange;
119
+ default: return theme.colors.textSecondary;
120
+ }
121
+ };
122
+
123
+ return (
124
+ <div
125
+ onClick={handleClick}
126
+ style={{
127
+ width: 200,
128
+ minHeight: 120,
129
+ borderRadius: 8,
130
+ background: theme.isDarkMode
131
+ ? `linear-gradient(135deg, ${nodeColor}15 0%, ${theme.colors.background} 100%)`
132
+ : `linear-gradient(145deg, #ffffff 0%, ${nodeColor}08 100%)`,
133
+ border: `2px solid ${selected ? theme.colors.focus : nodeColor + '60'}`,
134
+ fontFamily: 'system-ui, -apple-system, sans-serif',
135
+ fontSize: 10,
136
+ overflow: 'hidden',
137
+ }}
138
+ >
139
+ {/* Header */}
140
+ <div style={{
141
+ padding: '6px 8px',
142
+ borderBottom: `1px solid ${theme.colors.border}`,
143
+ background: `${nodeColor}20`,
144
+ display: 'flex',
145
+ alignItems: 'center',
146
+ gap: 6,
147
+ }}>
148
+ <span style={{ fontSize: 14 }}>📊</span>
149
+ <span style={{ fontWeight: 600, color: theme.colors.text, fontSize: 11 }}>
150
+ {data?.label || 'Team Monitor'}
151
+ </span>
152
+ </div>
153
+
154
+ {/* Stats Grid */}
155
+ <div style={{
156
+ display: 'grid',
157
+ gridTemplateColumns: 'repeat(4, 1fr)',
158
+ gap: 2,
159
+ padding: '6px 4px',
160
+ borderBottom: `1px solid ${theme.colors.border}`,
161
+ }}>
162
+ <div style={{ textAlign: 'center' }}>
163
+ <div style={{ fontSize: 12, fontWeight: 'bold', color: theme.dracula.purple }}>
164
+ {teamStatus?.members?.length || 0}
165
+ </div>
166
+ <div style={{ fontSize: 8, color: theme.colors.textSecondary }}>Team</div>
167
+ </div>
168
+ <div style={{ textAlign: 'center' }}>
169
+ <div style={{ fontSize: 12, fontWeight: 'bold', color: theme.dracula.cyan }}>
170
+ {teamStatus?.tasks?.total || 0}
171
+ </div>
172
+ <div style={{ fontSize: 8, color: theme.colors.textSecondary }}>Tasks</div>
173
+ </div>
174
+ <div style={{ textAlign: 'center' }}>
175
+ <div style={{ fontSize: 12, fontWeight: 'bold', color: theme.dracula.green }}>
176
+ {teamStatus?.tasks?.completed || 0}
177
+ </div>
178
+ <div style={{ fontSize: 8, color: theme.colors.textSecondary }}>Done</div>
179
+ </div>
180
+ <div style={{ textAlign: 'center' }}>
181
+ <div style={{ fontSize: 12, fontWeight: 'bold', color: theme.dracula.orange }}>
182
+ {teamStatus?.tasks?.active || 0}
183
+ </div>
184
+ <div style={{ fontSize: 8, color: theme.colors.textSecondary }}>Active</div>
185
+ </div>
186
+ </div>
187
+
188
+ {/* Active Tasks */}
189
+ <div style={{ padding: '4px 6px', maxHeight: 80, overflow: 'auto' }}>
190
+ {!data?.teamId && !connectedTeamLead ? (
191
+ <div style={{ color: theme.colors.textSecondary, textAlign: 'center', padding: 8, fontSize: 9 }}>
192
+ Connect to AI Employee
193
+ </div>
194
+ ) : isLoading && !teamStatus ? (
195
+ <div style={{ color: theme.colors.textSecondary, textAlign: 'center', padding: 8, fontSize: 9 }}>
196
+ Loading...
197
+ </div>
198
+ ) : teamStatus?.active_tasks && teamStatus.active_tasks.length > 0 ? (
199
+ teamStatus.active_tasks.slice(0, 3).map((task) => (
200
+ <div key={task.id} style={{
201
+ display: 'flex',
202
+ alignItems: 'center',
203
+ gap: 4,
204
+ padding: '2px 0',
205
+ fontSize: 9,
206
+ }}>
207
+ <span style={{
208
+ width: 6,
209
+ height: 6,
210
+ borderRadius: '50%',
211
+ backgroundColor: getStatusColor(task.status),
212
+ flexShrink: 0,
213
+ }} />
214
+ <span style={{
215
+ color: theme.colors.text,
216
+ overflow: 'hidden',
217
+ textOverflow: 'ellipsis',
218
+ whiteSpace: 'nowrap',
219
+ flex: 1,
220
+ }}>
221
+ {task.title}
222
+ </span>
223
+ </div>
224
+ ))
225
+ ) : (
226
+ <div style={{ color: theme.colors.textSecondary, textAlign: 'center', padding: 4, fontSize: 9 }}>
227
+ No active tasks
228
+ </div>
229
+ )}
230
+ </div>
231
+
232
+ {/* Input Handle */}
233
+ <Handle
234
+ id="input-team"
235
+ type="target"
236
+ position={Position.Left}
237
+ isConnectable={isConnectable}
238
+ style={{
239
+ left: -6,
240
+ top: '50%',
241
+ width: 10,
242
+ height: 10,
243
+ backgroundColor: theme.isDarkMode ? theme.colors.background : '#ffffff',
244
+ border: `2px solid ${nodeColor}`,
245
+ borderRadius: '50%',
246
+ }}
247
+ />
248
+
249
+ {/* Output Handle */}
250
+ <Handle
251
+ id="output-main"
252
+ type="source"
253
+ position={Position.Right}
254
+ isConnectable={isConnectable}
255
+ style={{
256
+ right: -6,
257
+ top: '50%',
258
+ width: 10,
259
+ height: 10,
260
+ backgroundColor: nodeColor,
261
+ border: `2px solid ${theme.isDarkMode ? theme.colors.background : '#ffffff'}`,
262
+ borderRadius: '50%',
263
+ }}
264
+ />
265
+ </div>
266
+ );
267
+ };
268
+
269
+ export default TeamMonitorNode;
@@ -252,6 +252,31 @@ const InputSection: React.FC<InputSectionProps> = ({ nodeId, visible = true }) =
252
252
  body: 'string',
253
253
  json: 'object'
254
254
  },
255
+ // Twitter node output schemas
256
+ twitter: {
257
+ tweet_id: 'string',
258
+ text: 'string',
259
+ author_id: 'string',
260
+ created_at: 'string',
261
+ action: 'string'
262
+ },
263
+ twitterSearch: {
264
+ tweets: [{
265
+ id: 'string',
266
+ text: 'string',
267
+ author_id: 'string',
268
+ created_at: 'string'
269
+ }],
270
+ count: 'number',
271
+ query: 'string'
272
+ },
273
+ twitterUser: {
274
+ id: 'string',
275
+ username: 'string',
276
+ name: 'string',
277
+ profile_image_url: 'string',
278
+ verified: 'boolean'
279
+ },
255
280
  httpRequest: {
256
281
  status: 'number',
257
282
  data: 'any',
@@ -90,6 +90,15 @@ export interface WhatsAppStatus {
90
90
  timestamp?: number;
91
91
  }
92
92
 
93
+ export interface TwitterStatus {
94
+ connected: boolean;
95
+ username: string | null;
96
+ user_id: string | null;
97
+ name?: string;
98
+ profile_image_url?: string;
99
+ verified?: boolean;
100
+ }
101
+
93
102
  // WhatsApp Rate Limit types (from Go RPC schema)
94
103
  export interface RateLimitConfig {
95
104
  enabled: boolean;
@@ -203,6 +212,7 @@ interface WebSocketContextValue {
203
212
  androidStatus: AndroidStatus;
204
213
  setAndroidStatus: React.Dispatch<React.SetStateAction<AndroidStatus>>;
205
214
  whatsappStatus: WhatsAppStatus;
215
+ twitterStatus: TwitterStatus;
206
216
  whatsappMessages: WhatsAppMessage[]; // History of received messages
207
217
  lastWhatsAppMessage: WhatsAppMessage | null; // Most recent message
208
218
  apiKeyStatuses: Record<string, ApiKeyStatus>;
@@ -322,6 +332,12 @@ const defaultWhatsAppStatus: WhatsAppStatus = {
322
332
  pairing: false
323
333
  };
324
334
 
335
+ const defaultTwitterStatus: TwitterStatus = {
336
+ connected: false,
337
+ username: null,
338
+ user_id: null,
339
+ };
340
+
325
341
  const WebSocketContext = createContext<WebSocketContextValue | null>(null);
326
342
 
327
343
  // WebSocket URL (convert http to ws)
@@ -355,6 +371,7 @@ export const WebSocketProvider: React.FC<{ children: React.ReactNode }> = ({ chi
355
371
  const [reconnecting, setReconnecting] = useState(false);
356
372
  const [androidStatus, setAndroidStatus] = useState<AndroidStatus>(defaultAndroidStatus);
357
373
  const [whatsappStatus, setWhatsappStatus] = useState<WhatsAppStatus>(defaultWhatsAppStatus);
374
+ const [twitterStatus, setTwitterStatus] = useState<TwitterStatus>(defaultTwitterStatus);
358
375
  const [whatsappMessages, setWhatsappMessages] = useState<WhatsAppMessage[]>([]);
359
376
  const [lastWhatsAppMessage, setLastWhatsAppMessage] = useState<WhatsAppMessage | null>(null);
360
377
  const [apiKeyStatuses, setApiKeyStatuses] = useState<Record<string, ApiKeyStatus>>({});
@@ -469,6 +486,7 @@ export const WebSocketProvider: React.FC<{ children: React.ReactNode }> = ({ chi
469
486
  if (data) {
470
487
  if (data.android) setAndroidStatus(data.android);
471
488
  if (data.whatsapp) setWhatsappStatus(data.whatsapp);
489
+ if (data.twitter) setTwitterStatus(data.twitter);
472
490
  if (data.api_keys) setApiKeyStatuses(data.api_keys);
473
491
  // Node statuses from initial_status - group by workflow_id (n8n pattern)
474
492
  if (data.nodes) {
@@ -523,6 +541,19 @@ export const WebSocketProvider: React.FC<{ children: React.ReactNode }> = ({ chi
523
541
  setWhatsappStatus(data || defaultWhatsAppStatus);
524
542
  break;
525
543
 
544
+ case 'twitter_oauth_complete':
545
+ // Handle Twitter OAuth completion broadcast from backend
546
+ if (data?.success) {
547
+ setTwitterStatus({
548
+ connected: true,
549
+ username: data.username || null,
550
+ user_id: data.user_id || null,
551
+ name: data.name,
552
+ profile_image_url: data.profile_image_url,
553
+ });
554
+ }
555
+ break;
556
+
526
557
  case 'whatsapp_message_received':
527
558
  // Handle incoming WhatsApp message from Go service
528
559
  if (data) {
@@ -1959,6 +1990,7 @@ export const WebSocketProvider: React.FC<{ children: React.ReactNode }> = ({ chi
1959
1990
  androidStatus,
1960
1991
  setAndroidStatus,
1961
1992
  whatsappStatus,
1993
+ twitterStatus,
1962
1994
  whatsappMessages,
1963
1995
  lastWhatsAppMessage,
1964
1996
  apiKeyStatuses,
@@ -2089,6 +2121,12 @@ export const useWhatsAppStatus = (): WhatsAppStatus => {
2089
2121
  return whatsappStatus;
2090
2122
  };
2091
2123
 
2124
+ // Hook specifically for Twitter status
2125
+ export const useTwitterStatus = (): TwitterStatus => {
2126
+ const { twitterStatus } = useWebSocket();
2127
+ return twitterStatus;
2128
+ };
2129
+
2092
2130
  // Hook specifically for deployment status
2093
2131
  export const useDeploymentStatus = (): DeploymentStatus => {
2094
2132
  const { deploymentStatus } = useWebSocket();
@@ -23,6 +23,31 @@ export interface ProviderDefaults {
23
23
  reasoning_format: 'parsed' | 'hidden';
24
24
  }
25
25
 
26
+ export interface ModelUsageSummary {
27
+ model: string;
28
+ input_tokens: number;
29
+ output_tokens: number;
30
+ total_tokens: number;
31
+ input_cost: number;
32
+ output_cost: number;
33
+ cache_cost: number;
34
+ total_cost: number;
35
+ execution_count: number;
36
+ }
37
+
38
+ export interface ProviderUsageSummary {
39
+ provider: string;
40
+ total_input_tokens: number;
41
+ total_output_tokens: number;
42
+ total_tokens: number;
43
+ total_input_cost: number;
44
+ total_output_cost: number;
45
+ total_cache_cost: number;
46
+ total_cost: number;
47
+ execution_count: number;
48
+ models: ModelUsageSummary[];
49
+ }
50
+
26
51
  export interface UseApiKeysResult {
27
52
  // Validate and store API key
28
53
  validateApiKey: (provider: string, apiKey: string) => Promise<ApiKeyValidationResult>;
@@ -52,6 +77,9 @@ export interface UseApiKeysResult {
52
77
  getProviderDefaults: (provider: string) => Promise<ProviderDefaults>;
53
78
  saveProviderDefaults: (provider: string, defaults: ProviderDefaults) => Promise<boolean>;
54
79
 
80
+ // Provider usage summary
81
+ getProviderUsageSummary: () => Promise<ProviderUsageSummary[]>;
82
+
55
83
  // State
56
84
  isValidating: boolean;
57
85
  validationError: string | null;
@@ -280,6 +308,21 @@ export const useApiKeys = (): UseApiKeysResult => {
280
308
  }
281
309
  }, [sendRequest, isConnected]);
282
310
 
311
+ /**
312
+ * Get aggregated usage and cost summary by provider
313
+ */
314
+ const getProviderUsageSummary = useCallback(async (): Promise<ProviderUsageSummary[]> => {
315
+ if (!isConnected) return [];
316
+
317
+ try {
318
+ const response = await sendRequest<{ providers: ProviderUsageSummary[] }>('get_provider_usage_summary', {});
319
+ return response?.providers || [];
320
+ } catch (error) {
321
+ console.warn('Error fetching provider usage summary:', error);
322
+ return [];
323
+ }
324
+ }, [sendRequest, isConnected]);
325
+
283
326
  return {
284
327
  validateApiKey,
285
328
  saveApiKey,
@@ -291,6 +334,7 @@ export const useApiKeys = (): UseApiKeysResult => {
291
334
  getAiModels,
292
335
  getProviderDefaults,
293
336
  saveProviderDefaults,
337
+ getProviderUsageSummary,
294
338
  isValidating,
295
339
  validationError,
296
340
  isConnected
@@ -342,9 +342,65 @@ export const specializedAgentNodes: Record<string, INodeTypeDescription> = {
342
342
  subtitle: 'Agent Coordination',
343
343
  description: 'AI Agent specialized for orchestrating multiple agents. Coordinates complex multi-agent workflows by delegating tasks to specialized agents and synthesizing their results.',
344
344
  defaults: { name: 'Orchestrator Agent', color: dracula.cyan },
345
- inputs: AI_AGENT_INPUTS,
345
+ inputs: [
346
+ ...AI_AGENT_INPUTS,
347
+ { name: 'teammates', displayName: 'Team', type: 'main' as NodeConnectionType, description: 'Connect teammate agents for team mode' }
348
+ ],
346
349
  outputs: AI_AGENT_OUTPUTS,
347
- properties: AI_AGENT_PROPERTIES
350
+ properties: [
351
+ ...AI_AGENT_PROPERTIES,
352
+ {
353
+ displayName: 'Team Mode',
354
+ name: 'teamMode',
355
+ type: 'options',
356
+ options: [
357
+ { name: 'Disabled', value: '' },
358
+ { name: 'Parallel', value: 'parallel' },
359
+ { name: 'Sequential', value: 'sequential' }
360
+ ],
361
+ default: '',
362
+ description: 'Enable team mode to coordinate connected teammate agents'
363
+ }
364
+ ]
365
+ },
366
+
367
+ // AI Employee - Team lead node for coordinating multiple agents
368
+ ai_employee: {
369
+ displayName: 'AI Employee',
370
+ name: 'ai_employee',
371
+ icon: '👥',
372
+ group: ['agent', 'ai'],
373
+ version: 1,
374
+ subtitle: 'Team Orchestration',
375
+ description: 'AI Employee for coordinating multiple AI agents. Connect teammate agents to enable shared task lists, messaging, and coordinated execution.',
376
+ defaults: { name: 'AI Employee', color: dracula.purple },
377
+ inputs: [
378
+ ...AI_AGENT_INPUTS,
379
+ { name: 'teammates', displayName: 'Team', type: 'main' as NodeConnectionType, description: 'Connect teammate agents' }
380
+ ],
381
+ outputs: AI_AGENT_OUTPUTS,
382
+ properties: [
383
+ ...AI_AGENT_PROPERTIES,
384
+ {
385
+ displayName: 'Team Mode',
386
+ name: 'teamMode',
387
+ type: 'options',
388
+ options: [
389
+ { name: 'Parallel', value: 'parallel' },
390
+ { name: 'Sequential', value: 'sequential' }
391
+ ],
392
+ default: 'parallel',
393
+ description: 'How teammates process tasks'
394
+ },
395
+ {
396
+ displayName: 'Max Concurrent',
397
+ name: 'maxConcurrent',
398
+ type: 'number',
399
+ default: 5,
400
+ typeOptions: { minValue: 1, maxValue: 20 },
401
+ description: 'Maximum concurrent task executions'
402
+ }
403
+ ]
348
404
  }
349
405
  };
350
406
 
@@ -353,4 +409,4 @@ export const specializedAgentNodes: Record<string, INodeTypeDescription> = {
353
409
  // ============================================================================
354
410
 
355
411
  // List of specialized agent node types for identification
356
- export const SPECIALIZED_AGENT_TYPES = ['android_agent', 'coding_agent', 'web_agent', 'task_agent', 'social_agent', 'travel_agent', 'tool_agent', 'productivity_agent', 'payments_agent', 'consumer_agent', 'autonomous_agent', 'orchestrator_agent'];
412
+ export const SPECIALIZED_AGENT_TYPES = ['android_agent', 'coding_agent', 'web_agent', 'task_agent', 'social_agent', 'travel_agent', 'tool_agent', 'productivity_agent', 'payments_agent', 'consumer_agent', 'autonomous_agent', 'orchestrator_agent', 'ai_employee'];