@agentuity/runtime 1.0.47 → 2.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/_globals.d.ts +58 -0
  2. package/dist/_globals.d.ts.map +1 -0
  3. package/dist/_globals.js +71 -0
  4. package/dist/_globals.js.map +1 -0
  5. package/dist/_metadata.d.ts.map +1 -1
  6. package/dist/_metadata.js +14 -0
  7. package/dist/_metadata.js.map +1 -1
  8. package/dist/_process-protection.d.ts +2 -0
  9. package/dist/_process-protection.d.ts.map +1 -1
  10. package/dist/_process-protection.js +14 -23
  11. package/dist/_process-protection.js.map +1 -1
  12. package/dist/_server.d.ts +4 -0
  13. package/dist/_server.d.ts.map +1 -1
  14. package/dist/_server.js +4 -0
  15. package/dist/_server.js.map +1 -1
  16. package/dist/_services.d.ts +1 -1
  17. package/dist/_services.d.ts.map +1 -1
  18. package/dist/_services.js +5 -1
  19. package/dist/_services.js.map +1 -1
  20. package/dist/_standalone.d.ts.map +1 -1
  21. package/dist/_standalone.js +3 -9
  22. package/dist/_standalone.js.map +1 -1
  23. package/dist/agent.d.ts.map +1 -1
  24. package/dist/agent.js +1 -0
  25. package/dist/agent.js.map +1 -1
  26. package/dist/app.d.ts +149 -71
  27. package/dist/app.d.ts.map +1 -1
  28. package/dist/app.js +121 -156
  29. package/dist/app.js.map +1 -1
  30. package/dist/bootstrap.d.ts +44 -0
  31. package/dist/bootstrap.d.ts.map +1 -0
  32. package/dist/bootstrap.js +256 -0
  33. package/dist/bootstrap.js.map +1 -0
  34. package/dist/dev-patches/aisdk.d.ts.map +1 -1
  35. package/dist/dev-patches/aisdk.js +6 -8
  36. package/dist/dev-patches/aisdk.js.map +1 -1
  37. package/dist/dev-patches/gateway.d.ts.map +1 -1
  38. package/dist/dev-patches/gateway.js +7 -8
  39. package/dist/dev-patches/gateway.js.map +1 -1
  40. package/dist/handlers/_route-meta.d.ts +20 -0
  41. package/dist/handlers/_route-meta.d.ts.map +1 -0
  42. package/dist/handlers/_route-meta.js +25 -0
  43. package/dist/handlers/_route-meta.js.map +1 -0
  44. package/dist/handlers/cron.d.ts.map +1 -1
  45. package/dist/handlers/cron.js +3 -1
  46. package/dist/handlers/cron.js.map +1 -1
  47. package/dist/handlers/sse.d.ts +3 -3
  48. package/dist/handlers/sse.d.ts.map +1 -1
  49. package/dist/handlers/sse.js +4 -16
  50. package/dist/handlers/sse.js.map +1 -1
  51. package/dist/handlers/stream.d.ts.map +1 -1
  52. package/dist/handlers/stream.js +4 -12
  53. package/dist/handlers/stream.js.map +1 -1
  54. package/dist/handlers/websocket.d.ts +3 -1
  55. package/dist/handlers/websocket.d.ts.map +1 -1
  56. package/dist/handlers/websocket.js +6 -37
  57. package/dist/handlers/websocket.js.map +1 -1
  58. package/dist/index.d.ts +1 -1
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +1 -1
  61. package/dist/index.js.map +1 -1
  62. package/dist/middleware.d.ts +1 -8
  63. package/dist/middleware.d.ts.map +1 -1
  64. package/dist/middleware.js +29 -71
  65. package/dist/middleware.js.map +1 -1
  66. package/dist/otel/logger.d.ts.map +1 -1
  67. package/dist/otel/logger.js +4 -7
  68. package/dist/otel/logger.js.map +1 -1
  69. package/dist/otel/otel.d.ts +4 -1
  70. package/dist/otel/otel.d.ts.map +1 -1
  71. package/dist/otel/otel.js +13 -2
  72. package/dist/otel/otel.js.map +1 -1
  73. package/dist/router.d.ts +10 -62
  74. package/dist/router.d.ts.map +1 -1
  75. package/dist/router.js +9 -146
  76. package/dist/router.js.map +1 -1
  77. package/dist/workbench.d.ts +1 -1
  78. package/dist/workbench.d.ts.map +1 -1
  79. package/dist/workbench.js +120 -12
  80. package/dist/workbench.js.map +1 -1
  81. package/package.json +7 -7
  82. package/src/_globals.ts +92 -0
  83. package/src/_metadata.ts +14 -0
  84. package/src/_process-protection.ts +17 -28
  85. package/src/_server.ts +4 -0
  86. package/src/_services.ts +6 -2
  87. package/src/_standalone.ts +4 -9
  88. package/src/agent.ts +1 -0
  89. package/src/app.ts +294 -195
  90. package/src/bootstrap.ts +316 -0
  91. package/src/dev-patches/aisdk.ts +8 -11
  92. package/src/dev-patches/gateway.ts +9 -11
  93. package/src/globals.d.ts +28 -0
  94. package/src/handlers/_route-meta.ts +31 -0
  95. package/src/handlers/cron.ts +4 -1
  96. package/src/handlers/sse.ts +8 -19
  97. package/src/handlers/stream.ts +5 -12
  98. package/src/handlers/websocket.ts +11 -37
  99. package/src/index.ts +2 -3
  100. package/src/middleware.ts +40 -99
  101. package/src/otel/logger.ts +5 -8
  102. package/src/otel/otel.ts +14 -2
  103. package/src/router.ts +12 -216
  104. package/src/workbench.ts +135 -12
