@ai-setting/roy-agent-core 1.5.41 → 1.5.43

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.
@@ -0,0 +1,313 @@
1
+ import {
2
+ getXDGPath
3
+ } from "./roy-agent-core-qxnbvgwe.js";
4
+ import {
5
+ TracedAs,
6
+ init_decorator
7
+ } from "./roy-agent-core-q5qj0fes.js";
8
+ import {
9
+ createLogger,
10
+ init_logger
11
+ } from "./roy-agent-core-10n2jh7p.js";
12
+ import {
13
+ __legacyDecorateClassTS
14
+ } from "./roy-agent-core-fs0mn2jk.js";
15
+
16
+ // src/env/agent/agent-registry.ts
17
+ import { readdir, readFile, writeFile, mkdir, unlink } from "fs/promises";
18
+ import { existsSync } from "fs";
19
+ import { join } from "path";
20
+ import { z } from "zod";
21
+ import yaml from "yaml";
22
+ init_logger();
23
+ init_decorator();
24
+ var logger = createLogger("agent:registry");
25
+ var AgentConfigSchema = z.object({
26
+ name: z.string(),
27
+ type: z.enum(["primary", "sub"]),
28
+ description: z.string().optional(),
29
+ systemPromptRef: z.string().optional(),
30
+ systemPrompt: z.string().optional(),
31
+ model: z.string().optional(),
32
+ maxIterations: z.number().optional(),
33
+ maxErrorRetries: z.number().optional(),
34
+ allowedTools: z.array(z.string()).optional(),
35
+ deniedTools: z.array(z.string()).optional(),
36
+ toolTimeout: z.number().optional(),
37
+ toolRetries: z.number().optional(),
38
+ doomLoopThreshold: z.number().optional(),
39
+ filterHistory: z.boolean().optional()
40
+ });
41
+
42
+ class AgentRegistry {
43
+ agents = new Map;
44
+ configDir;
45
+ configComponent;
46
+ promptComponent;
47
+ resolvePromptComponent;
48
+ constructor(options) {
49
+ this.configComponent = options.configComponent;
50
+ this.promptComponent = options.promptComponent ?? null;
51
+ this.resolvePromptComponent = options.resolvePromptComponent;
52
+ const customDir = this.configComponent?.get?.("agent.configDir");
53
+ if (customDir && typeof customDir === "string" && customDir.length > 0) {
54
+ this.configDir = customDir;
55
+ } else if (options.baseDir) {
56
+ this.configDir = join(options.baseDir, "agents");
57
+ } else {
58
+ this.configDir = join(getXDGPath("data"), "agents");
59
+ }
60
+ logger.debug(`[AgentRegistry] Config dir: ${this.configDir}`);
61
+ }
62
+ getConfigDir() {
63
+ return this.configDir;
64
+ }
65
+ hasAgent(name) {
66
+ return this.agents.has(name);
67
+ }
68
+ get(name) {
69
+ return this.agents.get(name);
70
+ }
71
+ list() {
72
+ return Array.from(this.agents.values());
73
+ }
74
+ listAgentsByType(type) {
75
+ return this.list().filter((agent) => agent.type === type);
76
+ }
77
+ register(agent) {
78
+ if (!agent.name) {
79
+ throw new Error("Agent name is required");
80
+ }
81
+ this.agents.set(agent.name, { ...agent });
82
+ logger.info(`[AgentRegistry] Registered agent: ${agent.name}`, { type: agent.type });
83
+ }
84
+ unregister(name) {
85
+ const deleted = this.agents.delete(name);
86
+ if (deleted) {
87
+ logger.info(`[AgentRegistry] Unregistered agent: ${name}`);
88
+ }
89
+ return deleted;
90
+ }
91
+ clear() {
92
+ this.agents.clear();
93
+ logger.info("[AgentRegistry] Cleared all agents");
94
+ }
95
+ async getSystemPrompt(name) {
96
+ const agent = this.agents.get(name);
97
+ if (!agent) {
98
+ return;
99
+ }
100
+ const promptComponent = this.getActivePromptComponent();
101
+ if (agent.systemPromptRef) {
102
+ try {
103
+ const prompt = promptComponent?.get?.(agent.systemPromptRef);
104
+ if (prompt) {
105
+ logger.debug(`[AgentRegistry] Resolved systemPromptRef: ${agent.systemPromptRef}`);
106
+ return prompt;
107
+ }
108
+ if (promptComponent?.getPrompt) {
109
+ const rendered = await promptComponent.getPrompt(agent.systemPromptRef);
110
+ if (rendered) {
111
+ return rendered;
112
+ }
113
+ }
114
+ if (agent.systemPrompt) {
115
+ logger.debug(`[AgentRegistry] systemPromptRef not found, falling back to inline`);
116
+ return agent.systemPrompt;
117
+ }
118
+ return;
119
+ } catch (error) {
120
+ logger.warn(`[AgentRegistry] Failed to resolve systemPromptRef: ${agent.systemPromptRef}`, { error });
121
+ return agent.systemPrompt;
122
+ }
123
+ }
124
+ return agent.systemPrompt;
125
+ }
126
+ getActivePromptComponent() {
127
+ return this.resolvePromptComponent?.() ?? this.promptComponent;
128
+ }
129
+ getInlineSystemPrompt(name) {
130
+ const agent = this.agents.get(name);
131
+ if (!agent) {
132
+ return;
133
+ }
134
+ return agent.systemPrompt;
135
+ }
136
+ async load() {
137
+ try {
138
+ return await this.loadFromDirectory(this.configDir);
139
+ } catch (error) {
140
+ logger.warn(`[AgentRegistry] Failed to load agents: ${error}`);
141
+ return 0;
142
+ }
143
+ }
144
+ async loadFromDirectory(directory) {
145
+ let loaded = 0;
146
+ try {
147
+ if (!existsSync(directory)) {
148
+ logger.debug(`[AgentRegistry] Config directory does not exist: ${directory}`);
149
+ return 0;
150
+ }
151
+ const files = await readdir(directory, { withFileTypes: true });
152
+ for (const file of files) {
153
+ const fullPath = join(directory, file.name);
154
+ if (file.isDirectory()) {
155
+ continue;
156
+ }
157
+ if (!isAgentConfigFile(file.name)) {
158
+ continue;
159
+ }
160
+ try {
161
+ const content = await readFile(fullPath, "utf-8");
162
+ const agent = this.parseAgentConfig(content, fullPath);
163
+ if (agent) {
164
+ this.agents.set(agent.name, agent);
165
+ logger.info(`[AgentRegistry] Loaded agent: ${agent.name} from ${file.name}`);
166
+ loaded++;
167
+ }
168
+ } catch (error) {
169
+ logger.warn(`[AgentRegistry] Failed to load agent from ${file.name}: ${error}`);
170
+ }
171
+ }
172
+ logger.info(`[AgentRegistry] Loaded ${loaded} agents from ${directory}`);
173
+ } catch (error) {
174
+ logger.warn(`[AgentRegistry] Error reading config directory: ${error}`);
175
+ }
176
+ return loaded;
177
+ }
178
+ parseAgentConfig(content, source) {
179
+ try {
180
+ const parsed = yaml.parse(content);
181
+ const result = AgentConfigSchema.safeParse(parsed);
182
+ if (!result.success) {
183
+ logger.warn(`[AgentRegistry] Invalid agent config: ${source}`, {
184
+ errors: result.error.errors
185
+ });
186
+ return null;
187
+ }
188
+ return result.data;
189
+ } catch (error) {
190
+ logger.error(`[AgentRegistry] Failed to parse agent config: ${source}`, { error });
191
+ return null;
192
+ }
193
+ }
194
+ async saveAgent(agent) {
195
+ try {
196
+ await this.ensureConfigDir();
197
+ const yamlContent = this.agentToYaml(agent);
198
+ const filePath = join(this.configDir, `${agent.name}.yaml`);
199
+ await writeFile(filePath, yamlContent, "utf-8");
200
+ this.agents.set(agent.name, agent);
201
+ logger.info(`[AgentRegistry] Saved agent: ${agent.name} to ${filePath}`);
202
+ return true;
203
+ } catch (error) {
204
+ logger.error(`[AgentRegistry] Failed to save agent: ${agent.name}`, { error });
205
+ return false;
206
+ }
207
+ }
208
+ async ensureConfigDir() {
209
+ if (!existsSync(this.configDir)) {
210
+ await mkdir(this.configDir, { recursive: true });
211
+ logger.debug(`[AgentRegistry] Created config directory: ${this.configDir}`);
212
+ }
213
+ }
214
+ agentToYaml(agent) {
215
+ const config = {
216
+ name: agent.name,
217
+ type: agent.type,
218
+ ...agent.description !== undefined ? { description: agent.description } : {},
219
+ ...agent.systemPromptRef !== undefined ? { systemPromptRef: agent.systemPromptRef } : {},
220
+ ...agent.systemPrompt !== undefined ? { systemPrompt: agent.systemPrompt } : {},
221
+ ...agent.model !== undefined ? { model: agent.model } : {},
222
+ ...agent.maxIterations !== undefined ? { maxIterations: agent.maxIterations } : {},
223
+ ...agent.maxErrorRetries !== undefined ? { maxErrorRetries: agent.maxErrorRetries } : {},
224
+ ...agent.allowedTools !== undefined ? { allowedTools: agent.allowedTools } : {},
225
+ ...agent.deniedTools !== undefined ? { deniedTools: agent.deniedTools } : {},
226
+ ...agent.toolTimeout !== undefined ? { toolTimeout: agent.toolTimeout } : {},
227
+ ...agent.toolRetries !== undefined ? { toolRetries: agent.toolRetries } : {},
228
+ ...agent.doomLoopThreshold !== undefined ? { doomLoopThreshold: agent.doomLoopThreshold } : {},
229
+ ...agent.filterHistory !== undefined ? { filterHistory: agent.filterHistory } : {}
230
+ };
231
+ return yaml.stringify(config).trimEnd() + `
232
+ `;
233
+ }
234
+ async deleteAgent(name) {
235
+ const agent = this.agents.get(name);
236
+ if (!agent) {
237
+ logger.warn(`[AgentRegistry] Agent not found: ${name}`);
238
+ return false;
239
+ }
240
+ try {
241
+ const filePath = join(this.configDir, `${name}.yaml`);
242
+ if (existsSync(filePath)) {
243
+ await unlink(filePath);
244
+ logger.info(`[AgentRegistry] Deleted agent file: ${filePath}`);
245
+ }
246
+ this.agents.delete(name);
247
+ return true;
248
+ } catch (error) {
249
+ logger.error(`[AgentRegistry] Failed to delete agent: ${name}`, { error });
250
+ return false;
251
+ }
252
+ }
253
+ exportAgents(options = {}) {
254
+ const agents = this.list();
255
+ if (options.format === "json") {
256
+ return agents;
257
+ }
258
+ return agents;
259
+ }
260
+ exportAgentsAsJson() {
261
+ return JSON.stringify(this.list(), null, 2);
262
+ }
263
+ size() {
264
+ return this.agents.size;
265
+ }
266
+ configDirExists() {
267
+ return existsSync(this.configDir);
268
+ }
269
+ getAgentFilePath(name) {
270
+ return join(this.configDir, `${name}.yaml`);
271
+ }
272
+ }
273
+ __legacyDecorateClassTS([
274
+ TracedAs("agent.registry.register", { recordParams: true, recordResult: true, log: true })
275
+ ], AgentRegistry.prototype, "register", null);
276
+ __legacyDecorateClassTS([
277
+ TracedAs("agent.registry.unregister", { recordParams: true, recordResult: true, log: true })
278
+ ], AgentRegistry.prototype, "unregister", null);
279
+ __legacyDecorateClassTS([
280
+ TracedAs("agent.registry.clear", { recordParams: false, recordResult: true, log: true })
281
+ ], AgentRegistry.prototype, "clear", null);
282
+ __legacyDecorateClassTS([
283
+ TracedAs("agent.registry.getSystemPrompt", { recordParams: true, recordResult: true, log: true })
284
+ ], AgentRegistry.prototype, "getSystemPrompt", null);
285
+ __legacyDecorateClassTS([
286
+ TracedAs("agent.registry.load", { recordParams: false, recordResult: true, log: true })
287
+ ], AgentRegistry.prototype, "load", null);
288
+ __legacyDecorateClassTS([
289
+ TracedAs("agent.registry.loadFromDirectory", { recordParams: true, recordResult: true, log: true })
290
+ ], AgentRegistry.prototype, "loadFromDirectory", null);
291
+ function isAgentConfigFile(fileName) {
292
+ return fileName.endsWith(".yaml") || fileName.endsWith(".yml");
293
+ }
294
+ var DEFAULT_SUBAGENT_PROMPT = `You are a subagent created by the main agent to handle a specific task.
295
+
296
+ ## Your Role
297
+ - You were created to handle: {task_description}
298
+ - Complete this task. That's your entire purpose.
299
+ - You are NOT the main agent. Don't try to be.
300
+
301
+ ## Rules
302
+ 1. **Stay focused** - Do your assigned task, nothing else
303
+ 2. **Complete the task** - Your final message will be automatically reported to the main agent
304
+ 3. **Don't initiate** - No heartbeats, no proactive actions, no side quests
305
+ 4. **Be ephemeral** - You may be terminated after task completion. That's fine.
306
+ 5. **No nested delegation** - Do NOT use delegate_task or stop_task tools. Complete the task yourself.
307
+
308
+ ## Execution
309
+ - Use the available tools to complete the task
310
+ - If you need more information, ask the main agent through the result
311
+ - Return a clear summary of what you did and the results`;
312
+
313
+ export { AgentRegistry, DEFAULT_SUBAGENT_PROMPT };
@@ -1048,6 +1048,14 @@ class ConfigComponent extends BaseComponent {
1048
1048
  }
