family-ai-agent 1.0.0
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.
- package/.env.example +49 -0
- package/README.md +161 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +336 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config/index.d.ts +37 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +68 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/models.d.ts +17 -0
- package/dist/config/models.d.ts.map +1 -0
- package/dist/config/models.js +128 -0
- package/dist/config/models.js.map +1 -0
- package/dist/core/agents/agent-factory.d.ts +31 -0
- package/dist/core/agents/agent-factory.d.ts.map +1 -0
- package/dist/core/agents/agent-factory.js +151 -0
- package/dist/core/agents/agent-factory.js.map +1 -0
- package/dist/core/agents/base-agent.d.ts +51 -0
- package/dist/core/agents/base-agent.d.ts.map +1 -0
- package/dist/core/agents/base-agent.js +245 -0
- package/dist/core/agents/base-agent.js.map +1 -0
- package/dist/core/agents/index.d.ts +8 -0
- package/dist/core/agents/index.d.ts.map +1 -0
- package/dist/core/agents/index.js +9 -0
- package/dist/core/agents/index.js.map +1 -0
- package/dist/core/agents/personalities/automation.d.ts +14 -0
- package/dist/core/agents/personalities/automation.d.ts.map +1 -0
- package/dist/core/agents/personalities/automation.js +146 -0
- package/dist/core/agents/personalities/automation.js.map +1 -0
- package/dist/core/agents/personalities/chat.d.ts +10 -0
- package/dist/core/agents/personalities/chat.d.ts.map +1 -0
- package/dist/core/agents/personalities/chat.js +132 -0
- package/dist/core/agents/personalities/chat.js.map +1 -0
- package/dist/core/agents/personalities/coding.d.ts +16 -0
- package/dist/core/agents/personalities/coding.d.ts.map +1 -0
- package/dist/core/agents/personalities/coding.js +166 -0
- package/dist/core/agents/personalities/coding.js.map +1 -0
- package/dist/core/agents/personalities/research.d.ts +13 -0
- package/dist/core/agents/personalities/research.d.ts.map +1 -0
- package/dist/core/agents/personalities/research.js +133 -0
- package/dist/core/agents/personalities/research.js.map +1 -0
- package/dist/core/agents/types.d.ts +102 -0
- package/dist/core/agents/types.d.ts.map +1 -0
- package/dist/core/agents/types.js +2 -0
- package/dist/core/agents/types.js.map +1 -0
- package/dist/core/orchestrator/graph.d.ts +118 -0
- package/dist/core/orchestrator/graph.d.ts.map +1 -0
- package/dist/core/orchestrator/graph.js +233 -0
- package/dist/core/orchestrator/graph.js.map +1 -0
- package/dist/database/client.d.ts +19 -0
- package/dist/database/client.d.ts.map +1 -0
- package/dist/database/client.js +95 -0
- package/dist/database/client.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +67 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/openrouter-client.d.ts +45 -0
- package/dist/llm/openrouter-client.d.ts.map +1 -0
- package/dist/llm/openrouter-client.js +155 -0
- package/dist/llm/openrouter-client.js.map +1 -0
- package/dist/memory/conversation/index.d.ts +37 -0
- package/dist/memory/conversation/index.d.ts.map +1 -0
- package/dist/memory/conversation/index.js +196 -0
- package/dist/memory/conversation/index.js.map +1 -0
- package/dist/memory/index.d.ts +4 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +5 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/knowledge-base/index.d.ts +51 -0
- package/dist/memory/knowledge-base/index.d.ts.map +1 -0
- package/dist/memory/knowledge-base/index.js +222 -0
- package/dist/memory/knowledge-base/index.js.map +1 -0
- package/dist/memory/longterm/vector-store.d.ts +44 -0
- package/dist/memory/longterm/vector-store.d.ts.map +1 -0
- package/dist/memory/longterm/vector-store.js +229 -0
- package/dist/memory/longterm/vector-store.js.map +1 -0
- package/dist/safety/audit-logger.d.ts +68 -0
- package/dist/safety/audit-logger.d.ts.map +1 -0
- package/dist/safety/audit-logger.js +215 -0
- package/dist/safety/audit-logger.js.map +1 -0
- package/dist/safety/guardrails/input-guardrail.d.ts +21 -0
- package/dist/safety/guardrails/input-guardrail.d.ts.map +1 -0
- package/dist/safety/guardrails/input-guardrail.js +145 -0
- package/dist/safety/guardrails/input-guardrail.js.map +1 -0
- package/dist/safety/guardrails/output-guardrail.d.ts +18 -0
- package/dist/safety/guardrails/output-guardrail.d.ts.map +1 -0
- package/dist/safety/guardrails/output-guardrail.js +125 -0
- package/dist/safety/guardrails/output-guardrail.js.map +1 -0
- package/dist/safety/index.d.ts +4 -0
- package/dist/safety/index.d.ts.map +1 -0
- package/dist/safety/index.js +5 -0
- package/dist/safety/index.js.map +1 -0
- package/dist/utils/errors.d.ts +36 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +94 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +47 -0
- package/dist/utils/logger.js.map +1 -0
- package/docker/init-db.sql +149 -0
- package/docker/sandbox/Dockerfile.sandbox +29 -0
- package/docker-compose.yml +61 -0
- package/package.json +80 -0
- package/src/cli/index.ts +392 -0
- package/src/config/index.ts +85 -0
- package/src/config/models.ts +156 -0
- package/src/core/agents/agent-factory.ts +192 -0
- package/src/core/agents/base-agent.ts +333 -0
- package/src/core/agents/index.ts +27 -0
- package/src/core/agents/personalities/automation.ts +202 -0
- package/src/core/agents/personalities/chat.ts +159 -0
- package/src/core/agents/personalities/coding.ts +227 -0
- package/src/core/agents/personalities/research.ts +177 -0
- package/src/core/agents/types.ts +124 -0
- package/src/core/orchestrator/graph.ts +305 -0
- package/src/database/client.ts +109 -0
- package/src/index.ts +104 -0
- package/src/llm/openrouter-client.ts +218 -0
- package/src/memory/conversation/index.ts +313 -0
- package/src/memory/index.ts +23 -0
- package/src/memory/knowledge-base/index.ts +357 -0
- package/src/memory/longterm/vector-store.ts +364 -0
- package/src/safety/audit-logger.ts +357 -0
- package/src/safety/guardrails/input-guardrail.ts +191 -0
- package/src/safety/guardrails/output-guardrail.ts +160 -0
- package/src/safety/index.ts +21 -0
- package/src/utils/errors.ts +120 -0
- package/src/utils/logger.ts +74 -0
- package/tsconfig.json +37 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid';
|
|
2
|
+
import { BaseAgent } from '../base-agent.js';
|
|
3
|
+
import type {
|
|
4
|
+
AgentConfig,
|
|
5
|
+
AgentState,
|
|
6
|
+
TaskResult,
|
|
7
|
+
RetrievedMemory,
|
|
8
|
+
HandoffDecision,
|
|
9
|
+
} from '../types.js';
|
|
10
|
+
import { getRecommendedModel } from '../../../config/models.js';
|
|
11
|
+
|
|
12
|
+
const RESEARCH_SYSTEM_PROMPT = `You are an expert Research Agent in the Family AI Agent system, specializing in information gathering and analysis.
|
|
13
|
+
|
|
14
|
+
Your identity: Research Specialist
|
|
15
|
+
Your personality:
|
|
16
|
+
- Thorough and methodical
|
|
17
|
+
- Fact-focused and objective
|
|
18
|
+
- Clear in presenting findings
|
|
19
|
+
- Good at synthesizing information from multiple sources
|
|
20
|
+
|
|
21
|
+
Your capabilities:
|
|
22
|
+
- Web search and information retrieval
|
|
23
|
+
- Fact-checking and verification
|
|
24
|
+
- Summarizing complex topics
|
|
25
|
+
- Comparing and analyzing data
|
|
26
|
+
- Creating research reports
|
|
27
|
+
|
|
28
|
+
Research methodology:
|
|
29
|
+
1. Understand the research question clearly
|
|
30
|
+
2. Identify key search terms and concepts
|
|
31
|
+
3. Gather information from multiple sources
|
|
32
|
+
4. Verify facts and cross-reference
|
|
33
|
+
5. Synthesize findings into coherent summaries
|
|
34
|
+
6. Cite sources when possible
|
|
35
|
+
|
|
36
|
+
Guidelines:
|
|
37
|
+
- Always prioritize accuracy over speed
|
|
38
|
+
- Clearly distinguish between facts and opinions
|
|
39
|
+
- Note any uncertainties or conflicting information
|
|
40
|
+
- Provide sources for claims when available
|
|
41
|
+
- Ask for clarification if the research question is ambiguous
|
|
42
|
+
|
|
43
|
+
When presenting findings:
|
|
44
|
+
- Start with a brief summary/answer
|
|
45
|
+
- Provide supporting details and evidence
|
|
46
|
+
- List sources and references
|
|
47
|
+
- Note any limitations or areas needing further research`;
|
|
48
|
+
|
|
49
|
+
export class ResearchAgent extends BaseAgent {
|
|
50
|
+
constructor(tools: AgentConfig['tools'] = []) {
|
|
51
|
+
super({
|
|
52
|
+
identity: {
|
|
53
|
+
id: `research-${nanoid(8)}`,
|
|
54
|
+
name: 'Researcher',
|
|
55
|
+
role: 'research',
|
|
56
|
+
description: 'Expert research and information gathering agent',
|
|
57
|
+
capabilities: [
|
|
58
|
+
'web_search',
|
|
59
|
+
'fact_checking',
|
|
60
|
+
'summarization',
|
|
61
|
+
'analysis',
|
|
62
|
+
'report_writing',
|
|
63
|
+
'data_comparison',
|
|
64
|
+
'source_verification',
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
model: getRecommendedModel('research'),
|
|
68
|
+
temperature: 0.3, // Lower temperature for factual accuracy
|
|
69
|
+
systemPrompt: RESEARCH_SYSTEM_PROMPT,
|
|
70
|
+
tools,
|
|
71
|
+
maxIterations: 15, // More iterations for thorough research
|
|
72
|
+
timeout: 180000, // 3 minutes for research tasks
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
protected buildSystemPrompt(
|
|
77
|
+
context: Record<string, unknown>,
|
|
78
|
+
memories: RetrievedMemory[]
|
|
79
|
+
): string {
|
|
80
|
+
let prompt = this.systemPrompt;
|
|
81
|
+
|
|
82
|
+
// Add previous research findings from memory
|
|
83
|
+
const researchMemories = memories.filter(
|
|
84
|
+
(m) => m.type === 'episodic' || m.type === 'semantic'
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
if (researchMemories.length > 0) {
|
|
88
|
+
prompt += '\n\n## Previous Research Findings\n';
|
|
89
|
+
for (const memory of researchMemories) {
|
|
90
|
+
prompt += `- ${memory.content} (relevance: ${(memory.relevanceScore * 100).toFixed(0)}%)\n`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Add research context
|
|
95
|
+
if (context.researchTopic) {
|
|
96
|
+
prompt += `\n\n## Current Research Topic\n${context.researchTopic}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (context.researchConstraints) {
|
|
100
|
+
prompt += `\n\n## Research Constraints\n`;
|
|
101
|
+
prompt += JSON.stringify(context.researchConstraints, null, 2);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return prompt;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
protected async shouldHandoff(
|
|
108
|
+
state: AgentState,
|
|
109
|
+
result: TaskResult
|
|
110
|
+
): Promise<HandoffDecision> {
|
|
111
|
+
const output = String(result.output).toLowerCase();
|
|
112
|
+
|
|
113
|
+
// Check if implementation is needed
|
|
114
|
+
if (
|
|
115
|
+
output.includes('to implement this') ||
|
|
116
|
+
output.includes('code needed') ||
|
|
117
|
+
output.includes('write the code')
|
|
118
|
+
) {
|
|
119
|
+
return {
|
|
120
|
+
shouldHandoff: true,
|
|
121
|
+
targetAgent: 'coding',
|
|
122
|
+
reason: 'Research complete, implementation needed',
|
|
123
|
+
context: {
|
|
124
|
+
researchFindings: result.output,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Check if automation is suggested
|
|
130
|
+
if (
|
|
131
|
+
output.includes('automate this process') ||
|
|
132
|
+
output.includes('schedule regular')
|
|
133
|
+
) {
|
|
134
|
+
return {
|
|
135
|
+
shouldHandoff: true,
|
|
136
|
+
targetAgent: 'automation',
|
|
137
|
+
reason: 'Research suggests automation solution',
|
|
138
|
+
context: {
|
|
139
|
+
researchFindings: result.output,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
shouldHandoff: false,
|
|
146
|
+
reason: 'Research task complete',
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Additional research-specific methods
|
|
151
|
+
async summarize(content: string, maxLength: number = 500): Promise<TaskResult> {
|
|
152
|
+
return this.execute(
|
|
153
|
+
`Summarize the following content in ${maxLength} words or less:\n\n${content}`,
|
|
154
|
+
{ task: 'summarization' }
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async factCheck(claim: string): Promise<TaskResult> {
|
|
159
|
+
return this.execute(
|
|
160
|
+
`Fact-check the following claim and provide evidence:\n\n"${claim}"`,
|
|
161
|
+
{ task: 'fact_checking' }
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async compare(items: string[]): Promise<TaskResult> {
|
|
166
|
+
return this.execute(
|
|
167
|
+
`Compare and analyze the following items:\n\n${items.map((i, idx) => `${idx + 1}. ${i}`).join('\n')}`,
|
|
168
|
+
{ task: 'comparison' }
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function createResearchAgent(tools: AgentConfig['tools'] = []): ResearchAgent {
|
|
174
|
+
return new ResearchAgent(tools);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export default ResearchAgent;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
2
|
+
import type { StructuredTool } from '@langchain/core/tools';
|
|
3
|
+
|
|
4
|
+
export type AgentRole = 'supervisor' | 'research' | 'coding' | 'automation' | 'chat';
|
|
5
|
+
|
|
6
|
+
export type AgentStatus = 'idle' | 'thinking' | 'executing' | 'waiting' | 'completed' | 'error';
|
|
7
|
+
|
|
8
|
+
export interface AgentIdentity {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
role: AgentRole;
|
|
12
|
+
description: string;
|
|
13
|
+
capabilities: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AgentConfig {
|
|
17
|
+
identity: AgentIdentity;
|
|
18
|
+
model: string;
|
|
19
|
+
temperature?: number;
|
|
20
|
+
maxTokens?: number;
|
|
21
|
+
systemPrompt: string;
|
|
22
|
+
tools: StructuredTool[];
|
|
23
|
+
maxIterations?: number;
|
|
24
|
+
timeout?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AgentState {
|
|
28
|
+
messages: BaseMessage[];
|
|
29
|
+
currentTask?: TaskDefinition;
|
|
30
|
+
activeAgents: AgentIdentity[];
|
|
31
|
+
sharedContext: Record<string, unknown>;
|
|
32
|
+
taskResults: TaskResult[];
|
|
33
|
+
nextAgent: string | null;
|
|
34
|
+
memories: RetrievedMemory[];
|
|
35
|
+
iterationCount: number;
|
|
36
|
+
status: AgentStatus;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface TaskDefinition {
|
|
40
|
+
id: string;
|
|
41
|
+
type: 'research' | 'coding' | 'automation' | 'chat' | 'complex';
|
|
42
|
+
description: string;
|
|
43
|
+
input: string;
|
|
44
|
+
priority: 'low' | 'normal' | 'high' | 'critical';
|
|
45
|
+
requiredCapabilities: string[];
|
|
46
|
+
context?: Record<string, unknown>;
|
|
47
|
+
parentTaskId?: string;
|
|
48
|
+
subtasks?: TaskDefinition[];
|
|
49
|
+
deadline?: Date;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface TaskResult {
|
|
53
|
+
taskId: string;
|
|
54
|
+
agentId: string;
|
|
55
|
+
status: 'success' | 'partial' | 'failed';
|
|
56
|
+
output: unknown;
|
|
57
|
+
error?: string;
|
|
58
|
+
executionTimeMs: number;
|
|
59
|
+
metadata?: Record<string, unknown>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface RetrievedMemory {
|
|
63
|
+
id: string;
|
|
64
|
+
type: 'semantic' | 'episodic' | 'procedural';
|
|
65
|
+
content: string;
|
|
66
|
+
relevanceScore: number;
|
|
67
|
+
metadata?: Record<string, unknown>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface AgentMessage {
|
|
71
|
+
id: string;
|
|
72
|
+
timestamp: Date;
|
|
73
|
+
sender: AgentIdentity;
|
|
74
|
+
recipient: AgentIdentity | 'supervisor' | 'broadcast';
|
|
75
|
+
type: 'request' | 'response' | 'handoff' | 'status' | 'error';
|
|
76
|
+
payload: {
|
|
77
|
+
task?: TaskDefinition;
|
|
78
|
+
result?: TaskResult;
|
|
79
|
+
context?: Record<string, unknown>;
|
|
80
|
+
reason?: string;
|
|
81
|
+
};
|
|
82
|
+
metadata: {
|
|
83
|
+
priority: 'low' | 'normal' | 'high' | 'critical';
|
|
84
|
+
threadId: string;
|
|
85
|
+
parentMessageId?: string;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface HandoffDecision {
|
|
90
|
+
shouldHandoff: boolean;
|
|
91
|
+
targetAgent?: AgentRole;
|
|
92
|
+
reason: string;
|
|
93
|
+
context?: Record<string, unknown>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface RoutingDecision {
|
|
97
|
+
selectedAgents: AgentRole[];
|
|
98
|
+
executionMode: 'single' | 'sequential' | 'parallel';
|
|
99
|
+
reasoning: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Tool result types
|
|
103
|
+
export interface ToolResult {
|
|
104
|
+
success: boolean;
|
|
105
|
+
output: unknown;
|
|
106
|
+
error?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Agent events for logging/monitoring
|
|
110
|
+
export type AgentEventType =
|
|
111
|
+
| 'started'
|
|
112
|
+
| 'thinking'
|
|
113
|
+
| 'tool_call'
|
|
114
|
+
| 'tool_result'
|
|
115
|
+
| 'handoff'
|
|
116
|
+
| 'completed'
|
|
117
|
+
| 'error';
|
|
118
|
+
|
|
119
|
+
export interface AgentEvent {
|
|
120
|
+
type: AgentEventType;
|
|
121
|
+
agentId: string;
|
|
122
|
+
timestamp: Date;
|
|
123
|
+
data: Record<string, unknown>;
|
|
124
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { StateGraph, Annotation, END, START } from '@langchain/langgraph';
|
|
2
|
+
import { BaseMessage, HumanMessage, AIMessage, SystemMessage } from '@langchain/core/messages';
|
|
3
|
+
import { nanoid } from 'nanoid';
|
|
4
|
+
|
|
5
|
+
import { getAgentFactory } from '../agents/agent-factory.js';
|
|
6
|
+
import type {
|
|
7
|
+
AgentRole,
|
|
8
|
+
AgentState,
|
|
9
|
+
TaskDefinition,
|
|
10
|
+
TaskResult,
|
|
11
|
+
RetrievedMemory,
|
|
12
|
+
AgentIdentity,
|
|
13
|
+
} from '../agents/types.js';
|
|
14
|
+
import { createLogger } from '../../utils/logger.js';
|
|
15
|
+
|
|
16
|
+
const logger = createLogger('OrchestratorGraph');
|
|
17
|
+
|
|
18
|
+
// Define the state annotation for LangGraph
|
|
19
|
+
export const AgentStateAnnotation = Annotation.Root({
|
|
20
|
+
// Messages in the conversation
|
|
21
|
+
messages: Annotation<BaseMessage[]>({
|
|
22
|
+
reducer: (prev, next) => [...prev, ...next],
|
|
23
|
+
default: () => [],
|
|
24
|
+
}),
|
|
25
|
+
|
|
26
|
+
// Current user input
|
|
27
|
+
input: Annotation<string>({
|
|
28
|
+
reducer: (_, next) => next,
|
|
29
|
+
default: () => '',
|
|
30
|
+
}),
|
|
31
|
+
|
|
32
|
+
// Current task being processed
|
|
33
|
+
currentTask: Annotation<TaskDefinition | null>({
|
|
34
|
+
reducer: (_, next) => next,
|
|
35
|
+
default: () => null,
|
|
36
|
+
}),
|
|
37
|
+
|
|
38
|
+
// Active agents in this workflow
|
|
39
|
+
activeAgents: Annotation<AgentIdentity[]>({
|
|
40
|
+
reducer: (prev, next) => {
|
|
41
|
+
// Merge unique agents
|
|
42
|
+
const existing = new Set(prev.map((a) => a.id));
|
|
43
|
+
return [...prev, ...next.filter((a) => !existing.has(a.id))];
|
|
44
|
+
},
|
|
45
|
+
default: () => [],
|
|
46
|
+
}),
|
|
47
|
+
|
|
48
|
+
// Shared context for multi-agent coordination
|
|
49
|
+
sharedContext: Annotation<Record<string, unknown>>({
|
|
50
|
+
reducer: (prev, next) => ({ ...prev, ...next }),
|
|
51
|
+
default: () => ({}),
|
|
52
|
+
}),
|
|
53
|
+
|
|
54
|
+
// Results from completed tasks
|
|
55
|
+
taskResults: Annotation<TaskResult[]>({
|
|
56
|
+
reducer: (prev, next) => [...prev, ...next],
|
|
57
|
+
default: () => [],
|
|
58
|
+
}),
|
|
59
|
+
|
|
60
|
+
// Retrieved memories for context
|
|
61
|
+
memories: Annotation<RetrievedMemory[]>({
|
|
62
|
+
reducer: (_, next) => next,
|
|
63
|
+
default: () => [],
|
|
64
|
+
}),
|
|
65
|
+
|
|
66
|
+
// Next agent to route to
|
|
67
|
+
nextAgent: Annotation<AgentRole | 'end' | null>({
|
|
68
|
+
reducer: (_, next) => next,
|
|
69
|
+
default: () => null,
|
|
70
|
+
}),
|
|
71
|
+
|
|
72
|
+
// Final response to user
|
|
73
|
+
response: Annotation<string>({
|
|
74
|
+
reducer: (_, next) => next,
|
|
75
|
+
default: () => '',
|
|
76
|
+
}),
|
|
77
|
+
|
|
78
|
+
// Error state
|
|
79
|
+
error: Annotation<string | null>({
|
|
80
|
+
reducer: (_, next) => next,
|
|
81
|
+
default: () => null,
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
export type GraphState = typeof AgentStateAnnotation.State;
|
|
86
|
+
|
|
87
|
+
// Supervisor node - analyzes and routes tasks
|
|
88
|
+
async function supervisorNode(state: GraphState): Promise<Partial<GraphState>> {
|
|
89
|
+
logger.debug('Supervisor analyzing task', { input: state.input?.slice(0, 100) });
|
|
90
|
+
|
|
91
|
+
const input = state.input.toLowerCase();
|
|
92
|
+
|
|
93
|
+
// Simple routing logic based on keywords
|
|
94
|
+
let nextAgent: AgentRole = 'chat';
|
|
95
|
+
|
|
96
|
+
// Research patterns
|
|
97
|
+
if (
|
|
98
|
+
input.includes('search') ||
|
|
99
|
+
input.includes('find') ||
|
|
100
|
+
input.includes('research') ||
|
|
101
|
+
input.includes('look up') ||
|
|
102
|
+
input.includes('what is') ||
|
|
103
|
+
input.includes('who is') ||
|
|
104
|
+
input.includes('investigate')
|
|
105
|
+
) {
|
|
106
|
+
nextAgent = 'research';
|
|
107
|
+
}
|
|
108
|
+
// Coding patterns
|
|
109
|
+
else if (
|
|
110
|
+
input.includes('code') ||
|
|
111
|
+
input.includes('program') ||
|
|
112
|
+
input.includes('function') ||
|
|
113
|
+
input.includes('debug') ||
|
|
114
|
+
input.includes('fix bug') ||
|
|
115
|
+
input.includes('implement') ||
|
|
116
|
+
input.includes('write a script') ||
|
|
117
|
+
input.includes('refactor')
|
|
118
|
+
) {
|
|
119
|
+
nextAgent = 'coding';
|
|
120
|
+
}
|
|
121
|
+
// Automation patterns
|
|
122
|
+
else if (
|
|
123
|
+
input.includes('automate') ||
|
|
124
|
+
input.includes('schedule') ||
|
|
125
|
+
input.includes('every day') ||
|
|
126
|
+
input.includes('every hour') ||
|
|
127
|
+
input.includes('batch') ||
|
|
128
|
+
input.includes('workflow') ||
|
|
129
|
+
input.includes('recurring')
|
|
130
|
+
) {
|
|
131
|
+
nextAgent = 'automation';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const task: TaskDefinition = {
|
|
135
|
+
id: nanoid(),
|
|
136
|
+
type: nextAgent === 'chat' ? 'chat' : nextAgent === 'coding' ? 'coding' : nextAgent === 'research' ? 'research' : 'automation',
|
|
137
|
+
description: state.input,
|
|
138
|
+
input: state.input,
|
|
139
|
+
priority: 'normal',
|
|
140
|
+
requiredCapabilities: [],
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
logger.info('Task routed', { taskId: task.id, targetAgent: nextAgent });
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
currentTask: task,
|
|
147
|
+
nextAgent,
|
|
148
|
+
sharedContext: {
|
|
149
|
+
routedAt: new Date().toISOString(),
|
|
150
|
+
routingReason: `Routed to ${nextAgent} based on input analysis`,
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Generic agent execution node
|
|
156
|
+
async function agentNode(
|
|
157
|
+
role: AgentRole
|
|
158
|
+
): Promise<(state: GraphState) => Promise<Partial<GraphState>>> {
|
|
159
|
+
return async (state: GraphState): Promise<Partial<GraphState>> => {
|
|
160
|
+
logger.debug(`${role} agent executing`, { taskId: state.currentTask?.id });
|
|
161
|
+
|
|
162
|
+
const factory = getAgentFactory();
|
|
163
|
+
const agent = factory.getAgent(role);
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const result = await agent.execute(
|
|
167
|
+
state.input,
|
|
168
|
+
state.sharedContext,
|
|
169
|
+
state.memories
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const aiMessage = new AIMessage({
|
|
173
|
+
content: String(result.output),
|
|
174
|
+
name: agent.name,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
messages: [aiMessage],
|
|
179
|
+
taskResults: [result],
|
|
180
|
+
response: String(result.output),
|
|
181
|
+
activeAgents: [agent.getInfo()],
|
|
182
|
+
nextAgent: 'end',
|
|
183
|
+
};
|
|
184
|
+
} catch (error) {
|
|
185
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
186
|
+
logger.error(`${role} agent failed`, { error: errorMessage });
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
error: errorMessage,
|
|
190
|
+
response: `Sorry, I encountered an error: ${errorMessage}`,
|
|
191
|
+
nextAgent: 'end',
|
|
192
|
+
};
|
|
193
|
+
} finally {
|
|
194
|
+
factory.releaseAgent(agent.id);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Create agent nodes
|
|
200
|
+
const chatAgentNode = await agentNode('chat');
|
|
201
|
+
const researchAgentNode = await agentNode('research');
|
|
202
|
+
const codingAgentNode = await agentNode('coding');
|
|
203
|
+
const automationAgentNode = await agentNode('automation');
|
|
204
|
+
|
|
205
|
+
// Routing function
|
|
206
|
+
function routeToAgent(state: GraphState): AgentRole | 'end' {
|
|
207
|
+
if (state.error) {
|
|
208
|
+
return 'end';
|
|
209
|
+
}
|
|
210
|
+
return state.nextAgent ?? 'chat';
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Build the graph
|
|
214
|
+
export function createOrchestratorGraph() {
|
|
215
|
+
const workflow = new StateGraph(AgentStateAnnotation)
|
|
216
|
+
// Add nodes
|
|
217
|
+
.addNode('supervisor', supervisorNode)
|
|
218
|
+
.addNode('chat', chatAgentNode)
|
|
219
|
+
.addNode('research', researchAgentNode)
|
|
220
|
+
.addNode('coding', codingAgentNode)
|
|
221
|
+
.addNode('automation', automationAgentNode)
|
|
222
|
+
|
|
223
|
+
// Entry point
|
|
224
|
+
.addEdge(START, 'supervisor')
|
|
225
|
+
|
|
226
|
+
// Conditional routing from supervisor
|
|
227
|
+
.addConditionalEdges('supervisor', routeToAgent, {
|
|
228
|
+
chat: 'chat',
|
|
229
|
+
research: 'research',
|
|
230
|
+
coding: 'coding',
|
|
231
|
+
automation: 'automation',
|
|
232
|
+
end: END,
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
// All agents go to END after execution
|
|
236
|
+
.addEdge('chat', END)
|
|
237
|
+
.addEdge('research', END)
|
|
238
|
+
.addEdge('coding', END)
|
|
239
|
+
.addEdge('automation', END);
|
|
240
|
+
|
|
241
|
+
return workflow.compile();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Singleton graph instance
|
|
245
|
+
let graphInstance: ReturnType<typeof createOrchestratorGraph> | null = null;
|
|
246
|
+
|
|
247
|
+
export function getOrchestratorGraph() {
|
|
248
|
+
if (!graphInstance) {
|
|
249
|
+
graphInstance = createOrchestratorGraph();
|
|
250
|
+
}
|
|
251
|
+
return graphInstance;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Helper function to run the orchestrator
|
|
255
|
+
export async function runOrchestrator(
|
|
256
|
+
input: string,
|
|
257
|
+
context: Record<string, unknown> = {},
|
|
258
|
+
memories: RetrievedMemory[] = []
|
|
259
|
+
): Promise<GraphState> {
|
|
260
|
+
const graph = getOrchestratorGraph();
|
|
261
|
+
|
|
262
|
+
const initialState: Partial<GraphState> = {
|
|
263
|
+
input,
|
|
264
|
+
messages: [new HumanMessage(input)],
|
|
265
|
+
sharedContext: context,
|
|
266
|
+
memories,
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
logger.info('Orchestrator started', { input: input.slice(0, 100) });
|
|
270
|
+
|
|
271
|
+
const result = await graph.invoke(initialState);
|
|
272
|
+
|
|
273
|
+
logger.info('Orchestrator completed', {
|
|
274
|
+
response: result.response?.slice(0, 100),
|
|
275
|
+
agentsUsed: result.activeAgents?.map((a) => a.name),
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return result as GraphState;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Stream execution
|
|
282
|
+
export async function* streamOrchestrator(
|
|
283
|
+
input: string,
|
|
284
|
+
context: Record<string, unknown> = {},
|
|
285
|
+
memories: RetrievedMemory[] = []
|
|
286
|
+
): AsyncGenerator<{ node: string; state: Partial<GraphState> }, void, unknown> {
|
|
287
|
+
const graph = getOrchestratorGraph();
|
|
288
|
+
|
|
289
|
+
const initialState: Partial<GraphState> = {
|
|
290
|
+
input,
|
|
291
|
+
messages: [new HumanMessage(input)],
|
|
292
|
+
sharedContext: context,
|
|
293
|
+
memories,
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const stream = await graph.stream(initialState);
|
|
297
|
+
|
|
298
|
+
for await (const chunk of stream) {
|
|
299
|
+
for (const [node, state] of Object.entries(chunk)) {
|
|
300
|
+
yield { node, state: state as Partial<GraphState> };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export default { createOrchestratorGraph, getOrchestratorGraph, runOrchestrator, streamOrchestrator };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
import { getDatabaseUrl } from '../config/index.js';
|
|
3
|
+
import { createLogger } from '../utils/logger.js';
|
|
4
|
+
import { MemoryError } from '../utils/errors.js';
|
|
5
|
+
|
|
6
|
+
const { Pool } = pg;
|
|
7
|
+
const logger = createLogger('Database');
|
|
8
|
+
|
|
9
|
+
let pool: pg.Pool | null = null;
|
|
10
|
+
|
|
11
|
+
export function getPool(): pg.Pool {
|
|
12
|
+
if (!pool) {
|
|
13
|
+
pool = new Pool({
|
|
14
|
+
connectionString: getDatabaseUrl(),
|
|
15
|
+
max: 20,
|
|
16
|
+
idleTimeoutMillis: 30000,
|
|
17
|
+
connectionTimeoutMillis: 5000,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
pool.on('error', (err) => {
|
|
21
|
+
logger.error('Unexpected pool error', { error: err.message });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
pool.on('connect', () => {
|
|
25
|
+
logger.debug('New client connected to pool');
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return pool;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function query<T extends pg.QueryResultRow = pg.QueryResultRow>(
|
|
33
|
+
text: string,
|
|
34
|
+
params?: unknown[]
|
|
35
|
+
): Promise<pg.QueryResult<T>> {
|
|
36
|
+
const pool = getPool();
|
|
37
|
+
const start = Date.now();
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const result = await pool.query<T>(text, params);
|
|
41
|
+
const duration = Date.now() - start;
|
|
42
|
+
logger.debug('Query executed', { duration, rows: result.rowCount });
|
|
43
|
+
return result;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
46
|
+
logger.error('Query failed', { error: errorMessage, query: text.slice(0, 100) });
|
|
47
|
+
throw new MemoryError(`Database query failed: ${errorMessage}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function getClient(): Promise<pg.PoolClient> {
|
|
52
|
+
const pool = getPool();
|
|
53
|
+
return pool.connect();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function transaction<T>(
|
|
57
|
+
callback: (client: pg.PoolClient) => Promise<T>
|
|
58
|
+
): Promise<T> {
|
|
59
|
+
const client = await getClient();
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
await client.query('BEGIN');
|
|
63
|
+
const result = await callback(client);
|
|
64
|
+
await client.query('COMMIT');
|
|
65
|
+
return result;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
await client.query('ROLLBACK');
|
|
68
|
+
throw error;
|
|
69
|
+
} finally {
|
|
70
|
+
client.release();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function healthCheck(): Promise<boolean> {
|
|
75
|
+
try {
|
|
76
|
+
const result = await query('SELECT 1 as health');
|
|
77
|
+
return result.rows.length > 0;
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function closePool(): Promise<void> {
|
|
84
|
+
if (pool) {
|
|
85
|
+
await pool.end();
|
|
86
|
+
pool = null;
|
|
87
|
+
logger.info('Database pool closed');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Initialize the database connection
|
|
92
|
+
export async function initDatabase(): Promise<void> {
|
|
93
|
+
const healthy = await healthCheck();
|
|
94
|
+
if (healthy) {
|
|
95
|
+
logger.info('Database connection established');
|
|
96
|
+
} else {
|
|
97
|
+
throw new MemoryError('Failed to connect to database');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default {
|
|
102
|
+
getPool,
|
|
103
|
+
query,
|
|
104
|
+
getClient,
|
|
105
|
+
transaction,
|
|
106
|
+
healthCheck,
|
|
107
|
+
closePool,
|
|
108
|
+
initDatabase,
|
|
109
|
+
};
|