package/src/app.ts CHANGED
@@ -124,7 +124,47 @@ export interface CompressionConfig {
124
124
  honoOptions?: HonoCompressOptions;
125
125
  }
126
126
 
127
- export interface AppConfig<TAppState = Record<string, never>> {
127
+ /**
128
+ * Web analytics configuration options.
129
+ */
130
+ export interface AnalyticsOptions {
131
+ /** Enable/disable analytics @default true */
132
+ enabled?: boolean;
133
+ /** Require explicit user consent before tracking @default false */
134
+ requireConsent?: boolean;
135
+ /** Track click events on elements with data-analytics attribute @default true */
136
+ trackClicks?: boolean;
137
+ /** Track scroll depth @default true */
138
+ trackScroll?: boolean;
139
+ /** Track outbound link clicks @default true */
140
+ trackOutboundLinks?: boolean;
141
+ /** Track form submissions @default false */
142
+ trackForms?: boolean;
143
+ /** Track Core Web Vitals (LCP, FID, CLS) @default true */
144
+ trackWebVitals?: boolean;
145
+ /** Track JavaScript errors @default true */
146
+ trackErrors?: boolean;
147
+ /** Track SPA navigation changes @default true */
148
+ trackSPANavigation?: boolean;
149
+ /** Sampling rate (0-1) @default 1 */
150
+ sampleRate?: number;
151
+ /** URL patterns to exclude from tracking */
152
+ excludePatterns?: string[];
153
+ /** Global properties attached to every event */
154
+ globalProperties?: Record<string, unknown>;
155
+ }
156
+
157
+ /**
158
+ * Workbench UI configuration options.
159
+ */
160
+ export interface WorkbenchOptions {
161
+ /** Route path for the workbench UI @default '/workbench' */
162
+ route?: string;
163
+ /** Custom headers to include in workbench responses */
164
+ headers?: Record<string, string>;
165
+ }
166
+
167
+ export interface AppConfig {
128
168
  /**
129
169
  * Configure CORS (Cross-Origin Resource Sharing) settings.
130
170
  *
@@ -211,21 +251,62 @@ export interface AppConfig<TAppState = Record<string, never>> {
211
251
  email?: EmailService;
212
252
  };
213
253
  /**
214
- * Optional setup function called before server starts
215
- * Returns app state that will be available in all agents and routes
254
+ * Optional request timeout in seconds. If not provided, will default
255
+ * to zero which will cause the request to wait indefinitely.
216
256
  */
217
- setup?: () => Promise<TAppState> | TAppState;
257
+ requestTimeout?: number;
258
+
218
259
  /**
219
- * Optional shutdown function called when server is stopping
220
- * Receives the app state returned from setup
260
+ * Configure web analytics for frontend tracking.
261
+ *
262
+ * Set to `true` to enable with defaults, `false` to disable, or provide
263
+ * a configuration object to customize tracking behavior.
264
+ *
265
+ * @default true
266
+ *
267
+ * @example
268
+ * ```typescript
269
+ * // Enable with defaults
270
+ * const app = await createApp({ analytics: true });
271
+ *
272
+ * // Disable analytics
273
+ * const app = await createApp({ analytics: false });
274
+ *
275
+ * // Custom configuration
276
+ * const app = await createApp({
277
+ * analytics: {
278
+ * trackClicks: false,
279
+ * sampleRate: 0.5,
280
+ * }
281
+ * });
282
+ * ```
221
283
  */
222
- shutdown?: (state: TAppState) => Promise<void> | void;
284
+ analytics?: boolean | AnalyticsOptions;
223
285
 
224
286
  /**
225
- * Optional request timeout in seconds. If not provided, will default
226
- * to zero which will cause the request to wait indefinitely.
287
+ * Configure the workbench UI for agent testing.
288
+ *
289
+ * Set to `true` to enable at `/workbench`, a string to set a custom route,
290
+ * or an object for full configuration. Only active in development mode.
291
+ *
292
+ * @example
293
+ * ```typescript
294
+ * // Enable at default route (/workbench)
295
+ * const app = await createApp({ workbench: true });
296
+ *
297
+ * // Custom route
298
+ * const app = await createApp({ workbench: '/debug' });
299
+ *
300
+ * // Full configuration
301
+ * const app = await createApp({
302
+ * workbench: {
303
+ * route: '/debug',
304
+ * headers: { 'X-Custom': 'value' },
305
+ * }
306
+ * });
307
+ * ```
227
308
  */
228
- requestTimeout?: number;
309
+ workbench?: boolean | string | WorkbenchOptions;
229
310
 
230
311
  /**
231
312
  * **Experimental** — Optional user-provided router(s) to use instead of file-based routing.
@@ -273,6 +354,31 @@ export interface AppConfig<TAppState = Record<string, never>> {
273
354
  */
274
355
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
275
356
  router?: import('hono').Hono<any, any, any> | RouteMount | RouteMount[];
357
+
358
+ /**
359
+ * Agents to register with this application.
360
+ *
361
+ * Each agent is a value returned by `createAgent()`. Importing the agent
362
+ * module triggers self-registration; listing them here ensures they are
363
+ * included in the build and available for workbench metadata, setup/shutdown
364
+ * lifecycle, and agent-to-agent calls via `ctx.invoke()`.
365
+ *
366
+ * Type safety for agent calls comes from direct imports — use
367
+ * `ctx.invoke(() => myAgent.run(input))` for fully typed invocations.
368
+ *
369
+ * @example
370
+ * ```typescript
371
+ * import greeting from './agent/greeting/agent';
372
+ * import session from './agent/session/agent';
373
+ *
374
+ * export default await createApp({
375
+ * agents: [greeting, session],
376
+ * router,
377
+ * });
378
+ * ```
379
+ */
380
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
381
+ agents?: import('./agent').AgentRunner<any, any, any>[];
276
382
  }
277
383
 
278
384
  /**
@@ -337,12 +443,7 @@ export function getApp(): null {
337
443
 
338
444
  // Re-export event functions from _events
339
445
  export { fireEvent } from './_events';
340
- import {
341
- addEventListener as globalAddEventListener,
342
- removeEventListener as globalRemoveEventListener,
343
- } from './_events';
344
- import type { AppEventMap } from './_events';
345
- import { getLogger, getRouter } from './_server';
446
+
346
447
  import type { Hono } from 'hono';
347
448
 
348
449
  // ============================================================================
@@ -359,180 +460,213 @@ export interface Server {
359
460
  url: string;
360
461
  }
361
462
 
362
- export interface AppResult<TAppState = Record<string, never>> {
463
+ export interface AppResult {
363
464
  /**
364
- * The application state returned from setup
465
+ * App configuration
365
466
  */
366
- state: TAppState;
467
+ config?: AppConfig;
367
468
  /**
368
- * Shutdown function to call when server stops
469
+ * The Hono router instance
369
470
  */
370
- shutdown?: (state: TAppState) => Promise<void> | void;
471
+ router: import('hono').Hono<Env>;
371
472
  /**
372
- * App configuration (for middleware setup)
473
+ * Server information
373
474
  */
374
- config?: AppConfig<TAppState>;
475
+ server: Server;
375
476
  /**
376
- * The router instance (for backwards compatibility)
477
+ * Logger instance
377
478
  */
378
- router: import('hono').Hono<Env<TAppState>>;
479
+ logger: Logger;
379
480
  /**
380
- * Server information (for backwards compatibility)
481
+ * Fetch handler for the application.
482
+ * Bun --hot uses this on the default export to hot-swap the running server's
483
+ * request handler without restarting the process.
381
484
  */
382
- server: Server;
485
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
486
+ fetch: (req: Request, ...args: any[]) => Response | Promise<Response>;
383
487
  /**
384
- * Logger instance (for backwards compatibility)
488
+ * Port the server listens on.
489
+ * Used by Bun --hot alongside `fetch` to configure the server.
385
490
  */
386
- logger: Logger;
491
+ port: number;
387
492
  /**
388
- * Add an event listener for app events
493
+ * Hostname the server binds to.
389
494
  */
390
- addEventListener<K extends keyof AppEventMap<TAppState>>(
391
- eventName: K,
392
- callback: (eventName: K, ...args: AppEventMap<TAppState>[K]) => void | Promise<void>
393
- ): void;
495
+ hostname: string;
394
496
  /**
395
- * Remove an event listener for app events
497
+ * WebSocket handler for Bun.serve().
498
+ * Required by Bun --hot to enable WebSocket upgrade support.
396
499
  */
397
- removeEventListener<K extends keyof AppEventMap<TAppState>>(
398
- eventName: K,
399
- callback: (eventName: K, ...args: AppEventMap<TAppState>[K]) => void | Promise<void>
400
- ): void;
500
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
501
+ websocket?: any;
401
502
  }