1049
1049
  this.sourceUnwatchFns.clear();
1050
1050
  }
1051
+ shutdown() {
1052
+ this.unwatchAll();
1053
+ this.persistQueue.clear();
1054
+ }
1055
+ async onStop() {
1056
+ this.shutdown();
1057
+ await super.onStop();
1058
+ }
1051
1059
  getSources() {
1052
1060
  return [...this.sources];
1053
1061
  }
@@ -2,6 +2,9 @@ import {
2
2
  AskUserError,
3
3
  init_workflow_hil
4
4
  } from "./roy-agent-core-e25xkv53.js";
5
+ import {
6
+ AgentRegistry
7
+ } from "./roy-agent-core-fg3j215p.js";
5
8
  import {
6
9
  ContextError
7
10
  } from "./roy-agent-core-ctdhjv68.js";
@@ -166,6 +169,7 @@ class AgentComponent extends BaseComponent {
166
169
  configWatcher;
167
170
  messageConverter = new SessionMessageConverter;
168
171
  _constructorConfig;
172
+ registry;
169
173
  runCounter = 0;
170
174
  constructor(options = {}) {
171
175
  super();
@@ -185,6 +189,7 @@ class AgentComponent extends BaseComponent {
185
189
  }
186
190
  this.configComponent = options.configComponent;
187
191
  await this.registerConfig(options);
192
+ await this.initRegistry(options.configComponent);
188
193
  this.setStatus("running");
189
194
  }
190
195
  async registerConfig(options) {
@@ -271,6 +276,70 @@ class AgentComponent extends BaseComponent {
271
276
  newValue: event.newValue
272
277
  });
273
278
  }
