@agentuity/cli 0.0.100 → 0.0.101

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 (94) hide show
  1. package/AGENTS.md +1 -1
  2. package/dist/api.d.ts +1 -0
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +1 -1
  5. package/dist/api.js.map +1 -1
  6. package/dist/cmd/build/ast.d.ts +2 -1
  7. package/dist/cmd/build/ast.d.ts.map +1 -1
  8. package/dist/cmd/build/ast.js +135 -47
  9. package/dist/cmd/build/ast.js.map +1 -1
  10. package/dist/cmd/build/entry-generator.d.ts.map +1 -1
  11. package/dist/cmd/build/entry-generator.js +220 -188
  12. package/dist/cmd/build/entry-generator.js.map +1 -1
  13. package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
  14. package/dist/cmd/build/vite/agent-discovery.js +103 -45
  15. package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
  16. package/dist/cmd/build/vite/bun-dev-server.js +1 -1
  17. package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
  18. package/dist/cmd/build/vite/docs-generator.d.ts +13 -0
  19. package/dist/cmd/build/vite/docs-generator.d.ts.map +1 -0
  20. package/dist/cmd/build/vite/docs-generator.js +81 -0
  21. package/dist/cmd/build/vite/docs-generator.js.map +1 -0
  22. package/dist/cmd/build/vite/index.d.ts +3 -3
  23. package/dist/cmd/build/vite/index.d.ts.map +1 -1
  24. package/dist/cmd/build/vite/index.js +9 -7
  25. package/dist/cmd/build/vite/index.js.map +1 -1
  26. package/dist/cmd/build/vite/lifecycle-generator.d.ts +1 -1
  27. package/dist/cmd/build/vite/lifecycle-generator.d.ts.map +1 -1
  28. package/dist/cmd/build/vite/lifecycle-generator.js +19 -5
  29. package/dist/cmd/build/vite/lifecycle-generator.js.map +1 -1
  30. package/dist/cmd/build/vite/metadata-generator.d.ts.map +1 -1
  31. package/dist/cmd/build/vite/metadata-generator.js +145 -0
  32. package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
  33. package/dist/cmd/build/vite/registry-generator.d.ts +3 -3
  34. package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
  35. package/dist/cmd/build/vite/registry-generator.js +627 -103
  36. package/dist/cmd/build/vite/registry-generator.js.map +1 -1
  37. package/dist/cmd/build/vite/route-discovery.d.ts +4 -0
  38. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
  39. package/dist/cmd/build/vite/route-discovery.js.map +1 -1
  40. package/dist/cmd/build/vite/server-bundler.d.ts.map +1 -1
  41. package/dist/cmd/build/vite/server-bundler.js +18 -1
  42. package/dist/cmd/build/vite/server-bundler.js.map +1 -1
  43. package/dist/cmd/build/vite/vite-builder.d.ts +1 -1
  44. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  45. package/dist/cmd/build/vite/vite-builder.js +28 -18
  46. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  47. package/dist/cmd/build/vite-bundler.js +6 -6
  48. package/dist/cmd/build/vite-bundler.js.map +1 -1
  49. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  50. package/dist/cmd/cloud/deploy.js +11 -5
  51. package/dist/cmd/cloud/deploy.js.map +1 -1
  52. package/dist/cmd/dev/file-watcher.d.ts.map +1 -1
  53. package/dist/cmd/dev/file-watcher.js +4 -2
  54. package/dist/cmd/dev/file-watcher.js.map +1 -1
  55. package/dist/cmd/dev/index.d.ts.map +1 -1
  56. package/dist/cmd/dev/index.js +102 -21
  57. package/dist/cmd/dev/index.js.map +1 -1
  58. package/dist/cmd/dev/sync.d.ts.map +1 -1
  59. package/dist/cmd/dev/sync.js +19 -3
  60. package/dist/cmd/dev/sync.js.map +1 -1
  61. package/dist/config.d.ts.map +1 -1
  62. package/dist/config.js +8 -0
  63. package/dist/config.js.map +1 -1
  64. package/dist/index.d.ts +0 -1
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +0 -1
  67. package/dist/index.js.map +1 -1
  68. package/package.json +5 -8
  69. package/src/api.ts +1 -1
  70. package/src/cmd/build/ast.ts +161 -48
  71. package/src/cmd/build/entry-generator.ts +225 -190
  72. package/src/cmd/build/vite/agent-discovery.ts +151 -58
  73. package/src/cmd/build/vite/bun-dev-server.ts +1 -1
  74. package/src/cmd/build/vite/docs-generator.ts +87 -0
  75. package/src/cmd/build/vite/index.ts +9 -7
  76. package/src/cmd/build/vite/lifecycle-generator.ts +19 -5
  77. package/src/cmd/build/vite/metadata-generator.ts +178 -0
  78. package/src/cmd/build/vite/registry-generator.ts +727 -108
  79. package/src/cmd/build/vite/route-discovery.ts +4 -0
  80. package/src/cmd/build/vite/server-bundler.ts +20 -1
  81. package/src/cmd/build/vite/vite-builder.ts +44 -30
  82. package/src/cmd/build/vite-bundler.ts +6 -6
  83. package/src/cmd/cloud/deploy.ts +15 -5
  84. package/src/cmd/dev/file-watcher.ts +8 -2
  85. package/src/cmd/dev/index.ts +141 -30
  86. package/src/cmd/dev/sync.ts +41 -6
  87. package/src/config.ts +9 -0
  88. package/src/index.ts +0 -5
  89. package/src/runtime-bootstrap.md +1 -1
  90. package/dist/runtime-bootstrap.d.ts +0 -56
  91. package/dist/runtime-bootstrap.d.ts.map +0 -1
  92. package/dist/runtime-bootstrap.js +0 -95
  93. package/dist/runtime-bootstrap.js.map +0 -1
  94. package/src/runtime-bootstrap.ts +0 -131
