@@ -13,6 +13,7 @@ import type {
1313} from '../models/types.js' ;
1414import { AuthService } from './auth-service.js' ;
1515import { track } from '../analytics/posthog.js' ;
16+ import type { StorageInterface } from '../storage/storage-interface.js' ;
1617
1718/** Generate a random claim token */
1819function generateClaimToken ( ) : string {
@@ -24,15 +25,77 @@ export class AgentService {
2425 private nameIndex : Map < string , string > = new Map ( ) ; // name -> id
2526 private claimTokenIndex : Map < string , string > = new Map ( ) ; // claimToken -> id
2627 private authService : AuthService ;
28+ private storage ?: StorageInterface ;
2729
28- constructor ( authService : AuthService ) {
30+ constructor ( authService : AuthService , storage ?: StorageInterface ) {
2931 this . authService = authService ;
32+ this . storage = storage ;
33+ }
34+
35+ /**
36+ * Initialize agents from storage
37+ */
38+ async initializeAgents ( ) : Promise < void > {
39+ if ( ! this . storage ) return ;
40+
41+ try {
42+ const storedAgents = await this . storage . getAllAgents ( ) ;
43+ for ( const stored of storedAgents ) {
44+ const agent : Agent = {
45+ id : stored . id ,
46+ name : stored . name ,
47+ token : stored . token || '' ,
48+ capabilities : stored . capabilities || [ ] ,
49+ permissions : ( stored . permissions as Permission [ ] ) || this . authService . createDefaultPermissions ( ) ,
50+ status : 'offline' , // Always start offline, let them reconnect
51+ metadata : stored . metadata || { } ,
52+ createdAt : stored . createdAt ,
53+ lastSeenAt : stored . lastSeenAt || stored . createdAt ,
54+ claimToken : stored . claimToken ,
55+ registrationStatus : stored . registrationStatus as RegistrationStatus || 'claimed' ,
56+ } ;
57+
58+ this . agents . set ( agent . id , agent ) ;
59+ this . nameIndex . set ( agent . name , agent . id ) ;
60+ if ( agent . claimToken ) {
61+ this . claimTokenIndex . set ( agent . claimToken , agent . id ) ;
62+ }
63+ }
64+ console . log ( `[AgentService] Loaded ${ storedAgents . length } agents from storage` ) ;
65+ } catch ( err ) {
66+ console . error ( '[AgentService] Failed to load agents from storage:' , err ) ;
67+ }
68+ }
69+
70+ /**
71+ * Save agent to storage
72+ */
73+ private async saveToStorage ( agent : Agent ) : Promise < void > {
74+ if ( ! this . storage ) return ;
75+
76+ try {
77+ await this . storage . saveAgent ( {
78+ id : agent . id ,
79+ name : agent . name ,
80+ capabilities : agent . capabilities ,
81+ permissions : agent . permissions as { resource : string ; actions : string [ ] } [ ] ,
82+ status : agent . status ,
83+ metadata : agent . metadata ,
84+ lastSeenAt : agent . lastSeenAt ,
85+ createdAt : agent . createdAt ,
86+ token : agent . token ,
87+ claimToken : agent . claimToken ,
88+ registrationStatus : agent . registrationStatus ,
89+ } ) ;
90+ } catch ( err ) {
91+ console . error ( '[AgentService] Failed to save agent to storage:' , err ) ;
92+ }
3093 }
3194
3295 /**
3396 * Register a new agent (direct registration - legacy)
3497 */
35- register ( registration : AgentRegistration ) : Agent {
98+ async register ( registration : AgentRegistration ) : Promise < Agent > {
3699 // Check for duplicate name
37100 if ( this . nameIndex . has ( registration . name ) ) {
38101 throw new Error ( `Agent with name "${ registration . name } " already exists` ) ;
@@ -61,6 +124,9 @@ export class AgentService {
61124 this . agents . set ( id , agent ) ;
62125 this . nameIndex . set ( registration . name , id ) ;
63126
127+ // Persist to storage
128+ await this . saveToStorage ( agent ) ;
129+
64130 // Track registration
65131 track ( id , 'agent_registered' , { agent_id : id , agent_name : agent . name } ) ;
66132
@@ -72,7 +138,7 @@ export class AgentService {
72138 * Create a pending registration (human-initiated)
73139 * Returns a claim token that must be used by the agent to complete registration
74140 */
75- createPendingRegistration ( name : string ) : { id : string ; name : string ; claimToken : string } {
141+ async createPendingRegistration ( name : string ) : Promise < { id : string ; name : string ; claimToken : string } > {
76142 // Check for duplicate name
77143 if ( this . nameIndex . has ( name ) ) {
78144 throw new Error ( `Agent with name "${ name } " already exists` ) ;
@@ -101,6 +167,9 @@ export class AgentService {
101167 this . nameIndex . set ( name , id ) ;
102168 this . claimTokenIndex . set ( claimToken , id ) ;
103169
170+ // Persist to storage
171+ await this . saveToStorage ( agent ) ;
172+
104173 console . log ( `[AgentService] Created pending registration: ${ name } (${ id } )` ) ;
105174 return { id, name, claimToken } ;
106175 }
@@ -109,8 +178,37 @@ export class AgentService {
109178 * Claim a pending registration (agent-initiated)
110179 * Agent provides the claim token to complete registration and receive auth token
111180 */
112- claimRegistration ( claimToken : string , capabilities ?: string [ ] ) : Agent {
113- const id = this . claimTokenIndex . get ( claimToken ) ;
181+ async claimRegistration ( claimToken : string , capabilities ?: string [ ] ) : Promise < Agent > {
182+ // First check in-memory index
183+ let id = this . claimTokenIndex . get ( claimToken ) ;
184+
185+ // If not in memory, try loading from storage (another replica may have created it)
186+ if ( ! id && this . storage ) {
187+ const storedAgent = await this . storage . getAgentByClaimToken ( claimToken ) ;
188+ if ( storedAgent ) {
189+ // Load into memory
190+ const agent : Agent = {
191+ id : storedAgent . id ,
192+ name : storedAgent . name ,
193+ token : storedAgent . token || '' ,
194+ capabilities : storedAgent . capabilities || [ ] ,
195+ permissions : ( storedAgent . permissions as Permission [ ] ) || this . authService . createDefaultPermissions ( ) ,
196+ status : 'offline' ,
197+ metadata : storedAgent . metadata || { } ,
198+ createdAt : storedAgent . createdAt ,
199+ lastSeenAt : storedAgent . lastSeenAt || storedAgent . createdAt ,
200+ claimToken : storedAgent . claimToken ,
201+ registrationStatus : storedAgent . registrationStatus as RegistrationStatus || 'pending' ,
202+ } ;
203+ this . agents . set ( agent . id , agent ) ;
204+ this . nameIndex . set ( agent . name , agent . id ) ;
205+ if ( agent . claimToken ) {
206+ this . claimTokenIndex . set ( agent . claimToken , agent . id ) ;
207+ }
208+ id = agent . id ;
209+ }
210+ }
211+
114212 if ( ! id ) {
115213 throw new Error ( 'Invalid or expired claim token' ) ;
116214 }
@@ -133,6 +231,9 @@ export class AgentService {
133231 // Remove from claim token index
134232 this . claimTokenIndex . delete ( claimToken ) ;
135233
234+ // Persist to storage
235+ await this . saveToStorage ( agent ) ;
236+
136237 // Track registration
137238 track ( id , 'agent_claimed' , { agent_id : id , agent_name : agent . name } ) ;
138239
0 commit comments