402
503
 
403
504
  /**
404
- * Create an Agentuity application with lifecycle management.
405
- *
406
- * In Vite-native architecture:
407
- * - This only handles setup/shutdown lifecycle
408
- * - Router creation and middleware are handled by the generated entry file
409
- * - Server is managed by Vite (dev) or Bun.serve (prod)
505
+ * Create and start an Agentuity application.
410
506
  *
411
- * @template TAppState - Type of application state from setup()
507
+ * This is the single entry point for the entire server lifecycle:
508
+ * OTel, middleware, route mounting, services, and Bun.serve().
412
509
  *
413
510
  * @example
414
511
  * ```typescript
415
- * // app.ts
416
512
  * import { createApp } from '@agentuity/runtime';
513
+ * import router from './src/api/router';
514
+ * import agents from './src/agent';
417
515
  *
418
- * const app = await createApp({
419
- * setup: async () => {
420
- * const db = await connectDB();
421
- * return { db };
422
- * },
423
- * shutdown: async (state) => {
424
- * await state.db.close();
425
- * }
516
+ * export default await createApp({
517
+ * router: { path: '/api', router },
518
+ * agents,
426
519
  * });
427
- *
428
- * // Access state in agents via ctx.app.db
429
520
  * ```
430
521
  */
431
- export async function createApp<TAppState = Record<string, never>>(
432
- config?: AppConfig<TAppState>
433
- ): Promise<AppResult<TAppState>> {
434
- // Run setup to get app state
435
- const state = config?.setup ? await config.setup() : ({} as TAppState);
436
-
437
- // Store state and config globally for generated entry file to access
438
- (globalThis as any).__AGENTUITY_APP_STATE__ = state;
439
- (globalThis as any).__AGENTUITY_APP_CONFIG__ = config;
440
-
441
- // Store user-provided router(s) normalized as RouteMount[] for the entry file.
442
- // When set, the entry file mounts these instead of auto-discovered route files.
443
- if (config?.router) {
444
- (globalThis as any).__AGENTUITY_USER_ROUTER__ = normalizeRouterConfig(config.router);
522
+ export async function createApp(config?: AppConfig): Promise<AppResult> {
523
+ // --- Imports (lazy to avoid circular deps) ---
524
+ const { bootstrapRuntimeEnv } = await import('@agentuity/server');
525
+ const { register } = await import('./otel/config');
526
+ const { setGlobalLogger, setGlobalTracer, setGlobalRouter, getSpanProcessors } = await import(
527
+ './_server'
528
+ );
529
+ const { createServices, getThreadProvider, getSessionProvider } = await import('./_services');
530
+ const {
531
+ createBaseMiddleware,
532
+ createCorsMiddleware,
533
+ createOtelMiddleware,
534
+ createCompressionMiddleware,
535
+ } = await import('./middleware');
536
+ const { runAgentSetups, createAgentMiddleware } = await import('./agent');
537
+ const { loadBuildMetadata } = await import('./_metadata');
538
+ const { patchBunS3ForStorageDev } = await import('./bun-s3-patch');
539
+ const { createWorkbenchRouter } = await import('./workbench');
540
+ const {
541
+ isDevelopment,
542
+ resolveAnalyticsConfig,
543
+ resolveWorkbenchConfig,
544
+ registerHealthRoutes,
545
+ registerAnalyticsRoutes,
546
+ registerWebRoutes,
547
+ registerWorkbenchUI,
548
+ startServer,
549
+ } = await import('./bootstrap');
550
+ const { websocket } = await import('hono/bun');
551
+
552
+ // --- Step 0: Environment ---
553
+ if (isDevelopment()) {
554
+ bootstrapRuntimeEnv();
555
+ }
556
+ if (isDevelopment() && process.env.AGENTUITY_NO_BUNDLE === 'true') {
557
+ const { applyDevPatches } = await import('./dev-patches');
558
+ await applyDevPatches();
559
+ }
560
+ loadBuildMetadata();
561
+ patchBunS3ForStorageDev();
562
+
563
+ // --- Step 1: Telemetry ---
564
+ const otel = register({
565
+ processors: getSpanProcessors(),
566
+ logLevel: (process.env.AGENTUITY_LOG_LEVEL || 'info') as import('@agentuity/core').LogLevel,
567
+ });
568
+ setGlobalLogger(otel.logger);
569
+ setGlobalTracer(otel.tracer);
570
+
571
+ // --- Step 2: Router + middleware ---
572
+ const { createRouter } = await import('./router');
573
+ const app = createRouter();
574
+ setGlobalRouter(app);
575
+
576
+ app.use('*', createCompressionMiddleware(config?.compression));
577
+ app.use(
578
+ '*',
579
+ createBaseMiddleware({
580
+ logger: otel.logger,
581
+ tracer: otel.tracer,
582
+ meter: otel.meter,
583
+ })
584
+ );
585
+ app.use('/_agentuity/workbench/*', createOtelMiddleware());
586
+
587
+ // --- Step 3: Services ---
588
+ const port = process.env.PORT || '3500';
589
+ const serverUrl = `http://127.0.0.1:${port}`;
590
+ createServices(otel.logger, config, serverUrl);
591
+
592
+ const threadProvider = getThreadProvider();
593
+ const sessionProvider = getSessionProvider();
594
+ await threadProvider.initialize({});
595
+ await sessionProvider.initialize({});
596
+
597
+ // --- Step 4: Routes ---
598
+ const analyticsConfig = resolveAnalyticsConfig(config?.analytics);
599
+ const workbenchConfig = resolveWorkbenchConfig(config?.workbench);
600
+
601
+ registerHealthRoutes(app);
602
+
603
+ if (analyticsConfig.enabled) {
604
+ registerAnalyticsRoutes(app, analyticsConfig);
445
605
  }
446
606
 
447
- // Store shutdown function for cleanup
448
- const shutdown = config?.shutdown;
449
- if (shutdown) {
450
- (globalThis as any).__AGENTUITY_SHUTDOWN__ = shutdown;
607
+ // Mount user routers
608
+ if (config?.router) {
609
+ const mounts = normalizeRouterConfig(config.router);
610
+ for (const mount of mounts) {
611
+ const prefix = mount.path.endsWith('/') ? mount.path + '*' : mount.path + '/*';
612
+ app.use(prefix, createCorsMiddleware(config?.cors));
613
+ app.use(prefix, createOtelMiddleware());
614
+ app.use(prefix, createAgentMiddleware(''));
615
+ app.route(mount.path, mount.router);
616
+ }
451
617
  }
452
618
 
453
- // Return a logger proxy that lazily resolves to the global logger
454
- // This is necessary because Vite bundling inlines and reorders module code,
455
- // causing app.ts to execute before entry file sets the global logger.
456
- // The proxy ensures logger works correctly when actually used (in handlers/callbacks).
457
- const logger: Logger = {
458
- trace: (...args) => {
459
- const gl = getLogger();
460
- if (gl) gl.trace(...args);
461
- },
462
- debug: (...args) => {
463
- const gl = getLogger();
464
- if (gl) gl.debug(...args);
465
- },
466
- info: (...args) => {
467
- const gl = getLogger();
468
- if (gl) gl.info(...args);
469
- else console.log('[INFO]', ...args);
470
- },
471
- warn: (...args) => {
472
- const gl = getLogger();
473
- if (gl) gl.warn(...args);
474
- else console.warn('[WARN]', ...args);
475
- },
476
- error: (...args) => {
477
- const gl = getLogger();
478
- if (gl) gl.error(...args);
479
- else console.error('[ERROR]', ...args);
480
- },
481
- fatal: (...args): never => {
482
- const gl = getLogger();
483
- if (gl) return gl.fatal(...args);
484
- // Fallback: log to console but let the real logger handle exit
485
- console.error('[FATAL]', ...args);
486
- throw new Error('Fatal error');
487
- },
488
- child: (bindings) => {
489
- const gl = getLogger();
490
- return gl ? gl.child(bindings) : logger;
491
- },
492
- };
619
+ // Workbench
620
+ const workbenchRouter = createWorkbenchRouter();
621
+ app.route('/', workbenchRouter);
622
+ registerWorkbenchUI(app, workbenchConfig);
493
623
 
494
- // Create server info from environment
495
- const port = process.env.PORT || '3500';
496
- const server: Server = {
497
- url: `http://127.0.0.1:${port}`,
498
- };
624
+ // Web (production static serving)
625
+ registerWebRoutes(app, analyticsConfig);
626
+
627
+ // --- Step 5: Agent lifecycle + server ---
628
+ await runAgentSetups({});
629
+
630
+ // In dev mode with --hot, Bun manages the server via the default export's
631
+ // `fetch` property. In production, we start Bun.serve() explicitly.
632
+ if (!isDevelopment()) {
633
+ startServer(app, { requestTimeout: config?.requestTimeout });
634
+ }
499
635
 
500
- // Get router from global (set by entry file before app.ts import)
501
- // In dev mode, router may not be available during bundling
502
- const globalRouter = getRouter();
503
- if (!globalRouter) {
504
- throw new Error(
505
- 'Router is not available. Ensure router is initialized before calling createApp(). This typically happens during bundling or when the entry file has not properly set up the router.'
506
- );
636
+ // Only log on first startup, not on --hot reloads
637
+ const { serverStarted } = await import('./_globals');
638
+ if (!serverStarted.get()) {
639
+ serverStarted.set(true);
640
+ otel.logger.debug('Server listening on %s', serverUrl);
507
641
  }
508
- const router = globalRouter as Hono<Env<TAppState>>;
509
642
 
510
- return {
511
- state,
512
- shutdown,
643
+ const portNumber = parseInt(port, 10);
644
+
645
+ const result: AppResult = {
513
646
  config,
514
- router,
515
- server,
516
- logger,
517
- addEventListener: globalAddEventListener,
518
- removeEventListener: globalRemoveEventListener,
647
+ router: app as Hono<Env>,
648
+ server: { url: serverUrl },
649
+ logger: otel.logger,
650
+ // Bun --hot picks up `fetch` and `port` on the default export to
651
+ // hot-swap the running server's request handler without restarting.
652
+ fetch: app.fetch,
653
+ port: portNumber,
654
+ hostname: '127.0.0.1',
655
+ websocket,
519
656
  };
520
- }
521
657
 
522
- /**
523
- * Get the global app state
524
- * Used by generated entry file and middleware
525
- */
526
- export function getAppState<TAppState = any>(): TAppState {
527
- return (globalThis as any).__AGENTUITY_APP_STATE__ || ({} as TAppState);
528
- }
658
+ // In production, startServer() already called Bun.serve(). If we leave
659
+ // `fetch` + `port` on the default export, Bun v1.2+ auto-serves from it
660
+ // too causing EADDRINUSE. Strip those properties so only the explicit
661
+ // Bun.serve() is active.
662
+ if (!isDevelopment()) {
663
+ delete (result as unknown as Record<string, unknown>).fetch;
664
+ delete (result as unknown as Record<string, unknown>).port;
665
+ delete (result as unknown as Record<string, unknown>).hostname;
666
+ delete (result as unknown as Record<string, unknown>).websocket;
667
+ }
529
668
 
530
- /**
531
- * Get the global app config
532
- * Used by generated entry file for middleware setup
533
- */
534
- export function getAppConfig<TAppState = any>(): AppConfig<TAppState> | undefined {
535
- return (globalThis as any).__AGENTUITY_APP_CONFIG__;
669
+ return result;
536
670
  }
537
671
 
538
672
  /**
@@ -542,7 +676,7 @@ export function getAppConfig<TAppState = any>(): AppConfig<TAppState> | undefine
542
676
  * - [{ path, router }, ...] → as-is
543
677
  * @internal
544
678
  */
545
- function normalizeRouterConfig(
679
+ export function normalizeRouterConfig(
546
680
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
547
681
  router: import('hono').Hono<any, any, any> | RouteMount | RouteMount[]
548
682
  ): RouteMount[] {
@@ -556,33 +690,6 @@ function normalizeRouterConfig(
556
690
  return [{ path: '/api', router: router as import('hono').Hono<any, any, any> }];
557
691
  }
558
692
 
559
- /**
560
- * Get the user-provided router mounts from createApp({ router }).
561
- * Returns undefined if no user router was provided (file-based routing).
562
- * Used by generated entry file to skip file-based route discovery.
563
- * @internal
564
- */
565
- export function getUserRouter(): RouteMount[] | undefined {
566
- return (globalThis as any).__AGENTUITY_USER_ROUTER__;
567
- }
568
-
569
- /**
570
- * Set the global app config (for testing purposes)
571
- * @internal
572
- */
573
- export function setAppConfig<TAppState = any>(config: AppConfig<TAppState> | undefined): void {
574
- if (config === undefined) {
575
- delete (globalThis as any).__AGENTUITY_APP_CONFIG__;
576
- } else {
577
- (globalThis as any).__AGENTUITY_APP_CONFIG__ = config;
578
- }
579
- }
580
-
581
- /**
582
- * Symbol used to store shutdown hooks in globalThis.
583
- */
584
- const SHUTDOWN_HOOKS_KEY = Symbol.for('@agentuity/runtime:shutdown-hooks');
585
-
586
693
  /**
587
694
  * A shutdown hook function.
588
695
  */
@@ -592,11 +699,13 @@ export type ShutdownHook = () => Promise<void> | void;
592
699
  * Gets the global shutdown hooks registry.
593
700
  */
594
701
  function getShutdownHooks(): ShutdownHook[] {
595
- const global = globalThis as Record<symbol, ShutdownHook[]>;
596
- if (!global[SHUTDOWN_HOOKS_KEY]) {
597
- global[SHUTDOWN_HOOKS_KEY] = [];
702
+ const key = Symbol.for('@agentuity/runtime:shutdown-hooks');
703
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
704
+ const g = globalThis as any;
705
+ if (!g[key]) {
706
+ g[key] = [];
598
707
  }
599
- return global[SHUTDOWN_HOOKS_KEY];
708
+ return g[key];
600
709
  }
601
710
 
602
711
  /**
@@ -637,22 +746,12 @@ export function registerShutdownHook(hook: ShutdownHook): () => void {
637
746
  }
638
747
 
639
748
  /**
640
- * Run the global shutdown function and all registered shutdown hooks.
641
- * Called by generated entry file on cleanup.
749
+ * Run all registered shutdown hooks.
750
+ * Called during graceful shutdown (SIGTERM/SIGINT).
642
751
  *
643
- * Shutdown order:
644
- * 1. App's shutdown callback (if defined)
645
- * 2. Registered shutdown hooks (in reverse order - LIFO)
752
+ * Hooks are called in reverse order of registration (LIFO).
646
753
  */
647
754
  export async function runShutdown(): Promise<void> {
648
- // Run app's shutdown callback first
649
- const shutdown = (globalThis as any).__AGENTUITY_SHUTDOWN__;
650
- if (shutdown) {
651
- const state = getAppState();
652
- await shutdown(state);
653
- }
654
-
655
- // Run registered shutdown hooks in reverse order (LIFO)
656
755
  const hooks = getShutdownHooks();
657
756
  for (let i = hooks.length - 1; i >= 0; i--) {
658
757
  const hook = hooks[i];