279
+ async initRegistry(configComponent) {
280
+ try {
281
+ this.registry = new AgentRegistry({
282
+ configComponent,
283
+ resolvePromptComponent: () => this.env?.getComponent?.("prompt") ?? null
284
+ });
285
+ const loadedCount = await this.registry.load();
286
+ logger.info(`[AgentComponent] Loaded ${loadedCount} agents from config directory`);
287
+ for (const agentDef of this.registry.list()) {
288
+ if (!this.agents.has(agentDef.name)) {
289
+ const systemPrompt = await this.registry.getSystemPrompt(agentDef.name);
290
+ this.registerAgent(agentDef.name, {
291
+ type: agentDef.type,
292
+ systemPrompt,
293
+ systemPromptRef: agentDef.systemPromptRef,
294
+ allowedTools: agentDef.allowedTools,
295
+ deniedTools: agentDef.deniedTools,
296
+ model: agentDef.model,
297
+ maxIterations: agentDef.maxIterations
298
+ });
299
+ logger.debug(`[AgentComponent] Registered agent from config: ${agentDef.name}`);
300
+ }
301
+ }
302
+ } catch (error) {
303
+ logger.warn(`[AgentComponent] Failed to initialize AgentRegistry: ${error}`);
304
+ }
305
+ }
306
+ async syncRegistryAgentsFromConfig() {
307
+ if (!this.registry) {
308
+ return;
309
+ }
310
+ for (const agentDef of this.registry.list()) {
311
+ const systemPrompt = await this.registry.getSystemPrompt(agentDef.name);
312
+ const existing = this.agents.get(agentDef.name);
313
+ if (existing) {
314
+ if (systemPrompt !== undefined) {
315
+ existing.config.systemPrompt = systemPrompt;
316
+ }
317
+ if (agentDef.systemPromptRef) {
318
+ existing.config.systemPromptRef = agentDef.systemPromptRef;
319
+ }
320
+ } else {
321
+ this.registerAgent(agentDef.name, {
322
+ type: agentDef.type,
323
+ systemPrompt,
324
+ systemPromptRef: agentDef.systemPromptRef,
325
+ allowedTools: agentDef.allowedTools,
326
+ deniedTools: agentDef.deniedTools,
327
+ model: agentDef.model,
328
+ maxIterations: agentDef.maxIterations
329
+ });
330
+ }
331
+ logger.debug(`[AgentComponent] Synced registry agent: ${agentDef.name}`);
332
+ }
333
+ }
334
+ getRegistry() {
335
+ return this.registry;
336
+ }
337
+ getAgentFromRegistry(name) {
338
+ return this.registry?.get(name);
339
+ }
340
+ listAgentsFromRegistry() {
341
+ return this.registry?.list() ?? [];
342
+ }
274
343
  async onStop() {
275
344
  logger.info("[AgentComponent] Stopping and cleaning up resources...");
276
345
  for (const [name, agent] of this.agents) {
@@ -301,6 +370,27 @@ class AgentComponent extends BaseComponent {
301
370
  });
302
371
  }
