@agentuity/cli 0.0.69 → 0.0.70

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 (153) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/cmd/ai/capabilities/show.d.ts.map +1 -1
  4. package/dist/cmd/ai/capabilities/show.js +0 -13
  5. package/dist/cmd/ai/capabilities/show.js.map +1 -1
  6. package/dist/cmd/ai/prompt/agent.js +1 -1
  7. package/dist/cmd/ai/prompt/api.js +1 -1
  8. package/dist/cmd/build/ast.d.ts +1 -2
  9. package/dist/cmd/build/ast.d.ts.map +1 -1
  10. package/dist/cmd/build/ast.js +261 -260
  11. package/dist/cmd/build/ast.js.map +1 -1
  12. package/dist/cmd/build/bundler.d.ts +2 -1
  13. package/dist/cmd/build/bundler.d.ts.map +1 -1
  14. package/dist/cmd/build/bundler.js +11 -2
  15. package/dist/cmd/build/bundler.js.map +1 -1
  16. package/dist/cmd/build/index.js +1 -1
  17. package/dist/cmd/build/index.js.map +1 -1
  18. package/dist/cmd/build/plugin.d.ts.map +1 -1
  19. package/dist/cmd/build/plugin.js +152 -414
  20. package/dist/cmd/build/plugin.js.map +1 -1
  21. package/dist/cmd/build/route-registry.d.ts +36 -0
  22. package/dist/cmd/build/route-registry.d.ts.map +1 -0
  23. package/dist/cmd/build/route-registry.js +151 -0
  24. package/dist/cmd/build/route-registry.js.map +1 -0
  25. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  26. package/dist/cmd/cloud/deploy.js +1 -0
  27. package/dist/cmd/cloud/deploy.js.map +1 -1
  28. package/dist/cmd/cloud/index.d.ts.map +1 -1
  29. package/dist/cmd/cloud/index.js +0 -2
  30. package/dist/cmd/cloud/index.js.map +1 -1
  31. package/dist/cmd/dev/index.js +3 -3
  32. package/dist/cmd/dev/index.js.map +1 -1
  33. package/dist/cmd/dev/sync.d.ts.map +1 -1
  34. package/dist/cmd/dev/sync.js +0 -15
  35. package/dist/cmd/dev/sync.js.map +1 -1
  36. package/dist/cmd/dev/templates.d.ts.map +1 -1
  37. package/dist/cmd/dev/templates.js +11 -35
  38. package/dist/cmd/dev/templates.js.map +1 -1
  39. package/dist/cmd/profile/create.d.ts.map +1 -1
  40. package/dist/cmd/profile/create.js +0 -1
  41. package/dist/cmd/profile/create.js.map +1 -1
  42. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  43. package/dist/cmd/project/template-flow.js +64 -53
  44. package/dist/cmd/project/template-flow.js.map +1 -1
  45. package/dist/config.d.ts.map +1 -1
  46. package/dist/config.js +0 -3
  47. package/dist/config.js.map +1 -1
  48. package/dist/tui/box.d.ts +19 -0
  49. package/dist/tui/box.d.ts.map +1 -0
  50. package/dist/tui/box.js +160 -0
  51. package/dist/tui/box.js.map +1 -0
  52. package/dist/tui/colors.d.ts +20 -0
  53. package/dist/tui/colors.d.ts.map +1 -0
  54. package/dist/tui/colors.js +52 -0
  55. package/dist/tui/colors.js.map +1 -0
  56. package/dist/tui/group.d.ts +25 -0
  57. package/dist/tui/group.d.ts.map +1 -0
  58. package/dist/tui/group.js +32 -0
  59. package/dist/tui/group.js.map +1 -0
  60. package/dist/tui/prompt.d.ts +65 -0
  61. package/dist/tui/prompt.d.ts.map +1 -0
  62. package/dist/tui/prompt.js +377 -0
  63. package/dist/tui/prompt.js.map +1 -0
  64. package/dist/tui/symbols.d.ts +32 -0
  65. package/dist/tui/symbols.d.ts.map +1 -0
  66. package/dist/tui/symbols.js +52 -0
  67. package/dist/tui/symbols.js.map +1 -0
  68. package/dist/tui.d.ts +6 -0
  69. package/dist/tui.d.ts.map +1 -1
  70. package/dist/tui.js +6 -0
  71. package/dist/tui.js.map +1 -1
  72. package/dist/types.d.ts +0 -24
  73. package/dist/types.d.ts.map +1 -1
  74. package/dist/types.js +0 -1
  75. package/dist/types.js.map +1 -1
  76. package/package.json +3 -3
  77. package/src/cmd/ai/capabilities/show.ts +0 -13
  78. package/src/cmd/ai/prompt/agent.ts +1 -1
  79. package/src/cmd/ai/prompt/api.ts +1 -1
  80. package/src/cmd/build/ast.ts +364 -334
  81. package/src/cmd/build/bundler.ts +16 -1
  82. package/src/cmd/build/index.ts +1 -1
  83. package/src/cmd/build/plugin.ts +171 -493
  84. package/src/cmd/build/route-registry.ts +198 -0
  85. package/src/cmd/cloud/deploy.ts +1 -0
  86. package/src/cmd/cloud/index.ts +0 -2
  87. package/src/cmd/dev/index.ts +3 -3
  88. package/src/cmd/dev/sync.ts +0 -28
  89. package/src/cmd/dev/templates.ts +11 -35
  90. package/src/cmd/profile/create.ts +0 -1
  91. package/src/cmd/project/template-flow.ts +77 -57
  92. package/src/config.ts +0 -3
  93. package/src/tui/box.ts +202 -0
  94. package/src/tui/colors.ts +55 -0
  95. package/src/tui/group.ts +56 -0
  96. package/src/tui/prompt.ts +506 -0
  97. package/src/tui/symbols.ts +64 -0
  98. package/src/tui.ts +14 -0
  99. package/src/types.ts +0 -1
  100. package/dist/cmd/cloud/objectstore/delete-bucket.d.ts +0 -3
  101. package/dist/cmd/cloud/objectstore/delete-bucket.d.ts.map +0 -1
  102. package/dist/cmd/cloud/objectstore/delete-bucket.js +0 -72
  103. package/dist/cmd/cloud/objectstore/delete-bucket.js.map +0 -1
  104. package/dist/cmd/cloud/objectstore/delete.d.ts +0 -3
  105. package/dist/cmd/cloud/objectstore/delete.d.ts.map +0 -1
  106. package/dist/cmd/cloud/objectstore/delete.js +0 -65
  107. package/dist/cmd/cloud/objectstore/delete.js.map +0 -1
  108. package/dist/cmd/cloud/objectstore/get.d.ts +0 -3
  109. package/dist/cmd/cloud/objectstore/get.d.ts.map +0 -1
  110. package/dist/cmd/cloud/objectstore/get.js +0 -75
  111. package/dist/cmd/cloud/objectstore/get.js.map +0 -1
  112. package/dist/cmd/cloud/objectstore/index.d.ts +0 -3
  113. package/dist/cmd/cloud/objectstore/index.d.ts.map +0 -1
  114. package/dist/cmd/cloud/objectstore/index.js +0 -36
  115. package/dist/cmd/cloud/objectstore/index.js.map +0 -1
  116. package/dist/cmd/cloud/objectstore/list-buckets.d.ts +0 -3
  117. package/dist/cmd/cloud/objectstore/list-buckets.d.ts.map +0 -1
  118. package/dist/cmd/cloud/objectstore/list-buckets.js +0 -46
  119. package/dist/cmd/cloud/objectstore/list-buckets.js.map +0 -1
  120. package/dist/cmd/cloud/objectstore/list-keys.d.ts +0 -3
  121. package/dist/cmd/cloud/objectstore/list-keys.d.ts.map +0 -1
  122. package/dist/cmd/cloud/objectstore/list-keys.js +0 -58
  123. package/dist/cmd/cloud/objectstore/list-keys.js.map +0 -1
  124. package/dist/cmd/cloud/objectstore/put.d.ts +0 -3
  125. package/dist/cmd/cloud/objectstore/put.d.ts.map +0 -1
  126. package/dist/cmd/cloud/objectstore/put.js +0 -66
  127. package/dist/cmd/cloud/objectstore/put.js.map +0 -1
  128. package/dist/cmd/cloud/objectstore/repl.d.ts +0 -3
  129. package/dist/cmd/cloud/objectstore/repl.d.ts.map +0 -1
  130. package/dist/cmd/cloud/objectstore/repl.js +0 -224
  131. package/dist/cmd/cloud/objectstore/repl.js.map +0 -1
  132. package/dist/cmd/cloud/objectstore/url.d.ts +0 -3
  133. package/dist/cmd/cloud/objectstore/url.d.ts.map +0 -1
  134. package/dist/cmd/cloud/objectstore/url.js +0 -64
  135. package/dist/cmd/cloud/objectstore/url.js.map +0 -1
  136. package/dist/cmd/cloud/objectstore/util.d.ts +0 -11
  137. package/dist/cmd/cloud/objectstore/util.d.ts.map +0 -1
  138. package/dist/cmd/cloud/objectstore/util.js +0 -18
  139. package/dist/cmd/cloud/objectstore/util.js.map +0 -1
  140. package/src/cmd/build/ast.test.ts +0 -418
  141. package/src/cmd/build/fix-duplicate-exports.test.ts +0 -387
  142. package/src/cmd/cloud/objectstore/delete-bucket.ts +0 -77
  143. package/src/cmd/cloud/objectstore/delete.ts +0 -67
  144. package/src/cmd/cloud/objectstore/get.ts +0 -77
  145. package/src/cmd/cloud/objectstore/index.ts +0 -36
  146. package/src/cmd/cloud/objectstore/list-buckets.ts +0 -51
  147. package/src/cmd/cloud/objectstore/list-keys.ts +0 -63
  148. package/src/cmd/cloud/objectstore/put.ts +0 -74
  149. package/src/cmd/cloud/objectstore/repl.ts +0 -239
  150. package/src/cmd/cloud/objectstore/url.ts +0 -67
  151. package/src/cmd/cloud/objectstore/util.ts +0 -29
  152. package/src/crypto/box.test.ts +0 -431
  153. package/src/env-util.test.ts +0 -194