@@ -22,13 +22,12 @@ interface GenerateEntryOptions {
22
22
  */
23
23
  export async function generateEntryFile(options: GenerateEntryOptions): Promise<void> {
24
24
  const { rootDir, projectId, deploymentId, logger, mode, workbench, vitePort } = options;
25
- const isDev = mode === 'dev';
26
25
 
27
26
  const srcDir = join(rootDir, 'src');
28
- const agentuityDir = join(rootDir, '.agentuity');
29
- const entryPath = join(agentuityDir, 'app.generated.ts');
27
+ const generatedDir = join(srcDir, 'generated');
28
+ const entryPath = join(generatedDir, 'app.ts');
30
29
 
31
- logger.trace(`Generating ${mode} mode entry file...`);
30
+ logger.trace(`Generating unified entry file (supports both dev and prod modes)...`);
32
31
 
33
32
  // Discover routes to determine which files need to be imported
34
33
  const { routeInfoList } = await discoverRoutes(srcDir, projectId, deploymentId, logger);
@@ -77,12 +76,14 @@ export async function generateEntryFile(options: GenerateEntryOptions): Promise<
77
76
  ...runtimeImports,
78
77
  `} from '@agentuity/runtime';`,
79
78
  `import type { Context } from 'hono';`,
80
- `import { websocket } from 'hono/bun';`, // Always use Bun WebSocket (dev and prod)
81
- !isDev && hasWebFrontend ? `import { serveStatic } from 'hono/bun';` : '',
79
+ `import { websocket } from 'hono/bun';`,
80
+ // Conditionally import serveStatic and readFileSync for web frontend or workbench support
81
+ hasWebFrontend || hasWorkbench ? `import { serveStatic } from 'hono/bun';` : '',
82
+ hasWebFrontend || hasWorkbench ? `import { readFileSync, existsSync } from 'node:fs';` : '',
82
83
  ].filter(Boolean);
83
84
 
84
85
  imports.push(`import { type LogLevel } from '@agentuity/core';`);
85
- imports.push(`import { bootstrapRuntimeEnv } from '@agentuity/cli/runtime-bootstrap';`);
86
+ imports.push(`import { bootstrapRuntimeEnv } from '@agentuity/runtime';`);
86
87
 
87
88
  // Generate route mounting code for all discovered routes
88
89
  const routeImportsAndMounts: string[] = [];
@@ -107,7 +108,7 @@ export async function generateEntryFile(options: GenerateEntryOptions): Promise<
107
108
 
108
109
  const importName = `router_${routeIndex++}`;
109
110
  routeImportsAndMounts.push(
110
- `const { default: ${importName} } = await import('../src/api/${relativePath}.js');`
111
+ `const { default: ${importName} } = await import('../api/${relativePath}.js');`
111
112
  );
112
113
  routeImportsAndMounts.push(`app.route('${mountPath}', ${importName});`);
113
114
  }