303
372
  }
373
+ async resolveSystemPrompt(agent, agentName) {
374
+ if (agent.config.systemPrompt) {
375
+ return;
376
+ }
377
+ if (!agent.config.systemPromptRef) {
378
+ return;
379
+ }
380
+ if (!this.registry) {
381
+ logger.debug(`[resolveSystemPrompt] No registry available for ${agentName}`);
382
+ return;
383
+ }
384
+ try {
385
+ const resolvedPrompt = await this.registry.getSystemPrompt(agentName);
386
+ if (resolvedPrompt) {
387
+ agent.config.systemPrompt = resolvedPrompt;
388
+ logger.debug(`[AgentComponent] Resolved systemPrompt for agent: ${agentName}`);
389
+ }
390
+ } catch (error) {
391
+ logger.warn(`[AgentComponent] Failed to resolve systemPrompt for agent ${agentName}: ${error}`);
392
+ }
393
+ }
304
394
  getAgent(agentName) {
305
395
  return this.agents.get(agentName);
306
396
  }
@@ -331,7 +421,8 @@ class AgentComponent extends BaseComponent {
331
421
  doomLoopThreshold: config.doomLoopThreshold ?? this.config.defaultAgent.doomLoopThreshold,
332
422
  toolTimeout: config.toolTimeout ?? this.config.defaultAgent.toolTimeout,
333
423
  toolRetries: config.toolRetries ?? this.config.defaultAgent.toolRetries,
334
- systemPrompt: config.systemPrompt ?? this.config.defaultAgent.systemPrompt,
424
+ systemPrompt: config.systemPrompt ?? (config.systemPromptRef ? undefined : this.config.defaultAgent.systemPrompt),
425
+ systemPromptRef: config.systemPromptRef,
335
426
  model: config.model ?? this.config.defaultAgent.model,
336
427
  behaviorSpecId: config.behaviorSpecId ?? this.config.defaultAgent.behaviorSpecId,
337
428
  allowedTools: config.allowedTools ?? this.config.defaultAgent.allowedTools,
@@ -433,6 +524,7 @@ class AgentComponent extends BaseComponent {
433
524
  if (!this.doomLoopCaches.has(runId)) {
434
525
  this.doomLoopCaches.set(runId, new Map);
435
526
  }
527
+ await this.resolveSystemPrompt(agent, agentName);
436
528
  const effectiveContext = {
437
529
  ...context,
438
530
  agentType: agent.config.type,
@@ -1138,6 +1230,15 @@ ${ctx.context.additionInfo}`
1138
1230
  return messages;
1139
1231
  }
1140
1232
  }
1233
+ __legacyDecorateClassTS([
1234
+ TracedAs("agent.component.initRegistry", { recordParams: true, recordResult: true, log: true })
1235
+ ], AgentComponent.prototype, "initRegistry", null);
1236
+ __legacyDecorateClassTS([
1237
+ TracedAs("agent.component.syncRegistryAgentsFromConfig", { recordParams: false, recordResult: true, log: true })
1238
+ ], AgentComponent.prototype, "syncRegistryAgentsFromConfig", null);
1239
+ __legacyDecorateClassTS([
1240
+ TracedAs("agent.component.resolveSystemPrompt", { recordParams: true, recordResult: true, log: true })
1241
+ ], AgentComponent.prototype, "resolveSystemPrompt", null);
1141
1242
  __legacyDecorateClassTS([
1142
1243
  TracedAs("agent.component.run", { recordParams: true, recordResult: true, log: true })
1143
1244
  ], AgentComponent.prototype, "_run", null);
@@ -318,10 +318,25 @@ class McpLoader {
318
318
  return;
319
319
  try {
320
320
  await entry.client.close();
321
- this.clients.delete(name);
322
- logger2.info(`[McpLoader] Disconnected from ${name}`);
323
321
  } catch (error) {
324
- logger2.warn(`[McpLoader] Error disconnecting from ${name}`, { error });
322
+ logger2.warn(`[McpLoader] Error closing client for ${name}`, { error });
323
+ }
324
+ await this.closeTransport(entry.transport, name);
325
+ this.clients.delete(name);
326
+ logger2.info(`[McpLoader] Disconnected from ${name}`);
327
+ }
328
+ async closeTransport(transport, name) {
329
+ try {
330
+ const transportAny = transport;
331
+ if (typeof transportAny.close === "function") {
332
+ await transportAny.close();
333
+ }
334
+ const child = transportAny._process ?? transportAny.process;
335
+ if (child?.pid && !child.killed) {
336
+ child.kill?.("SIGTERM");
337
+ }
338
+ } catch (error) {
339
+ logger2.warn(`[McpLoader] Error closing transport for ${name}`, { error });
325
340
  }
326
341
  }
327
342
  async disconnectAll() {