@@ -1,22 +1,21 @@
1
1
  import type { BunPlugin } from 'bun';
2
- import { dirname, basename, join, resolve } from 'node:path';
2
+ import { dirname, join, resolve } from 'node:path';
3
3
  import { existsSync, writeFileSync, mkdirSync, unlinkSync } from 'node:fs';
4
4
  import type { BuildMetadata } from '@agentuity/server';
5
5
  import {
6
6
  parseAgentMetadata,
7
- parseRoute,
8
7
  parseEvalMetadata,
8
+ parseRoute,
9
9
  analyzeWorkbench,
10
- checkRouteConflicts,
11
10
  generateLifecycleTypes,
12
11
  findCreateAppEndPosition,
13
12
  } from './ast';
14
13
  import { StructuredError, type WorkbenchConfig } from '@agentuity/core';
15
14
  import { applyPatch, generatePatches } from './patch';
16
- import { detectSubagent } from '../../utils/detectSubagent';
17
15
  import { createLogger } from '@agentuity/server';
18
16
  import type { LogLevel } from '../../types';
19
17
  import { toCamelCase, toPascalCase } from '../../utils/string';
18
+ import { generateRouteRegistry, type RouteInfo } from './route-registry';
20
19
 
21
20
  /**
22
21
  * Setup lifecycle types by analyzing app.ts for setup() function
@@ -77,53 +76,21 @@ async function setupWorkbench(srcDir: string): Promise<WorkbenchConfig | null> {
77
76
 
78
77
  const workbenchConfig = analysis.config;
79
78
 
80
- // Check for route conflicts if workbench is being used
81
- if (workbenchConfig?.route) {
82
- const hasConflict = checkRouteConflicts(appContent, workbenchConfig.route);
83
- if (hasConflict) {
84
- const logger = createLogger((process.env.AGENTUITY_LOG_LEVEL as LogLevel) || 'info');
85
- logger.error(`🚨 Route conflict detected!\n`);
86
- logger.error(
87
- ` Workbench route '${workbenchConfig.route}' conflicts with existing application route`
88
- );
89
- logger.error(` Please use a different route or remove the conflicting route.\n`);
90
- }
91
- }
92
-
93
79
  return workbenchConfig;
94
80
  }
95
81
 
96
82
  const AgentIdentifierCollisionError = StructuredError('AgentIdentifierCollisionError');
97
- const SubAgentNameCollisionError = StructuredError('SubAgentNameCollisionError');
98
83
 
99
84
  function generateAgentRegistry(srcDir: string, agentInfo: Array<Record<string, string>>) {
100
- // Separate parent agents and subagents
101
- const parentAgents = agentInfo.filter((a) => !a.parent);
102
- const subagents = agentInfo.filter((a) => a.parent);
103
-
104
- // Group subagents by parent
105
- const subagentsByParent = new Map<string, Array<Record<string, string>>>();
106
- for (const subagent of subagents) {
107
- const parentName = subagent.parent!;
108
- if (!subagentsByParent.has(parentName)) {
109
- subagentsByParent.set(parentName, []);
110
- }
111
- subagentsByParent.get(parentName)!.push(subagent);
112
- }
113
-
114
85
  // Detect naming collisions in generated identifiers
115
- // Naming strategy: parent + child names are combined as `${parent}_${name}` then converted to camelCase
116
- // Example: parent="user", child="profile" → "user_profile" → "userProfile"
117
- // Potential collision: parent="user_profile", child="info" and parent="user", child="profile_info" both → "userProfileInfo"
118
86
  const generatedNames = new Set<string>();
119
87
  const collisions: string[] = [];
120
88
 
121
89
  for (const agent of agentInfo) {
122
- const fullName = agent.parent ? `${agent.parent}_${agent.name}` : agent.name;
123
- const camelName = toCamelCase(fullName);
90
+ const camelName = toCamelCase(agent.name);
124
91
 
125
92
  if (generatedNames.has(camelName)) {
126
- collisions.push(`Identifier collision detected: "${camelName}" (from "${fullName}")`);
93
+ collisions.push(`Identifier collision detected: "${camelName}" (from "${agent.name}")`);
127
94
  }
128
95
  generatedNames.add(camelName);
129
96
  }
@@ -138,158 +105,45 @@ function generateAgentRegistry(srcDir: string, agentInfo: Array<Record<string, s
138
105
  }
139
106
 
140
107
  // Generate imports for all agents
108
+ // Registry is now in .agentuity/, so imports need to go up one level and into src/
141
109
  const imports = agentInfo
142
- .map(({ name, path, parent }) => {
143
- const fullName = parent ? `${parent}.${name}` : name;
144
- const camelName = toCamelCase(fullName.replace('.', '_'));
145
- const relativePath = path.replace(/^\.\/agent\//, './');
110
+ .map(({ name, path }) => {
111
+ const camelName = toCamelCase(name);
112
+ const relativePath = path.replace(/^\.\/agent\//, '../src/agent/');
146
113
  return `import ${camelName}Agent from '${relativePath}';`;
147
114
  })
148
115
  .join('\n');
149
116
 
150
- // Evals are now imported in plugin.ts when agents are registered
151
- // No need to import them in registry.generated.ts
152
- const evalsImportsStr = '';
153
-
154
- // Validate that child property names don't collide with parent agent properties
155
- const reservedAgentProperties = ['metadata', 'run', 'inputSchema', 'outputSchema', 'stream'];
156
- for (const parentAgent of parentAgents) {
157
- const children = subagentsByParent.get(parentAgent.name) || [];
158
- for (const child of children) {
159
- const childPropertyName = toCamelCase(child.name);
160
-
161
- // Check for collision with reserved agent properties
162
- if (reservedAgentProperties.includes(childPropertyName)) {
163
- throw new SubAgentNameCollisionError({
164
- message:
165
- `Subagent property name collision detected: "${childPropertyName}" in parent "${parentAgent.name}"\n` +
166
- `The child name "${child.name}" conflicts with a reserved agent property (${reservedAgentProperties.join(', ')}).\n` +
167
- `Please rename the subagent to avoid this collision.`,
168
- });
169
- }
170
- }
171
- }
172
-
173
- // Generate nested registry structure
174
- const registryLines: string[] = [];
175
- for (const parentAgent of parentAgents) {
176
- const parentCamelName = toCamelCase(parentAgent.name);
177
- const children = subagentsByParent.get(parentAgent.name) || [];
178
-
179
- if (children.length === 0) {
180
- // No subagents, simple assignment
181
- registryLines.push(` ${parentCamelName}: ${parentCamelName}Agent,`);
182
- } else {
183
- // Has subagents, create nested structure using object spread (no mutation)
184
- registryLines.push(` ${parentCamelName}: {`);
185
- registryLines.push(` ...${parentCamelName}Agent,`);
186
- for (const child of children) {
187
- const childCamelName = toCamelCase(`${parentAgent.name}_${child.name}`);
188
- registryLines.push(` ${toCamelCase(child.name)}: ${childCamelName}Agent,`);
189
- }
190
- registryLines.push(` },`);
191
- }
192
- }
193
- const registry = registryLines.join('\n');
117
+ // Generate flat registry structure (no subagents)
118
+ const registry = agentInfo
119
+ .map(({ name }) => {
120
+ const camelName = toCamelCase(name);
121
+ return ` ${camelName}: ${camelName}Agent,`;
122
+ })
123
+ .join('\n');
194
124
 
195
125
  // Generate type exports for all agents
196
126
  const typeExports = agentInfo
197
- .map(({ name, parent }) => {
198
- const fullName = parent ? `${parent}_${name}` : name;
199
- const camelName = toCamelCase(fullName);
200
- const pascalName = toPascalCase(fullName);
127
+ .map(({ name }) => {
128
+ const camelName = toCamelCase(name);
129
+ const pascalName = toPascalCase(name);
201
130
  return `export type ${pascalName}AgentRunner = AgentRunner<typeof ${camelName}Agent['inputSchema'], typeof ${camelName}Agent['outputSchema'], typeof ${camelName}Agent['stream'] extends true ? true : false>;`;
202
131
  })
203
132
  .join('\n');
204
133
 
205
- // Generate nested agent type definitions for Hono Context augmentation
206
- const honoAgentTypeLines: string[] = [];
207
- for (const parentAgent of parentAgents) {
208
- const parentCamelName = toCamelCase(parentAgent.name);
209
- const children = subagentsByParent.get(parentAgent.name) || [];
210
-
211
- if (children.length === 0) {
212
- // No subagents
213
- honoAgentTypeLines.push(
214
- ` ${parentCamelName}: AgentRunner<LocalAgentRegistry['${parentCamelName}']['inputSchema'], LocalAgentRegistry['${parentCamelName}']['outputSchema'], LocalAgentRegistry['${parentCamelName}']['stream'] extends true ? true : false>;`
215
- );
216
- } else {
217
- // Has subagents - create intersection type
218
- honoAgentTypeLines.push(
219
- ` ${parentCamelName}: AgentRunner<LocalAgentRegistry['${parentCamelName}']['inputSchema'], LocalAgentRegistry['${parentCamelName}']['outputSchema'], LocalAgentRegistry['${parentCamelName}']['stream'] extends true ? true : false> & {`
220
- );
221
- for (const child of children) {
222
- const childCamelName = toCamelCase(child.name);
223
- const fullChildName = toCamelCase(`${parentAgent.name}_${child.name}`);
224
- honoAgentTypeLines.push(
225
- ` ${childCamelName}: AgentRunner<typeof ${fullChildName}Agent['inputSchema'], typeof ${fullChildName}Agent['outputSchema'], typeof ${fullChildName}Agent['stream'] extends true ? true : false>;`
226
- );
227
- }
228
- honoAgentTypeLines.push(` };`);
229
- }
230
- }
231
- const honoAgentTypes = honoAgentTypeLines.join('\n');
232
-
233
- // Generate agent type definitions for AgentRegistry interface augmentation
234
- const runtimeAgentTypeLines: string[] = [];
235
- for (const parentAgent of parentAgents) {
236
- const parentCamelName = toCamelCase(parentAgent.name);
237
- const children = subagentsByParent.get(parentAgent.name) || [];
238
-
239
- if (children.length === 0) {
240
- // No subagents - use typeof the imported agent
241
- runtimeAgentTypeLines.push(
242
- ` ${parentCamelName}: AgentRunner<typeof ${parentCamelName}Agent['inputSchema'], typeof ${parentCamelName}Agent['outputSchema'], typeof ${parentCamelName}Agent['stream'] extends true ? true : false>;`
243
- );
244
- } else {
245
- // Has subagents - create intersection type using typeof
246
- runtimeAgentTypeLines.push(
247
- ` ${parentCamelName}: AgentRunner<typeof ${parentCamelName}Agent['inputSchema'], typeof ${parentCamelName}Agent['outputSchema'], typeof ${parentCamelName}Agent['stream'] extends true ? true : false> & {`
248
- );
249
- for (const child of children) {
250
- const childCamelName = toCamelCase(child.name);
251
- const fullChildName = toCamelCase(`${parentAgent.name}_${child.name}`);
252
- runtimeAgentTypeLines.push(
253
- ` ${childCamelName}: AgentRunner<typeof ${fullChildName}Agent['inputSchema'], typeof ${fullChildName}Agent['outputSchema'], typeof ${fullChildName}Agent['stream'] extends true ? true : false>;`
254
- );
255
- }
256
- runtimeAgentTypeLines.push(` };`);
257
- }
258
- }
259
- const runtimeAgentTypes = runtimeAgentTypeLines.join('\n');
260
-
261
- // Generate React client types with nested structure
262
- const clientAgentTypeLines: string[] = [];
263
- for (const parentAgent of parentAgents) {
264
- const parentCamelName = toCamelCase(parentAgent.name);
265
- const children = subagentsByParent.get(parentAgent.name) || [];
266
-
267
- if (children.length === 0) {
268
- // No subagents
269
- clientAgentTypeLines.push(
270
- ` '${parentAgent.name}': Agent<typeof ${parentCamelName}Agent['inputSchema'], typeof ${parentCamelName}Agent['outputSchema']>;`
271
- );
272
- } else {
273
- // Has subagents - create nested type with subagent access via dot notation
274
- clientAgentTypeLines.push(
275
- ` '${parentAgent.name}': Agent<typeof ${parentCamelName}Agent['inputSchema'], typeof ${parentCamelName}Agent['outputSchema']>;`
276
- );
277
- for (const child of children) {
278
- const fullChildName = toCamelCase(`${parentAgent.name}_${child.name}`);
279
- clientAgentTypeLines.push(
280
- ` '${parentAgent.name}.${child.name}': Agent<typeof ${fullChildName}Agent['inputSchema'], typeof ${fullChildName}Agent['outputSchema']>;`
281
- );
282
- }
283
- }
284
- }
285
- const reactAgentTypes = clientAgentTypeLines.join('\n');
134
+ // Generate flat agent type definitions for AgentRegistry interface augmentation
135
+ const runtimeAgentTypes = agentInfo
136
+ .map(({ name }) => {
137
+ const camelName = toCamelCase(name);
138
+ return ` ${camelName}: AgentRunner<typeof ${camelName}Agent['inputSchema'], typeof ${camelName}Agent['outputSchema'], typeof ${camelName}Agent['stream'] extends true ? true : false>;`;
139
+ })
140
+ .join('\n');
286
141
 
287
142
  const generatedContent = `/// <reference types="hono" />
288
143
  // Auto-generated by Agentuity - do not edit manually
289
- ${imports}${evalsImportsStr}
144
+ ${imports}
290
145
  import type { AgentRunner, Logger } from '@agentuity/runtime';
291
- import type { KeyValueStorage, ObjectStorage, StreamStorage, VectorStorage } from '@agentuity/core';
292
- import type { Agent } from '@agentuity/react';
146
+ import type { KeyValueStorage, StreamStorage, VectorStorage } from '@agentuity/core';
293
147
 
294
148
  /**
295
149
  * Registry of all agents in this application.
@@ -315,38 +169,20 @@ ${runtimeAgentTypes}
315
169
  }
316
170
  }
317
171
 
318
- // Augment Hono Context to provide strongly-typed agents and runtime services
319
- // Note: Properties are added to Context via middleware in @agentuity/runtime
320
- declare module "hono" {
321
- interface Context {
322
- agentName: LocalAgentName;
323
- agent: {
324
- ${honoAgentTypes}
325
- };
326
- waitUntil: (promise: Promise<void> | (() => void | Promise<void>)) => void;
327
- logger: Logger;
328
- kv: KeyValueStorage;
329
- objectstore: ObjectStorage;
330
- stream: StreamStorage;
331
- vector: VectorStorage;
332
- }
333
- }
334
-
335
- // Augment @agentuity/react types with strongly-typed agents from this project
336
- declare module '@agentuity/react' {
337
- interface AgentRegistry {
338
- ${reactAgentTypes}
339
- }
340
- }
172
+ // NOTE: Hono Context properties are accessed via c.var (e.g., c.var.logger, c.var.kv)
173
+ // The Variables interface in @agentuity/runtime defines all available context properties
341
174
  `;
342
175
 
176
+ const projectRoot = join(srcDir, '..');
177
+ const agentuityDir = join(projectRoot, '.agentuity');
178
+ const registryPath = join(agentuityDir, 'registry.generated.ts');
179
+
343
180
  const agentsDir = join(srcDir, 'agent');
344
- const registryPath = join(agentsDir, 'registry.generated.ts');
345
181
  const legacyTypesPath = join(agentsDir, 'types.generated.d.ts');
346
182
 
347
- // Ensure agent directory exists
348
- if (!existsSync(agentsDir)) {
349
- mkdirSync(agentsDir, { recursive: true });
183
+ // Ensure .agentuity directory exists
184
+ if (!existsSync(agentuityDir)) {
185
+ mkdirSync(agentuityDir, { recursive: true });
350
186
  }
351
187
 
352
188
  writeFileSync(registryPath, generatedContent, 'utf-8');
@@ -364,11 +200,9 @@ export function getBuildMetadata(): Partial<BuildMetadata> {
364
200
  }
365
201
 
366
202
  const AgentNameDuplicateError = StructuredError('AgentNameDuplicateError');
367
- const MetadataMissingError = StructuredError('MetadataMissingError');
368
203
  const MetadataPropertyMissingError = StructuredError('MetadataPropertyMissingError')<{
369
204
  name: string;
370
205
  }>();
371
- const SubAgentMissingError = StructuredError('SubAgentMissingError');
372
206
 
373
207
  const AgentuityBundler: BunPlugin = {
374
208
  name: 'Agentuity Bundler',
@@ -389,69 +223,51 @@ const AgentuityBundler: BunPlugin = {
389
223
  (build.config.define?.['process.env.NODE_ENV']
390
224
  ? JSON.parse(build.config.define['process.env.NODE_ENV'])
391
225
  : 'production') === 'development';
392
- const routes: Set<string> = new Set();
393
226
  const agentInfo: Array<Record<string, string>> = [];
394
227
  const agentMetadata: Map<string, Map<string, string>> = new Map<
395
228
  string,
396
229
  Map<string, string>
397
230
  >();
398
231
  const transpiler = new Bun.Transpiler({ loader: 'ts', target: 'bun' });
399
- let routeDefinitions: BuildMetadata['routes'] = [];
400
-
401
- build.onResolve({ filter: /\/route\.ts$/, namespace: 'file' }, async (args) => {
402
- if (args.path.startsWith(srcDir)) {
403
- const importPath = args.path
404
- .replace(rootDir, '')
405
- .replace('.ts', '')
406
- .replace('/src/', './');
407
- routes.add(importPath);
408
- }
409
- return args;
410
- });
411
-
412
- build.onLoad({ filter: /\/route\.ts$/, namespace: 'file' }, async (args) => {
413
- if (args.path.startsWith(srcDir)) {
414
- const importPath = args.path
415
- .replace(rootDir, '')
416
- .replace('.ts', '')
417
- .replace('/src/', './');
418
- routes.add(importPath);
419
- }
420
- // return undefined to let Bun handle loading normally
421
- return;
422
- });
423
232
 
424
- build.onLoad({ filter: /\/agent\.ts$/, namespace: 'file' }, async (args) => {
233
+ // Scan ALL .ts files in src/agent directory for agents
234
+ build.onLoad({ filter: /\/agent\/.*\.ts$/, namespace: 'file' }, async (args) => {
425
235
  let newsource = await Bun.file(args.path).text();
426
236
  if (args.path.startsWith(srcDir)) {
427
237
  const contents = transpiler.transformSync(newsource);
428
- const [ns, md] = await parseAgentMetadata(
238
+ const result = await parseAgentMetadata(
429
239
  rootDir,
430
240
  args.path,
431
241
  contents,
432
242
  projectId,
433
243
  deploymentId
434
244
  );
435
- newsource = ns;
436
245
 
437
- // Detect if this is a subagent by checking path structure
438
- // Note: Path structure assumption - 4 segments: agent/parent/child/agent.ts
439
- const { isSubagent, parentName } = detectSubagent(args.path, srcDir);
440
- if (isSubagent && parentName) {
441
- md.set('parent', parentName);
246
+ // Skip files that don't have a createAgent export
247
+ if (result === undefined) {
248
+ return {
249
+ contents: newsource,
250
+ loader: 'ts',
251
+ };
442
252
  }
443
253
 
444
- const newAgentName = md.get('name');
445
- for (const [, kv] of agentMetadata) {
446
- const found = kv.get('name');
447
- if (newAgentName === found) {
448
- throw new AgentNameDuplicateError({
449
- message: `The agent in ${kv.get('filename')} and the agent in ${md.get('filename')} have the same name (${found}). Agent Names must be unique within a project.`,
450
- });
254
+ const [ns, md] = result;
255
+ newsource = ns;
256
+
257
+ // Only process files that actually export an agent
258
+ if (md.has('name')) {
259
+ const newAgentName = md.get('name');
260
+ for (const [, kv] of agentMetadata) {
261
+ const found = kv.get('name');
262
+ if (newAgentName === found) {
263
+ throw new AgentNameDuplicateError({
264
+ message: `The agent in ${kv.get('filename')} and the agent in ${md.get('filename')} have the same name (${found}). Agent Names must be unique within a project.`,
265
+ });
266
+ }
451
267
  }
452
- }
453
268
 
454
- agentMetadata.set(md.get('identifier')!, md);
269
+ agentMetadata.set(md.get('name')!, md);
270
+ }
455
271
  }
456
272
  return {
457
273
  contents: newsource,
@@ -509,121 +325,6 @@ const AgentuityBundler: BunPlugin = {
509
325
  await args.defer();
510
326
 
511
327
  const inserts: string[] = [];
512
- const routeMapping: Record<string, string> = {};
513
-
514
- for (const route of routes) {
515
- const name = basename(dirname(route));
516
- const agent = route.replace(/\/route$/, '/agent');
517
- const hasAgent = existsSync(join(srcDir, agent + '.ts'));
518
-
519
- // Detect if this is a subagent route using shared utility
520
- const { isSubagent, parentName } = detectSubagent(route);
521
-
522
- const agentPath = route.replace(/\/route$/, '/*').replace('./', '/');
523
- const routePath = route
524
- .replace(/\/route$/, '')
525
- .replace('/web/', '/api/')
526
- .replace('/web', '/api')
527
- .replace('./', '/');
528
-
529
- const definitions = await parseRoute(
530
- rootDir,
531
- join(srcDir, `${route}.ts`),
532
- projectId,
533
- deploymentId
534
- );
535
-
536
- let agentDetail: Record<string, string> = {};
537
-
538
- if (hasAgent) {
539
- const md = agentMetadata.get(name);
540
- if (!md) {
541
- throw new MetadataMissingError({
542
- message: `Couldn't find agent metadata for ${route}`,
543
- });
544
- }
545
- agentDetail = {
546
- name,
547
- id: md.get('id')!,
548
- path: `.${agent}`,
549
- filename: md.get('filename')!,
550
- identifier: md.get('identifier')!,
551
- description: md.get('description') ?? '',
552
- agentId: md.get('agentId')!,
553
- };
554
- if (isSubagent && parentName) {
555
- agentDetail.parent = parentName;
556
- }
557
- agentInfo.push(agentDetail);
558
- for (const def of definitions) {
559
- def.agentIds = [agentDetail.agentId, agentDetail.id];
560
- }
561
- }
562
-
563
- // do this after handling the agent association (if any)
564
- routeDefinitions = [...routeDefinitions, ...definitions];
565
-
566
- let buffer = `await (async() => {
567
- const { createAgentMiddleware, getRouter, registerAgent } = await import('@agentuity/runtime');
568
- const router = getRouter()!;
569
- const route = require('./src/${route}').default;`;
570
- if (hasAgent) {
571
- const agentRegistrationName =
572
- isSubagent && parentName ? `${parentName}.${name}` : name;
573
- // Build evals path from agent path (e.g., 'agent/eval/agent' -> 'agent/eval/eval.ts')
574
- const agentDirPath = agent.replace(/\/agent$/, '');
575
- const evalsPath = join(srcDir, agentDirPath, 'eval.ts');
576
- const evalsImport = existsSync(evalsPath)
577
- ? `\n require('./src/${agentDirPath}/eval');`
578
- : '';
579
- buffer += `
580
- const agent = require('./src/${agent}').default;
581
- router.all("${agentPath}", createAgentMiddleware('${agentRegistrationName}'));
582
- registerAgent("${agentRegistrationName}", agent);${evalsImport}`;
583
- }
584
- buffer += `
585
- router.route("${routePath}", route);
586
- })();`;
587
- inserts.push(buffer);
588
-
589
- for (const def of definitions) {
590
- routeMapping[`${def.method} ${def.path}`] = def.id;
591
- }
592
- }
593
-
594
- // Register standalone agents (agents without routes)
595
- const routeAgentNames = new Set(
596
- agentInfo
597
- .filter((a) => {
598
- // Check if this agent was added via a route (has a corresponding route file)
599
- const agentPath = a.path.replace(/^\./, '').replace(/\/agent$/, '/route');
600
- return routes.has(agentPath.replace(/^\/src\//, './'));
601
- })
602
- .map((a) => a.name)
603
- );
604
-
605
- for (const agentDetail of agentInfo) {
606
- if (!routeAgentNames.has(agentDetail.name)) {
607
- // This is a standalone agent - register it without a route
608
- const agentPath = agentDetail.path;
609
- const agentDirPath = agentPath.replace(/\/agent$/, '');
610
- const evalsPath = join(srcDir, agentDirPath.replace(/^\./, ''), 'eval.ts');
611
- const evalsImport = existsSync(evalsPath)
612
- ? `\n require('./src/${agentDirPath.replace(/^\.\//, '')}/eval');`
613
- : '';
614
- const isSubagent = !!agentDetail.parent;
615
- const agentRegistrationName = isSubagent
616
- ? `${agentDetail.parent}.${agentDetail.name}`
617
- : agentDetail.name;
618
-
619
- const buffer = `await (async() => {
620
- const { registerAgent } = await import('@agentuity/runtime');
621
- const agent = require('./src${agentPath}').default;
622
- registerAgent("${agentRegistrationName}", agent);${evalsImport}
623
- })();`;
624
- inserts.push(buffer);
625
- }
626
- }
627
328
 
628
329
  const indexFile = join(srcDir, 'web', 'index.html');
629
330
 
@@ -677,39 +378,48 @@ import { readFileSync, existsSync } from 'node:fs';
677
378
  })();`);
678
379
  }
679
380
 
680
- // Add standalone agents (agents without routes) to agentInfo
681
- // These agents can still be called by other agents or routes via ctx.agent
682
- const registeredIdentifiers = new Set(agentInfo.map((a) => a.identifier));
683
- for (const [identifier, md] of agentMetadata) {
684
- if (!registeredIdentifiers.has(identifier)) {
685
- // md.get('filename') can be either absolute or relative to rootDir
686
- const filename = md.get('filename')!;
687
- const absolutePath = filename.startsWith('/')
688
- ? filename
689
- : join(rootDir, filename);
690
-
691
- // Convert to path relative to srcDir like route-based agents
692
- // e.g., /path/to/src/agent/lifecycle/agent.ts -> ./agent/lifecycle/agent
693
- const agentPath = absolutePath.replace(srcDir, '.').replace('.ts', '');
694
-
695
- // Extract folder name as agent name (same as route-based logic)
696
- const folderName = basename(dirname(absolutePath));
697
-
698
- const { isSubagent, parentName } = detectSubagent(absolutePath, srcDir);
699
-
700
- const agentDetail: Record<string, string> = {
701
- name: folderName,
702
- id: md.get('id')!,
703
- path: agentPath,
704
- filename: absolutePath,
705
- identifier: md.get('identifier')!,
706
- description: md.get('description') ?? '',
707
- agentId: md.get('agentId')!,
708
- };
709
- if (isSubagent && parentName) {
710
- agentDetail.parent = parentName;
381
+ // Build agentInfo from all discovered agents and track directories
382
+ const agentDirs = new Set<string>();
383
+ for (const [_identifier, md] of agentMetadata) {
384
+ // md.get('filename') can be either absolute or relative to rootDir
385
+ const filename = md.get('filename')!;
386
+ const absolutePath = filename.startsWith('/') ? filename : join(rootDir, filename);
387
+
388
+ // Track which directories have agents
389
+ const dir = dirname(absolutePath);
390
+ agentDirs.add(dir);
391
+
392
+ // Convert to path relative to srcDir
393
+ // e.g., /path/to/src/agent/my-agent.ts -> ./agent/my-agent
394
+ const agentPath = absolutePath.replace(srcDir, '.').replace('.ts', '');
395
+
396
+ const agentDetail: Record<string, string> = {
397
+ name: md.get('name')!,
398
+ id: md.get('id')!,
399
+ path: agentPath,
400
+ filename: absolutePath,
401
+ description: md.get('description') ?? '',
402
+ agentId: md.get('agentId')!,
403
+ };
404
+ agentInfo.push(agentDetail);
405
+ }
406
+
407
+ // Validate that all directories in src/agent have at least one agent
408
+ const agentBaseDir = join(srcDir, 'agent');
409
+ if (existsSync(agentBaseDir)) {
410
+ const { readdirSync, statSync } = await import('node:fs');
411
+ const subdirs = readdirSync(agentBaseDir).filter((name) => {
412
+ const fullPath = join(agentBaseDir, name);
413
+ return statSync(fullPath).isDirectory();
414
+ });
415
+
416
+ for (const subdir of subdirs) {
417
+ const fullPath = join(agentBaseDir, subdir);
418
+ if (!agentDirs.has(fullPath)) {
419
+ throw new Error(
420
+ `Directory ${subdir} in src/agent must contain at least one agent (a file with a createAgent export)`
421
+ );
711
422
  }
712
- agentInfo.push(agentDetail);
713
423
  }
714
424
  }
715
425
 
@@ -723,6 +433,72 @@ import { readFileSync, existsSync } from 'node:fs';
723
433
  // Generate lifecycle types if setup() is present in app.ts
724
434
  await setupLifecycleTypes(rootDir, outDir, srcDir, logger);
725
435
 
436
+ // Parse routes from src/api directory
437
+ const apiRoutesMetadata: BuildMetadata['routes'] = [];
438
+ const routeInfoList: RouteInfo[] = [];
439
+ const apiDir = join(srcDir, 'api');
440
+ if (existsSync(apiDir)) {
441
+ const { readdirSync, statSync } = await import('node:fs');
442
+ const apiFiles = readdirSync(apiDir)
443
+ .filter((name) => name.endsWith('.ts') && !name.endsWith('.generated.ts'))
444
+ .map((name) => join(apiDir, name));
445
+
446
+ for (const apiFile of apiFiles) {
447
+ if (statSync(apiFile).isFile()) {
448
+ try {
449
+ const routes = await parseRoute(rootDir, apiFile, projectId, deploymentId);
450
+ apiRoutesMetadata.push(...routes);
451
+
452
+ // Collect route info for RouteRegistry generation
453
+ for (const route of routes) {
454
+ routeInfoList.push({
455
+ method: route.method.toUpperCase(),
456
+ path: route.path,
457
+ filename: route.filename,
458
+ hasValidator: route.config?.hasValidator === true,
459
+ routeType: route.type || 'api',
460
+ agentVariable: route.config?.agentVariable as string | undefined,
461
+ agentImportPath: route.config?.agentImportPath as string | undefined,
462
+ inputSchemaVariable: route.config?.inputSchemaVariable as
463
+ | string
464
+ | undefined,
465
+ outputSchemaVariable: route.config?.outputSchemaVariable as
466
+ | string
467
+ | undefined,
468
+ });
469
+ }
470
+ } catch (error) {
471
+ // Skip files that don't have createRouter (they might be utilities)
472
+ if (
473
+ error instanceof Error &&
474
+ error.message.includes('could not find an proper createRouter')
475
+ ) {
476
+ logger.trace(`Skipping ${apiFile}: no createRouter found`);
477
+ } else {
478
+ throw error;
479
+ }
480
+ }
481
+ }
482
+ }
483
+ }
484
+
485
+ // Generate RouteRegistry for type-safe route access
486
+ if (routeInfoList.length > 0) {
487
+ logger.trace(`Generating RouteRegistry with ${routeInfoList.length} routes`);
488
+ generateRouteRegistry(srcDir, routeInfoList);
489
+ }
490
+
491
+ // Auto-mount src/api/index.ts if it exists
492
+ const apiIndexPath = join(srcDir, 'api', 'index.ts');
493
+ if (existsSync(apiIndexPath)) {
494
+ inserts.push(`await (async() => {
495
+ const { getRouter } = await import('@agentuity/runtime');
496
+ const router = getRouter()!;
497
+ const api = require('./src/api/index').default;
498
+ router.route('/api', api);
499
+ })();`);
500
+ }
501
+
726
502
  // Only create the workbench routes if workbench is actually configured
727
503
  if (workbenchConfig) {
728
504
  inserts.push(`await (async() => {
@@ -772,14 +548,11 @@ await (async() => {
772
548
 
773
549
  // generate the build metadata
774
550
  metadata = {
775
- routes: routeDefinitions,
551
+ routes: apiRoutesMetadata,
776
552
  agents: [],
777
553
  };
778
554
 
779
- // Group agents by parent/child relationship
780
- const parentAgentMetadata = new Map<string, Map<string, string>>();
781
- const subagentsByParent = new Map<string, Array<Map<string, string>>>();
782
-
555
+ // Validate required metadata properties and build agent metadata
783
556
  for (const [, v] of agentMetadata) {
784
557
  if (!v.has('filename')) {
785
558
  throw new MetadataPropertyMissingError({
@@ -793,12 +566,6 @@ await (async() => {
793
566
  message: 'agent metadata is missing expected id property',
794
567
  });
795
568
  }
796
- if (!v.has('identifier')) {
797
- throw new MetadataPropertyMissingError({
798
- name: 'identifier',
799
- message: 'agent metadata is missing expected identifier property',
800
- });
801
- }
802
569
  if (!v.has('version')) {
803
570
  throw new MetadataPropertyMissingError({
804
571
  name: 'version',
@@ -817,45 +584,13 @@ await (async() => {
817
584
  message: 'agent metadata is missing expected agentId property',
818
585
  });
819
586
  }
820
-
821
- const parentName = v.get('parent');
822
- if (parentName) {
823
- // This is a subagent
824
- if (!subagentsByParent.has(parentName)) {
825
- subagentsByParent.set(parentName, []);
826
- }
827
- subagentsByParent.get(parentName)!.push(v);
828
- } else {
829
- // This is a parent or standalone agent
830
- parentAgentMetadata.set(v.get('identifier')!, v);
831
- }
832
- }
833
-
834
- // Validate that all subagents reference existing parent agents
835
- for (const [parentName, subagents] of subagentsByParent) {
836
- const parentExists = Array.from(parentAgentMetadata.values()).some(
837
- (meta) => meta.get('name') === parentName || meta.get('identifier') === parentName
838
- );
839
- if (!parentExists) {
840
- const subagentPaths = subagents.map((s) => s.get('filename')).join(', ');
841
- throw new SubAgentMissingError({
842
- message:
843
- `Subagent(s) [${subagentPaths}] reference parent "${parentName}" which does not exist. ` +
844
- `Ensure the parent agent is defined.`,
845
- });
846
- }
847
- }
848
-
849
- // Build metadata with nested subagents
850
- for (const [_identifier, v] of parentAgentMetadata) {
851
587
  const agentData: BuildMetadata['agents'][number] = {
852
588
  filename: v.get('filename')!,
853
589
  id: v.get('id')!,
854
- identifier: v.get('identifier')!,
855
590
  agentId: v.get('agentId')!,
856
591
  version: v.get('version')!,
857
592
  name: v.get('name')!,
858
- description: v.get('description') ?? '<no description provided>',
593
+ description: v.get('description') ?? '',
859
594
  projectId,
860
595
  };
861
596
 
@@ -889,66 +624,9 @@ await (async() => {
889
624
  logger.trace(`[plugin] No evals found for agent ${agentData.name}`);
890
625
  }
891
626
 
892
- // Add subagents if any (check both name and identifier)
893
- const subagents =
894
- subagentsByParent.get(agentData.name) ||
895
- subagentsByParent.get(agentData.identifier);
896
- if (subagents && subagents.length > 0) {
897
- agentData.subagents = subagents.map((sub) => {
898
- const subagentData: BuildMetadata['agents'][number] = {
899
- filename: sub.get('filename')!,
900
- id: sub.get('id')!,
901
- identifier: sub.get('identifier')!,
902
- agentId: sub.get('agentId')!,
903
- version: sub.get('version')!,
904
- name: sub.get('name')!,
905
- description: sub.get('description') ?? '<no description provided>',
906
- projectId,
907
- };
908
-
909
- // Add evals for subagents if any
910
- const subEvalsStr = sub.get('evals');
911
- if (subEvalsStr) {
912
- logger.trace(
913
- `[plugin] Found evals string for subagent ${subagentData.name}, parsing...`
914
- );
915
- try {
916
- const parsedSubEvals = JSON.parse(subEvalsStr) as Array<
917
- Omit<
918
- NonNullable<BuildMetadata['agents'][number]['evals']>[number],
919
- 'agentIdentifier' | 'projectId'
920
- >
921
- >;
922
- subagentData.evals = parsedSubEvals.map((evalItem) => ({
923
- ...evalItem,
924
- agentIdentifier: subagentData.agentId,
925
- projectId,
926
- }));
927
- logger.trace(
928
- `[plugin] Successfully parsed ${subagentData.evals?.length ?? 0} eval(s) for subagent ${subagentData.name}`
929
- );
930
- } catch (e) {
931
- logger.trace(
932
- `[plugin] Failed to parse evals for subagent ${subagentData.name}: ${e}`
933
- );
934
- console.warn(
935
- `Failed to parse evals for subagent ${subagentData.name}: ${e}`
936
- );
937
- }
938
- } else {
939
- logger.trace(`[plugin] No evals found for subagent ${subagentData.name}`);
940
- }
941
-
942
- return subagentData;
943
- });
944
- }
945
-
946
627
  metadata.agents!.push(agentData);
947
628
  }
948
629
 
949
- const routeMappingJSFile = Bun.file(join(outDir, '.routemapping.json'));
950
- await routeMappingJSFile.write(JSON.stringify(routeMapping));
951
-
952
630
  return {
953
631
  contents,
954
632
  loader: 'ts',