@@ -129,185 +130,199 @@ app.route('/', workbenchRouter);
129
130
  `
130
131
  : '';
131
132
 
132
- // Asset proxy routes (dev mode only - proxy to Vite asset server)
133
- const assetProxyRoutes =
134
- isDev && vitePort
135
- ? `
136
- // Asset proxy routes - Forward Vite-specific requests to asset server
137
- const VITE_ASSET_PORT = ${vitePort};
138
-
139
- const proxyToVite = async (c: Context) => {
140
- const viteUrl = \`http://127.0.0.1:\${VITE_ASSET_PORT}\${c.req.path}\`;
141
- const controller = new AbortController();
142
- const timeout = setTimeout(() => controller.abort(), 10000); // 10s timeout
143
- try {
144
- otel.logger.debug(\`[Proxy] \${c.req.method} \${c.req.path} -> Vite:\${VITE_ASSET_PORT}\`);
145
- const res = await fetch(viteUrl, { signal: controller.signal });
146
- clearTimeout(timeout);
147
- otel.logger.debug(\`[Proxy] \${c.req.path} -> \${res.status} (\${res.headers.get('content-type')})\`);
148
- return new Response(res.body, {
149
- status: res.status,
150
- headers: res.headers,
151
- });
152
- } catch (err) {
153
- clearTimeout(timeout);
154
- if (err instanceof Error && err.name === 'AbortError') {
155
- otel.logger.error(\`Vite proxy timeout: \${c.req.path}\`);
156
- return c.text('Vite asset server timeout', 504);
133
+ // Asset proxy routes - only generated in dev mode when vitePort is available
134
+ const assetProxyRoutes = vitePort
135
+ ? `
136
+ // Asset proxy routes - Development mode only (proxies to Vite asset server)
137
+ if (process.env.NODE_ENV !== 'production') {
138
+ const VITE_ASSET_PORT = parseInt(process.env.VITE_PORT || '${vitePort}', 10);
139
+
140
+ const proxyToVite = async (c: Context) => {
141
+ const viteUrl = \`http://127.0.0.1:\${VITE_ASSET_PORT}\${c.req.path}\`;
142
+ const controller = new AbortController();
143
+ const timeout = setTimeout(() => controller.abort(), 10000); // 10s timeout
144
+ try {
145
+ otel.logger.debug(\`[Proxy] \${c.req.method} \${c.req.path} -> Vite:\${VITE_ASSET_PORT}\`);
146
+ const res = await fetch(viteUrl, { signal: controller.signal });
147
+ clearTimeout(timeout);
148
+ otel.logger.debug(\`[Proxy] \${c.req.path} -> \${res.status} (\${res.headers.get('content-type')})\`);
149
+ return new Response(res.body, {
150
+ status: res.status,
151
+ headers: res.headers,
152
+ });
153
+ } catch (err) {
154
+ clearTimeout(timeout);
155
+ if (err instanceof Error && err.name === 'AbortError') {
156
+ otel.logger.error(\`Vite proxy timeout: \${c.req.path}\`);
157
+ return c.text('Vite asset server timeout', 504);
158
+ }
159
+ otel.logger.error(\`Failed to proxy to Vite: \${c.req.path} - \${err instanceof Error ? err.message : String(err)}\`);
160
+ return c.text('Vite asset server error', 500);
157
161
  }
158
- otel.logger.error(\`Failed to proxy to Vite: \${c.req.path} - \${err instanceof Error ? err.message : String(err)}\`);
159
- return c.text('Vite asset server error', 500);
160
- }
161
- };
162
+ };
162
163
 
163
- // Vite client scripts and HMR
164
- app.get('/@vite/*', proxyToVite);
165
- app.get('/@react-refresh', proxyToVite);
164
+ // Vite client scripts and HMR
165
+ app.get('/@vite/*', proxyToVite);
166
+ app.get('/@react-refresh', proxyToVite);
166
167
 
167
- // Source files for HMR
168
- app.get('/src/web/*', proxyToVite);
169
- app.get('/src/*', proxyToVite); // Catch-all for other source files
168
+ // Source files for HMR
169
+ app.get('/src/web/*', proxyToVite);
170
+ app.get('/src/*', proxyToVite); // Catch-all for other source files
170
171
 
171
- // Workbench source files (in .agentuity/workbench-src/)
172
- app.get('/.agentuity/workbench-src/*', proxyToVite);
172
+ // Workbench source files (in .agentuity/workbench-src/)
173
+ app.get('/.agentuity/workbench-src/*', proxyToVite);
173
174
 
174
- // Node modules (Vite transforms these)
175
- app.get('/node_modules/*', proxyToVite);
175
+ // Node modules (Vite transforms these)
176
+ app.get('/node_modules/*', proxyToVite);
176
177
 
177
- // Scoped packages (e.g., @agentuity/*, @types/*)
178
- app.get('/@*', proxyToVite);
178
+ // Scoped packages (e.g., @agentuity/*, @types/*)
179
+ app.get('/@*', proxyToVite);
179
180
 
180
- // File system access (for Vite's @fs protocol)
181
- app.get('/@fs/*', proxyToVite);
181
+ // File system access (for Vite's @fs protocol)
182
+ app.get('/@fs/*', proxyToVite);
182
183
 
183
- // Module resolution (for Vite's @id protocol)
184
- app.get('/@id/*', proxyToVite);
184
+ // Module resolution (for Vite's @id protocol)
185
+ app.get('/@id/*', proxyToVite);
185
186
 
186
- // Any .js, .jsx, .ts, .tsx files (catch remaining modules)
187
- app.get('/*.js', proxyToVite);
188
- app.get('/*.jsx', proxyToVite);
189
- app.get('/*.ts', proxyToVite);
190
- app.get('/*.tsx', proxyToVite);
191
- app.get('/*.css', proxyToVite);
187
+ // Any .js, .jsx, .ts, .tsx files (catch remaining modules)
188
+ app.get('/*.js', proxyToVite);
189
+ app.get('/*.jsx', proxyToVite);
190
+ app.get('/*.ts', proxyToVite);
191
+ app.get('/*.tsx', proxyToVite);
192
+ app.get('/*.css', proxyToVite);
193
+ }
192
194
  `
193
- : '';
195
+ : '';
196
+
197
+ // Runtime mode detection helper (defined at top level for reuse)
198
+ // Dynamic property access prevents Bun.build from inlining NODE_ENV at build time
199
+ const modeDetection = `
200
+ // Runtime mode detection helper
201
+ // Dynamic string concatenation prevents Bun.build from inlining NODE_ENV at build time
202
+ // See: https://github.com/oven-sh/bun/issues/20183
203
+ const getEnv = (key: string) => process.env[key];
204
+ const isDevelopment = () => getEnv('NODE' + '_' + 'ENV') !== 'production';
205
+ `;
194
206
 
195
- // Web routes (different for dev/prod)
207
+ // Web routes (runtime mode detection)
196
208
  let webRoutes = '';
197
209
  if (hasWebFrontend) {
198
- if (isDev) {
199
- webRoutes = `
200
- // Web routes (dev mode with Vite HMR via proxy)
201
- // Proxy HTML from Vite to let @vitejs/plugin-react handle React Fast Refresh preamble
202
- const devHtmlHandler = async (c: Context) => {
203
- const viteUrl = \`http://127.0.0.1:${vitePort}/src/web/index.html\`;
204
-
205
- try {
206
- otel.logger.debug('[Proxy] GET /src/web/index.html -> Vite:%d', ${vitePort});
207
- const res = await fetch(viteUrl, { signal: AbortSignal.timeout(10000) });
208
-
209
- // Get HTML text and transform relative paths to absolute
210
- const html = await res.text();
211
- const transformedHtml = html
212
- .replace(/src="\\.\\//g, 'src="/src/web/')
213
- .replace(/href="\\.\\//g, 'href="/src/web/');
214
-
215
- return new Response(transformedHtml, {
216
- status: res.status,
217
- headers: res.headers,
218
- });
219
- } catch (err) {
220
- otel.logger.error('Failed to proxy HTML to Vite: %s', err instanceof Error ? err.message : String(err));
221
- return c.text('Vite asset server error (HTML)', 500);
222
- }
223
- };
224
- app.get('/', devHtmlHandler);
225
- // 404 for unmatched API/system routes
226
- app.all('/_agentuity/*', (c: Context) => c.notFound());
227
- app.all('/api/*', (c: Context) => c.notFound());
228
- ${hasWorkbench ? '' : `app.all('/workbench/*', (c: Context) => c.notFound());`}
229
- // SPA fallback - serve index.html for client-side routing
230
- // Asset requests (/*.js, /*.tsx, /*.css, etc.) are handled by Vite proxy routes if present,
231
- // otherwise we check for file extensions to avoid returning HTML for missing assets
232
- app.get('*', (c: Context) => {
233
- const path = c.req.path;
234
- // If path has a file extension and Vite proxy isn't handling it, return 404
235
- // This prevents returning HTML for missing assets like /foo.js
236
- if (${!vitePort} && /\\.[a-zA-Z0-9]+$/.test(path)) {
237
- return c.notFound();
238
- }
239
- return devHtmlHandler(c);
240
- });
241
- `;
242
- } else {
243
- webRoutes = `
244
- // Web routes (production - static files)
245
- import { readFileSync } from 'node:fs';
246
- const indexHtml = readFileSync(import.meta.dir + '/client/index.html', 'utf-8');
247
-
248
- app.get('/', (c: Context) => c.html(indexHtml));
249
-
250
- // Serve static assets from /assets/* (Vite bundled output)
251
- app.use('/assets/*', serveStatic({ root: import.meta.dir + '/client' }));
252
-
253
- // Serve static public assets (favicon.ico, robots.txt, etc. from Vite's public folder)
254
- app.use('/*', serveStatic({ root: import.meta.dir + '/client', rewriteRequestPath: (path) => path }));
255
-
256
- // 404 for unmatched API/system routes (IMPORTANT: comes before SPA fallback)
257
- app.all('/_agentuity/*', (c: Context) => c.notFound());
258
- app.all('/api/*', (c: Context) => c.notFound());
259
- ${hasWorkbench ? '' : `app.all('/workbench/*', (c: Context) => c.notFound());`}
260
-
261
- // SPA fallback with asset protection
262
- // In production, we need to distinguish between:
263
- // - SPA routes like /dashboard, /users/123 (should return HTML)
264
- // - Missing assets like /foo.js, /bar.css (should return 404)
265
- // We check for file extensions to detect asset requests
266
- app.get('*', (c: Context) => {
267
- const path = c.req.path;
268
- // If path has a file extension, it's likely an asset request
269
- // Return 404 instead of serving HTML
270
- if (/\\.[a-zA-Z0-9]+$/.test(path)) {
271
- return c.notFound();
210
+ webRoutes = `
211
+ // Web routes - Runtime mode detection (dev proxies to Vite, prod serves static)
212
+ if (isDevelopment()) {
213
+ // Development mode: Proxy HTML from Vite to enable React Fast Refresh
214
+ const VITE_ASSET_PORT = parseInt(process.env.VITE_PORT || '${vitePort || 5173}', 10);
215
+
216
+ const devHtmlHandler = async (c: Context) => {
217
+ const viteUrl = \`http://127.0.0.1:\${VITE_ASSET_PORT}/src/web/index.html\`;
218
+
219
+ try {
220
+ otel.logger.debug('[Proxy] GET /src/web/index.html -> Vite:%d', VITE_ASSET_PORT);
221
+ const res = await fetch(viteUrl, { signal: AbortSignal.timeout(10000) });
222
+
223
+ // Get HTML text and transform relative paths to absolute
224
+ const html = await res.text();
225
+ const transformedHtml = html
226
+ .replace(/src="\\.\\//g, 'src="/src/web/')
227
+ .replace(/href="\\.\\//g, 'href="/src/web/');
228
+
229
+ return new Response(transformedHtml, {
230
+ status: res.status,
231
+ headers: res.headers,
232
+ });
233
+ } catch (err) {
234
+ otel.logger.error('Failed to proxy HTML to Vite: %s', err instanceof Error ? err.message : String(err));
235
+ return c.text('Vite asset server error (HTML)', 500);
236
+ }
237
+ };
238
+
239
+ app.get('/', devHtmlHandler);
240
+
241
+ // 404 for unmatched API/system routes
242
+ app.all('/_agentuity/*', (c: Context) => c.notFound());
243
+ app.all('/api/*', (c: Context) => c.notFound());
244
+ ${hasWorkbench ? '' : `app.all('/workbench/*', (c: Context) => c.notFound());`}
245
+
246
+ // SPA fallback - serve index.html for client-side routing
247
+ app.get('*', (c: Context) => {
248
+ const path = c.req.path;
249
+ // If path has a file extension, return 404 (prevents serving HTML for missing assets)
250
+ if (/\\.[a-zA-Z0-9]+$/.test(path)) {
251
+ return c.notFound();
252
+ }
253
+ return devHtmlHandler(c);
254
+ });
255
+ } else {
256
+ // Production mode: Serve static files from bundled output
257
+ const indexHtmlPath = import.meta.dir + '/client/index.html';
258
+ const indexHtml = existsSync(indexHtmlPath)
259
+ ? readFileSync(indexHtmlPath, 'utf-8')
260
+ : '';
261
+
262
+ if (!indexHtml) {
263
+ otel.logger.warn('Production HTML not found at %s', indexHtmlPath);
272
264
  }
273
- return c.html(indexHtml);
274
- });
275
- `;
265
+
266
+ app.get('/', (c: Context) => indexHtml ? c.html(indexHtml) : c.text('Production build incomplete', 500));
267
+
268
+ // Serve static assets from /assets/* (Vite bundled output)
269
+ app.use('/assets/*', serveStatic({ root: import.meta.dir + '/client' }));
270
+
271
+ // Serve static public assets (favicon.ico, robots.txt, etc.)
272
+ app.use('/*', serveStatic({ root: import.meta.dir + '/client', rewriteRequestPath: (path) => path }));
273
+
274
+ // 404 for unmatched API/system routes (IMPORTANT: comes before SPA fallback)
275
+ app.all('/_agentuity/*', (c: Context) => c.notFound());
276
+ app.all('/api/*', (c: Context) => c.notFound());
277
+ ${hasWorkbench ? '' : `app.all('/workbench/*', (c: Context) => c.notFound());`}
278
+
279
+ // SPA fallback with asset protection
280
+ app.get('*', (c: Context) => {
281
+ const path = c.req.path;
282
+ // If path has a file extension, it's likely an asset request - return 404
283
+ if (/\\.[a-zA-Z0-9]+$/.test(path)) {
284
+ return c.notFound();
276
285
  }
286
+ return c.html(indexHtml);
287
+ });
288
+ }
289
+ `;
277
290
  }
278
291
 
279
- // Workbench routes (if enabled)
292
+ // Workbench routes (if enabled) - runtime mode detection
280
293
  const workbenchRoute = workbench?.route ?? '/workbench';
281
- const workbenchSrcDir = join(agentuityDir, 'workbench-src');
282
294
  const workbenchRoutes = hasWorkbench
283
- ? isDev
284
- ? `
285
- // Workbench route (dev mode - let Vite serve source files with HMR)
286
- app.get('${workbenchRoute}', async (c: Context) => {
287
- const html = await Bun.file('${workbenchSrcDir}/index.html').text();
288
- // Rewrite script/css paths to use Vite's @fs protocol
289
- const withVite = html
290
- .replace('src="./main.tsx"', 'src="/@fs${workbenchSrcDir}/main.tsx"')
291
- .replace('href="./styles.css"', 'href="/@fs${workbenchSrcDir}/styles.css"');
292
- return c.html(withVite);
293
- });
294
- `
295
- : `
296
- // Workbench routes (production - serve pre-built assets)
297
- // Use import.meta.dir for absolute paths (app.js runs from .agentuity/)
298
- import { readFileSync, existsSync } from 'node:fs';
295
+ ? `
296
+ // Workbench routes - Runtime mode detection
297
+ const workbenchSrcDir = import.meta.dir + '/workbench-src';
299
298
  const workbenchIndexPath = import.meta.dir + '/workbench/index.html';
300
- if (existsSync(workbenchIndexPath)) {
301
- const workbenchIndex = readFileSync(workbenchIndexPath, 'utf-8');
302
- app.get('${workbenchRoute}', (c: Context) => c.html(workbenchIndex));
303
- app.get('${workbenchRoute}/*', serveStatic({ root: import.meta.dir + '/workbench' }));
299
+ const workbenchIndex = existsSync(workbenchIndexPath)
300
+ ? readFileSync(workbenchIndexPath, 'utf-8')
301
+ : '';
302
+
303
+ if (isDevelopment()) {
304
+ // Development mode: Let Vite serve source files with HMR
305
+ app.get('${workbenchRoute}', async (c: Context) => {
306
+ const html = await Bun.file(workbenchSrcDir + '/index.html').text();
307
+ // Rewrite script/css paths to use Vite's @fs protocol
308
+ const withVite = html
309
+ .replace('src="./main.tsx"', \`src="/@fs\${workbenchSrcDir}/main.tsx"\`)
310
+ .replace('href="./styles.css"', \`href="/@fs\${workbenchSrcDir}/styles.css"\`);
311
+ return c.html(withVite);
312
+ });
313
+ } else {
314
+ // Production mode: Serve pre-built assets
315
+ if (workbenchIndex) {
316
+ app.get('${workbenchRoute}', (c: Context) => c.html(workbenchIndex));
317
+ app.get('${workbenchRoute}/*', serveStatic({ root: import.meta.dir + '/workbench' }));
318
+ }
304
319
  }
305
320
  `
306
321
  : '';
307
322
 
308
323
  // Server startup (same for dev and prod - Bun.serve with native WebSocket)
309
324
  const serverStartup = `
310
- // Start Bun server${isDev ? ' (dev mode with Vite asset proxy)' : ''}
325
+ // Start Bun server
311
326
  if (typeof Bun !== 'undefined') {
312
327
  // Enable process exit protection now that we're starting the server
313
328
  enableProcessExitProtection();
@@ -323,20 +338,57 @@ if (typeof Bun !== 'undefined') {
323
338
  // Make server available globally for health checks
324
339
  (globalThis as any).__AGENTUITY_SERVER__ = server;
325
340
 
326
- otel.logger.info(\`Server listening on http://127.0.0.1:\${port}\`);${isDev && vitePort ? `\n\totel.logger.debug(\`Proxying Vite assets from port ${vitePort}\`);` : ''}
341
+ otel.logger.info(\`Server listening on http://127.0.0.1:\${port}\`);
342
+ if (isDevelopment() && process.env.VITE_PORT) {
343
+ otel.logger.debug(\`Proxying Vite assets from port \${process.env.VITE_PORT}\`);
344
+ }
345
+ }
346
+
347
+ // FOUND AN ERROR IN THIS FILE?
348
+ // Please file an issue at https://github.com/agentuity/sdk/issues
349
+ // or if you know the fix please submit a PR!
350
+ `;
351
+
352
+ const healthRoutes = `
353
+ // Health check routes (production only)
354
+ if (!isDevelopment()) {
355
+ const healthHandler = (c: Context) => {
356
+ return c.text('OK', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
357
+ };
358
+ const idleHandler = (c: Context) => {
359
+ // Check if server is idle (no pending requests/connections)
360
+ const server = (globalThis as any).__AGENTUITY_SERVER__;
361
+ if (!server) return c.text('NO', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
362
+
363
+ // Check for pending background tasks
364
+ if (hasWaitUntilPending()) return c.text('NO', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
365
+
366
+ if (server.pendingRequests > 1) return c.text('NO', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
367
+ if (server.pendingWebSockets > 0) return c.text('NO', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
368
+
369
+ return c.text('OK', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
370
+ };
371
+ app.get('/_agentuity/health', healthHandler);
372
+ app.get('/_health', healthHandler);
373
+ app.get('/_agentuity/idle', idleHandler);
374
+ app.get('/_idle', idleHandler);
327
375
  }
328
376
  `;
329
377
 
330
- const code = `// Auto-generated by Agentuity for ${mode} mode
378
+ const code = `// @generated
379
+ // Auto-generated by Agentuity
331
380
  // DO NOT EDIT - This file is regenerated on every build
381
+ // Supports both development and production modes via runtime detection
332
382
  ${imports.join('\n')}
333
383
 
384
+ ${modeDetection}
385
+
334
386
  // Step 0: Bootstrap runtime environment (load profile-specific .env files)
335
387
  // Only in development - production env vars are injected by platform
336
388
  // This must happen BEFORE any imports that depend on environment variables
337
- if (process.env.NODE_ENV !== 'production') {
338
- // Pass project directory (parent of .agentuity/) so .env files are loaded correctly
339
- await bootstrapRuntimeEnv({ projectDir: import.meta.dir + '/..' });
389
+ if (isDevelopment()) {
390
+ // Pass project directory (two levels up from src/generated/) so .env files are loaded correctly
391
+ await bootstrapRuntimeEnv({ projectDir: import.meta.dir + '/../..' });
340
392
  }
341
393
 
342
394
  // Step 1: Initialize telemetry and services
@@ -375,7 +427,7 @@ app.use('/api/*', createOtelMiddleware());
375
427
  app.use('/api/*', createAgentMiddleware(''));
376
428
 
377
429
  // Step 4: Import user's app.ts (runs createApp, gets state/config)
378
- await import('../app.ts');
430
+ await import('../../app.js');
379
431
 
380
432
  // Step 5: Initialize providers
381
433
  const threadProvider = getThreadProvider();
@@ -386,27 +438,7 @@ await sessionProvider.initialize(appState);
386
438
 
387
439
  // Step 6: Mount routes (AFTER middleware is applied)
388
440
 
389
- // System health/idle endpoints
390
- const healthHandler = (c: Context) => c.text('OK');
391
- const idleHandler = (c: Context) => {
392
- // Check if server is idle (no pending requests/connections)
393
- const server = (globalThis as any).__AGENTUITY_SERVER__;
394
- if (!server) return c.text('NO', { status: 200 });
395
-
396
- // Check for pending background tasks
397
- if (hasWaitUntilPending()) return c.text('NO', { status: 200 });
398
-
399
- if (server.pendingRequests > 1) return c.text('NO', { status: 200 });
400
- if (server.pendingWebSockets > 0) return c.text('NO', { status: 200 });
401
-
402
- return c.text('OK', { status: 200 });
403
- };
404
-
405
- app.get('/_agentuity/health', healthHandler);
406
- app.get('/_health', healthHandler);
407
- app.get('/_agentuity/idle', idleHandler);
408
- app.get('/_idle', idleHandler);
409
-
441
+ ${healthRoutes}
410
442
  ${assetProxyRoutes}
411
443
  ${apiMount}
412
444
  ${workbenchApiMount}
@@ -419,6 +451,9 @@ await runAgentSetups(appState);
419
451
  ${serverStartup}
420
452
  `;
421
453
 
422
- await Bun.write(entryPath, code);
423
- logger.trace(`Generated ${mode} mode entry file at %s`, entryPath);
454
+ // Collapse 2+ consecutive empty lines into 1 empty line (3+ \n becomes 2 \n)
455
+ const cleanedCode = code.replace(/\n{3,}/g, '\n\n');
456
+
457
+ await Bun.write(entryPath, cleanedCode);
458
+ logger.trace(`Generated unified entry file at %s (mode: ${mode})`, entryPath);
424
459
  }