@shepai/cli 1.26.0 → 1.27.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 (174) hide show
  1. package/dist/packages/core/src/application/ports/output/index.d.ts +1 -1
  2. package/dist/packages/core/src/application/ports/output/index.d.ts.map +1 -1
  3. package/dist/packages/core/src/application/ports/output/services/index.d.ts +1 -0
  4. package/dist/packages/core/src/application/ports/output/services/index.d.ts.map +1 -1
  5. package/dist/packages/core/src/application/ports/output/services/notification-service.interface.d.ts +29 -0
  6. package/dist/packages/core/src/application/ports/output/services/notification-service.interface.d.ts.map +1 -0
  7. package/dist/packages/core/src/application/ports/output/services/notification-service.interface.js +12 -0
  8. package/dist/packages/core/src/application/use-cases/features/create/metadata-generator.d.ts +3 -2
  9. package/dist/packages/core/src/application/use-cases/features/create/metadata-generator.d.ts.map +1 -1
  10. package/dist/packages/core/src/application/use-cases/features/create/metadata-generator.js +36 -15
  11. package/dist/packages/core/src/domain/factories/settings-defaults.factory.d.ts.map +1 -1
  12. package/dist/packages/core/src/domain/factories/settings-defaults.factory.js +13 -0
  13. package/dist/packages/core/src/domain/generated/output.d.ts +105 -0
  14. package/dist/packages/core/src/domain/generated/output.d.ts.map +1 -1
  15. package/dist/packages/core/src/domain/generated/output.js +15 -0
  16. package/dist/packages/core/src/infrastructure/di/container.d.ts.map +1 -1
  17. package/dist/packages/core/src/infrastructure/di/container.js +17 -0
  18. package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/settings.mapper.d.ts +8 -0
  19. package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/settings.mapper.d.ts.map +1 -1
  20. package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/settings.mapper.js +22 -0
  21. package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations.d.ts.map +1 -1
  22. package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations.js +14 -0
  23. package/dist/packages/core/src/infrastructure/repositories/sqlite-settings.repository.d.ts.map +1 -1
  24. package/dist/packages/core/src/infrastructure/repositories/sqlite-settings.repository.js +17 -3
  25. package/dist/packages/core/src/infrastructure/services/notifications/desktop-notifier.d.ts +22 -0
  26. package/dist/packages/core/src/infrastructure/services/notifications/desktop-notifier.d.ts.map +1 -0
  27. package/dist/packages/core/src/infrastructure/services/notifications/desktop-notifier.js +39 -0
  28. package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.d.ts +52 -0
  29. package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.d.ts.map +1 -0
  30. package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.js +64 -0
  31. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.d.ts +60 -0
  32. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.d.ts.map +1 -0
  33. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.js +225 -0
  34. package/dist/packages/core/src/infrastructure/services/notifications/notification.service.d.ts +21 -0
  35. package/dist/packages/core/src/infrastructure/services/notifications/notification.service.d.ts.map +1 -0
  36. package/dist/packages/core/src/infrastructure/services/notifications/notification.service.js +43 -0
  37. package/dist/src/presentation/cli/commands/ui.command.d.ts.map +1 -1
  38. package/dist/src/presentation/cli/commands/ui.command.js +8 -0
  39. package/dist/src/presentation/web/app/api/agent-events/route.d.ts +14 -0
  40. package/dist/src/presentation/web/app/api/agent-events/route.d.ts.map +1 -0
  41. package/dist/src/presentation/web/app/api/agent-events/route.js +77 -0
  42. package/dist/src/presentation/web/app/page.d.ts.map +1 -1
  43. package/dist/src/presentation/web/app/page.js +9 -12
  44. package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.js +2 -2
  45. package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.stories.js +3 -3
  46. package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.d.ts +5 -2
  47. package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.d.ts.map +1 -1
  48. package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.js +9 -0
  49. package/dist/src/presentation/web/components/common/feature-node/feature-node.d.ts.map +1 -1
  50. package/dist/src/presentation/web/components/common/feature-node/feature-node.js +9 -7
  51. package/dist/src/presentation/web/components/layouts/app-shell/app-shell.d.ts.map +1 -1
  52. package/dist/src/presentation/web/components/layouts/app-shell/app-shell.js +3 -0
  53. package/dist/src/presentation/web/components/ui/button.d.ts +1 -1
  54. package/dist/src/presentation/web/hooks/use-agent-events.d.ts +12 -0
  55. package/dist/src/presentation/web/hooks/use-agent-events.d.ts.map +1 -0
  56. package/dist/src/presentation/web/hooks/use-agent-events.js +58 -0
  57. package/dist/src/presentation/web/hooks/use-notifications.d.ts +10 -0
  58. package/dist/src/presentation/web/hooks/use-notifications.d.ts.map +1 -0
  59. package/dist/src/presentation/web/hooks/use-notifications.js +57 -0
  60. package/dist/src/presentation/web/hooks/use-notifications.stories.d.ts +6 -0
  61. package/dist/src/presentation/web/hooks/use-notifications.stories.d.ts.map +1 -0
  62. package/dist/src/presentation/web/hooks/use-notifications.stories.js +62 -0
  63. package/dist/tsconfig.build.tsbuildinfo +1 -1
  64. package/package.json +3 -1
  65. package/web/.next/BUILD_ID +1 -1
  66. package/web/.next/app-path-routes-manifest.json +1 -0
  67. package/web/.next/build-manifest.json +2 -2
  68. package/web/.next/cache/.previewinfo +1 -1
  69. package/web/.next/cache/.rscinfo +1 -1
  70. package/web/.next/cache/.tsbuildinfo +1 -1
  71. package/web/.next/cache/config.json +3 -3
  72. package/web/.next/fallback-build-manifest.json +2 -2
  73. package/web/.next/prerender-manifest.json +3 -3
  74. package/web/.next/required-server-files.js +1 -1
  75. package/web/.next/required-server-files.json +1 -1
  76. package/web/.next/routes-manifest.json +6 -0
  77. package/web/.next/server/app/_global-error.html +2 -2
  78. package/web/.next/server/app/_global-error.rsc +1 -1
  79. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  80. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  81. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  82. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  83. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  84. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  85. package/web/.next/server/app/_not-found.html +2 -2
  86. package/web/.next/server/app/_not-found.rsc +10 -10
  87. package/web/.next/server/app/_not-found.segments/_full.segment.rsc +10 -10
  88. package/web/.next/server/app/_not-found.segments/_head.segment.rsc +3 -3
  89. package/web/.next/server/app/_not-found.segments/_index.segment.rsc +6 -6
  90. package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  91. package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  92. package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  93. package/web/.next/server/app/api/agent-events/route/app-paths-manifest.json +3 -0
  94. package/web/.next/server/app/api/agent-events/route/build-manifest.json +11 -0
  95. package/web/.next/server/app/api/agent-events/route/server-reference-manifest.json +4 -0
  96. package/web/.next/server/app/api/agent-events/route.js +6 -0
  97. package/web/.next/server/app/api/agent-events/route.js.map +5 -0
  98. package/web/.next/server/app/api/agent-events/route.js.nft.json +1 -0
  99. package/web/.next/server/app/api/agent-events/route_client-reference-manifest.js +2 -0
  100. package/web/.next/server/app/api/dialog/pick-files/route.js +3 -2
  101. package/web/.next/server/app/api/dialog/pick-files/route.js.nft.json +1 -1
  102. package/web/.next/server/app/api/dialog/pick-folder/route.js +3 -2
  103. package/web/.next/server/app/api/dialog/pick-folder/route.js.nft.json +1 -1
  104. package/web/.next/server/app/api/features/create/route.js +3 -2
  105. package/web/.next/server/app/api/features/create/route.js.nft.json +1 -1
  106. package/web/.next/server/app/api/ide/open/route.js +4 -2
  107. package/web/.next/server/app/api/ide/open/route.js.nft.json +1 -1
  108. package/web/.next/server/app/api/shell/open/route.js +3 -2
  109. package/web/.next/server/app/api/shell/open/route.js.nft.json +1 -1
  110. package/web/.next/server/app/page.js +1 -1
  111. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  112. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  113. package/web/.next/server/app/version.html +2 -2
  114. package/web/.next/server/app/version.rsc +5 -5
  115. package/web/.next/server/app/version.segments/_full.segment.rsc +5 -5
  116. package/web/.next/server/app/version.segments/_head.segment.rsc +1 -1
  117. package/web/.next/server/app/version.segments/_index.segment.rsc +4 -4
  118. package/web/.next/server/app/version.segments/_tree.segment.rsc +2 -2
  119. package/web/.next/server/app/version.segments/version/__PAGE__.segment.rsc +2 -2
  120. package/web/.next/server/app/version.segments/version.segment.rsc +1 -1
  121. package/web/.next/server/app-paths-manifest.json +1 -0
  122. package/web/.next/server/chunks/403f9_next_567de315._.js +17 -0
  123. package/web/.next/server/chunks/403f9_next_567de315._.js.map +1 -0
  124. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_12059e3e.js +3 -0
  125. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_12059e3e.js.map +1 -0
  126. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_agent-events_route_actions_686edb08.js +3 -0
  127. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_agent-events_route_actions_686edb08.js.map +1 -0
  128. package/web/.next/server/chunks/{[root-of-the-server]__eaa478b7._.js → [root-of-the-server]__4e987cf3._.js} +2 -2
  129. package/web/.next/server/chunks/[root-of-the-server]__57104a4c._.js +7 -0
  130. package/web/.next/server/chunks/[root-of-the-server]__57104a4c._.js.map +1 -0
  131. package/web/.next/server/chunks/{[root-of-the-server]__602cce1b._.js → [root-of-the-server]__61349adc._.js} +2 -2
  132. package/web/.next/server/chunks/{[root-of-the-server]__bb4fc5b5._.js → [root-of-the-server]__a6bf88cc._.js} +2 -2
  133. package/web/.next/server/chunks/[root-of-the-server]__aa2074ca._.js +3 -0
  134. package/web/.next/server/chunks/[root-of-the-server]__aa2074ca._.js.map +1 -0
  135. package/web/.next/server/chunks/{[root-of-the-server]__1913013c._.js → [root-of-the-server]__ec87d735._.js} +2 -2
  136. package/web/.next/server/chunks/[root-of-the-server]__ecaf05bc._.js +3 -0
  137. package/web/.next/server/chunks/[root-of-the-server]__ecaf05bc._.js.map +1 -0
  138. package/web/.next/server/chunks/ssr/403f9_next_dist_esm_build_templates_app-page_dbf21e04.js +1 -1
  139. package/web/.next/server/chunks/ssr/[root-of-the-server]__80e5bed2._.js +1 -1
  140. package/web/.next/server/chunks/ssr/[root-of-the-server]__f12b60be._.js +1 -1
  141. package/web/.next/server/chunks/ssr/[root-of-the-server]__fa87595c._.js +1 -1
  142. package/web/.next/server/chunks/ssr/_09afa42a._.js +1 -1
  143. package/web/.next/server/chunks/ssr/_09afa42a._.js.map +1 -1
  144. package/web/.next/server/chunks/ssr/_884ddade._.js +1 -1
  145. package/web/.next/server/chunks/ssr/_c5d377cc._.js +1 -1
  146. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  147. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
  148. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +1 -1
  149. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js.map +1 -1
  150. package/web/.next/server/pages/404.html +2 -2
  151. package/web/.next/server/pages/500.html +2 -2
  152. package/web/.next/server/server-reference-manifest.js +1 -1
  153. package/web/.next/server/server-reference-manifest.json +1 -1
  154. package/web/.next/static/chunks/b32b17f102226f32.js +1 -0
  155. package/web/.next/static/chunks/d8905884f147599c.css +2 -0
  156. package/web/.next/static/chunks/{c2854b6c6d0ca1f3.js → fd961ef835ff4867.js} +2 -2
  157. package/web/.next/trace +1 -1
  158. package/web/.next/trace-build +1 -1
  159. package/web/.next/types/link.d.ts +1 -0
  160. package/web/.next/types/routes.d.ts +2 -1
  161. package/web/.next/types/validator.ts +9 -0
  162. package/web/.next/server/chunks/[root-of-the-server]__56ac1509._.js +0 -3
  163. package/web/.next/server/chunks/[root-of-the-server]__56ac1509._.js.map +0 -1
  164. package/web/.next/server/chunks/[root-of-the-server]__ba6c5641._.js +0 -21
  165. package/web/.next/server/chunks/[root-of-the-server]__ba6c5641._.js.map +0 -1
  166. package/web/.next/static/chunks/78ef4acb26e4d6ea.js +0 -1
  167. package/web/.next/static/chunks/86ff2b6444e06e88.css +0 -2
  168. /package/web/.next/server/chunks/{[root-of-the-server]__eaa478b7._.js.map → [root-of-the-server]__4e987cf3._.js.map} +0 -0
  169. /package/web/.next/server/chunks/{[root-of-the-server]__602cce1b._.js.map → [root-of-the-server]__61349adc._.js.map} +0 -0
  170. /package/web/.next/server/chunks/{[root-of-the-server]__bb4fc5b5._.js.map → [root-of-the-server]__a6bf88cc._.js.map} +0 -0
  171. /package/web/.next/server/chunks/{[root-of-the-server]__1913013c._.js.map → [root-of-the-server]__ec87d735._.js.map} +0 -0
  172. /package/web/.next/static/{_6j6GS-S6zPiASj5tsxJ8 → AmQVHj-yhxaqpAg8gC7MH}/_buildManifest.js +0 -0
  173. /package/web/.next/static/{_6j6GS-S6zPiASj5tsxJ8 → AmQVHj-yhxaqpAg8gC7MH}/_clientMiddlewareManifest.json +0 -0
  174. /package/web/.next/static/{_6j6GS-S6zPiASj5tsxJ8 → AmQVHj-yhxaqpAg8gC7MH}/_ssgManifest.js +0 -0
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Desktop Notifier
3
+ *
4
+ * Wraps node-notifier to send native OS desktop notifications
5
+ * (macOS Notification Center, Linux libnotify, Windows toast).
6
+ *
7
+ * All inputs are sanitized before passing to node-notifier:
8
+ * - Shell metacharacters are stripped (defense in depth)
9
+ * - Title truncated to 100 chars, body to 500 chars
10
+ * - Errors are caught and logged (never thrown)
11
+ */
12
+ import notifier from 'node-notifier';
13
+ const SHELL_METACHAR_REGEX = /[`$|;&()<>]/g;
14
+ const MAX_TITLE_LENGTH = 100;
15
+ const MAX_BODY_LENGTH = 500;
16
+ export class DesktopNotifier {
17
+ /**
18
+ * Send a native OS desktop notification.
19
+ *
20
+ * @param title - Notification title (sanitized and truncated)
21
+ * @param body - Notification body text (sanitized and truncated)
22
+ */
23
+ send(title, body) {
24
+ try {
25
+ notifier.notify({
26
+ title: this.sanitize(title, MAX_TITLE_LENGTH),
27
+ message: this.sanitize(body, MAX_BODY_LENGTH),
28
+ });
29
+ }
30
+ catch (error) {
31
+ // eslint-disable-next-line no-console
32
+ console.warn('Desktop notification failed:', error);
33
+ }
34
+ }
35
+ sanitize(input, maxLength) {
36
+ const cleaned = input.replace(SHELL_METACHAR_REGEX, '');
37
+ return cleaned.slice(0, maxLength);
38
+ }
39
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Notification Event Bus
3
+ *
4
+ * Typed EventEmitter singleton for in-process pub/sub of notification events.
5
+ * Follows the getSettings() singleton pattern from settings.service.ts.
6
+ *
7
+ * Multiple listeners can subscribe to the 'notification' event for fan-out
8
+ * delivery (SSE clients, desktop notifier, etc.).
9
+ *
10
+ * Usage:
11
+ * ```typescript
12
+ * import { getNotificationBus } from './notification-bus.js';
13
+ *
14
+ * const bus = getNotificationBus();
15
+ * bus.on('notification', (event) => { ... });
16
+ * bus.emit('notification', event);
17
+ * ```
18
+ */
19
+ import { EventEmitter } from 'node:events';
20
+ import type { NotificationEvent } from '../../../domain/generated/output.js';
21
+ export interface NotificationEventMap {
22
+ notification: [event: NotificationEvent];
23
+ }
24
+ export type NotificationBus = EventEmitter<NotificationEventMap>;
25
+ /**
26
+ * Initialize the notification event bus singleton.
27
+ * Must be called once during CLI bootstrap.
28
+ *
29
+ * @throws Error if the bus is already initialized
30
+ */
31
+ export declare function initializeNotificationBus(): void;
32
+ /**
33
+ * Get the notification event bus singleton.
34
+ *
35
+ * @returns The notification event bus
36
+ * @throws Error if the bus hasn't been initialized yet
37
+ */
38
+ export declare function getNotificationBus(): NotificationBus;
39
+ /**
40
+ * Check if the notification bus has been initialized.
41
+ *
42
+ * @returns True if the bus is initialized, false otherwise
43
+ */
44
+ export declare function hasNotificationBus(): boolean;
45
+ /**
46
+ * Reset the notification bus singleton (for testing purposes only).
47
+ * DO NOT use in production code.
48
+ *
49
+ * @internal
50
+ */
51
+ export declare function resetNotificationBus(): void;
52
+ //# sourceMappingURL=notification-bus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-bus.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/notifications/notification-bus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAE7E,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC;CAC1C;AAED,MAAM,MAAM,eAAe,GAAG,YAAY,CAAC,oBAAoB,CAAC,CAAC;AAIjE;;;;;GAKG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAMhD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CAQpD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAK3C"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Notification Event Bus
3
+ *
4
+ * Typed EventEmitter singleton for in-process pub/sub of notification events.
5
+ * Follows the getSettings() singleton pattern from settings.service.ts.
6
+ *
7
+ * Multiple listeners can subscribe to the 'notification' event for fan-out
8
+ * delivery (SSE clients, desktop notifier, etc.).
9
+ *
10
+ * Usage:
11
+ * ```typescript
12
+ * import { getNotificationBus } from './notification-bus.js';
13
+ *
14
+ * const bus = getNotificationBus();
15
+ * bus.on('notification', (event) => { ... });
16
+ * bus.emit('notification', event);
17
+ * ```
18
+ */
19
+ import { EventEmitter } from 'node:events';
20
+ let busInstance = null;
21
+ /**
22
+ * Initialize the notification event bus singleton.
23
+ * Must be called once during CLI bootstrap.
24
+ *
25
+ * @throws Error if the bus is already initialized
26
+ */
27
+ export function initializeNotificationBus() {
28
+ if (busInstance !== null) {
29
+ throw new Error('Notification bus already initialized. Cannot re-initialize.');
30
+ }
31
+ busInstance = new EventEmitter();
32
+ }
33
+ /**
34
+ * Get the notification event bus singleton.
35
+ *
36
+ * @returns The notification event bus
37
+ * @throws Error if the bus hasn't been initialized yet
38
+ */
39
+ export function getNotificationBus() {
40
+ if (busInstance === null) {
41
+ throw new Error('Notification bus not initialized. Call initializeNotificationBus() during CLI bootstrap.');
42
+ }
43
+ return busInstance;
44
+ }
45
+ /**
46
+ * Check if the notification bus has been initialized.
47
+ *
48
+ * @returns True if the bus is initialized, false otherwise
49
+ */
50
+ export function hasNotificationBus() {
51
+ return busInstance !== null;
52
+ }
53
+ /**
54
+ * Reset the notification bus singleton (for testing purposes only).
55
+ * DO NOT use in production code.
56
+ *
57
+ * @internal
58
+ */
59
+ export function resetNotificationBus() {
60
+ if (busInstance !== null) {
61
+ busInstance.removeAllListeners();
62
+ }
63
+ busInstance = null;
64
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Notification Watcher Service
3
+ *
4
+ * Polls the agent_runs and phase_timings tables to detect status
5
+ * transitions, dispatching NotificationEvents via the NotificationService
6
+ * (which handles settings filters, bus fan-out, and desktop dispatch).
7
+ *
8
+ * Maintains in-memory tracking of last-seen status per run to avoid
9
+ * duplicate notifications. Polling starts/stops with start()/stop()
10
+ * lifecycle methods.
11
+ *
12
+ * Design decision: Uses DB polling instead of IPC because the worker
13
+ * process is spawned detached with IPC disconnected.
14
+ */
15
+ import type { IAgentRunRepository } from '../../../application/ports/output/agents/agent-run-repository.interface.js';
16
+ import type { IPhaseTimingRepository } from '../../../application/ports/output/agents/phase-timing-repository.interface.js';
17
+ import type { INotificationService } from '../../../application/ports/output/services/notification-service.interface.js';
18
+ export declare class NotificationWatcherService {
19
+ private readonly runRepository;
20
+ private readonly phaseTimingRepository;
21
+ private readonly notificationService;
22
+ private readonly pollIntervalMs;
23
+ private readonly trackedRuns;
24
+ private intervalId;
25
+ constructor(runRepository: IAgentRunRepository, phaseTimingRepository: IPhaseTimingRepository, notificationService: INotificationService, pollIntervalMs?: number);
26
+ isRunning(): boolean;
27
+ start(): void;
28
+ stop(): void;
29
+ private poll;
30
+ private processRuns;
31
+ private emitStatusEvent;
32
+ private checkPhaseCompletions;
33
+ private resolveFeatureName;
34
+ }
35
+ /**
36
+ * Initialize the notification watcher singleton.
37
+ * Must be called once during web server startup.
38
+ *
39
+ * @throws Error if the watcher is already initialized
40
+ */
41
+ export declare function initializeNotificationWatcher(runRepository: IAgentRunRepository, phaseTimingRepository: IPhaseTimingRepository, notificationService: INotificationService, pollIntervalMs?: number): void;
42
+ /**
43
+ * Get the notification watcher singleton.
44
+ *
45
+ * @returns The notification watcher service
46
+ * @throws Error if the watcher hasn't been initialized yet
47
+ */
48
+ export declare function getNotificationWatcher(): NotificationWatcherService;
49
+ /**
50
+ * Check if the notification watcher has been initialized.
51
+ */
52
+ export declare function hasNotificationWatcher(): boolean;
53
+ /**
54
+ * Reset the notification watcher singleton (for testing purposes only).
55
+ * Stops the watcher if running before resetting.
56
+ *
57
+ * @internal
58
+ */
59
+ export declare function resetNotificationWatcher(): void;
60
+ //# sourceMappingURL=notification-watcher.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-watcher.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/notifications/notification-watcher.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4EAA4E,CAAC;AACtH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+EAA+E,CAAC;AAC5H,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,8EAA8E,CAAC;AAmDzH,qBAAa,0BAA0B;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;IACpD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAyB;IAC/D,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAuB;IAC3D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmC;IAC/D,OAAO,CAAC,UAAU,CAA+C;gBAG/D,aAAa,EAAE,mBAAmB,EAClC,qBAAqB,EAAE,sBAAsB,EAC7C,mBAAmB,EAAE,oBAAoB,EACzC,cAAc,GAAE,MAAiC;IAQnD,SAAS,IAAI,OAAO;IAIpB,KAAK,IAAI,IAAI;IAWb,IAAI,IAAI,IAAI;YAOE,IAAI;YAUJ,WAAW;IAoDzB,OAAO,CAAC,eAAe;YAgBT,qBAAqB;IAsBnC,OAAO,CAAC,kBAAkB;CAS3B;AAMD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,aAAa,EAAE,mBAAmB,EAClC,qBAAqB,EAAE,sBAAsB,EAC7C,mBAAmB,EAAE,oBAAoB,EACzC,cAAc,CAAC,EAAE,MAAM,GACtB,IAAI,CAWN;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,0BAA0B,CAQnE;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAK/C"}
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Notification Watcher Service
3
+ *
4
+ * Polls the agent_runs and phase_timings tables to detect status
5
+ * transitions, dispatching NotificationEvents via the NotificationService
6
+ * (which handles settings filters, bus fan-out, and desktop dispatch).
7
+ *
8
+ * Maintains in-memory tracking of last-seen status per run to avoid
9
+ * duplicate notifications. Polling starts/stops with start()/stop()
10
+ * lifecycle methods.
11
+ *
12
+ * Design decision: Uses DB polling instead of IPC because the worker
13
+ * process is spawned detached with IPC disconnected.
14
+ */
15
+ import { AgentRunStatus, NotificationEventType, NotificationSeverity, } from '../../../domain/generated/output.js';
16
+ const DEFAULT_POLL_INTERVAL_MS = 3000;
17
+ const ACTIVE_STATUSES = new Set([
18
+ AgentRunStatus.pending,
19
+ AgentRunStatus.running,
20
+ AgentRunStatus.waitingApproval,
21
+ ]);
22
+ const TERMINAL_STATUSES = new Set([
23
+ AgentRunStatus.completed,
24
+ AgentRunStatus.failed,
25
+ AgentRunStatus.cancelled,
26
+ AgentRunStatus.interrupted,
27
+ ]);
28
+ const STATUS_TO_EVENT = {
29
+ [AgentRunStatus.running]: {
30
+ eventType: NotificationEventType.AgentStarted,
31
+ severity: NotificationSeverity.Info,
32
+ },
33
+ [AgentRunStatus.waitingApproval]: {
34
+ eventType: NotificationEventType.WaitingApproval,
35
+ severity: NotificationSeverity.Warning,
36
+ },
37
+ [AgentRunStatus.completed]: {
38
+ eventType: NotificationEventType.AgentCompleted,
39
+ severity: NotificationSeverity.Success,
40
+ },
41
+ [AgentRunStatus.failed]: {
42
+ eventType: NotificationEventType.AgentFailed,
43
+ severity: NotificationSeverity.Error,
44
+ },
45
+ };
46
+ const EVENT_MESSAGES = {
47
+ [NotificationEventType.AgentStarted]: 'Feature agent started',
48
+ [NotificationEventType.WaitingApproval]: 'Feature agent is waiting for approval',
49
+ [NotificationEventType.AgentCompleted]: 'Feature agent completed successfully',
50
+ [NotificationEventType.AgentFailed]: 'Feature agent failed',
51
+ };
52
+ export class NotificationWatcherService {
53
+ runRepository;
54
+ phaseTimingRepository;
55
+ notificationService;
56
+ pollIntervalMs;
57
+ trackedRuns = new Map();
58
+ intervalId = null;
59
+ constructor(runRepository, phaseTimingRepository, notificationService, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS) {
60
+ this.runRepository = runRepository;
61
+ this.phaseTimingRepository = phaseTimingRepository;
62
+ this.notificationService = notificationService;
63
+ this.pollIntervalMs = pollIntervalMs;
64
+ }
65
+ isRunning() {
66
+ return this.intervalId !== null;
67
+ }
68
+ start() {
69
+ if (this.intervalId !== null)
70
+ return;
71
+ // Run first poll immediately
72
+ void this.poll();
73
+ this.intervalId = setInterval(() => {
74
+ void this.poll();
75
+ }, this.pollIntervalMs);
76
+ }
77
+ stop() {
78
+ if (this.intervalId !== null) {
79
+ clearInterval(this.intervalId);
80
+ this.intervalId = null;
81
+ }
82
+ }
83
+ async poll() {
84
+ try {
85
+ const runs = await this.runRepository.list();
86
+ await this.processRuns(runs);
87
+ }
88
+ catch (error) {
89
+ // eslint-disable-next-line no-console
90
+ console.warn('NotificationWatcherService poll failed:', error);
91
+ }
92
+ }
93
+ async processRuns(runs) {
94
+ const currentRunIds = new Set();
95
+ for (const run of runs) {
96
+ if (!ACTIVE_STATUSES.has(run.status) && !this.trackedRuns.has(run.id)) {
97
+ // Skip terminal runs we haven't been tracking
98
+ continue;
99
+ }
100
+ currentRunIds.add(run.id);
101
+ const prevState = this.trackedRuns.get(run.id);
102
+ if (!prevState) {
103
+ // New run — track and emit initial status event
104
+ const featureName = this.resolveFeatureName(run);
105
+ const newState = {
106
+ status: run.status,
107
+ completedPhases: new Set(),
108
+ featureName,
109
+ };
110
+ this.trackedRuns.set(run.id, newState);
111
+ this.emitStatusEvent(run, newState);
112
+ // Check for completed phases on first observation
113
+ await this.checkPhaseCompletions(run.id, newState);
114
+ }
115
+ else if (prevState.status !== run.status) {
116
+ // Status changed — emit event
117
+ prevState.status = run.status;
118
+ this.emitStatusEvent(run, prevState);
119
+ if (TERMINAL_STATUSES.has(run.status)) {
120
+ // Run reached terminal state — clean up after emitting
121
+ this.trackedRuns.delete(run.id);
122
+ }
123
+ else {
124
+ // Still active — check for phase completions
125
+ await this.checkPhaseCompletions(run.id, prevState);
126
+ }
127
+ }
128
+ else {
129
+ // Same status — just check for phase completions
130
+ await this.checkPhaseCompletions(run.id, prevState);
131
+ }
132
+ }
133
+ // Clean up tracking for runs that disappeared from the list
134
+ for (const trackedId of this.trackedRuns.keys()) {
135
+ if (!currentRunIds.has(trackedId)) {
136
+ this.trackedRuns.delete(trackedId);
137
+ }
138
+ }
139
+ }
140
+ emitStatusEvent(run, state) {
141
+ const mapping = STATUS_TO_EVENT[run.status];
142
+ if (!mapping)
143
+ return;
144
+ const event = {
145
+ eventType: mapping.eventType,
146
+ agentRunId: run.id,
147
+ featureName: state.featureName,
148
+ message: EVENT_MESSAGES[mapping.eventType] ?? `Agent status: ${run.status}`,
149
+ severity: mapping.severity,
150
+ timestamp: new Date().toISOString(),
151
+ };
152
+ this.notificationService.notify(event);
153
+ }
154
+ async checkPhaseCompletions(runId, state) {
155
+ const timings = await this.phaseTimingRepository.findByRunId(runId);
156
+ for (const timing of timings) {
157
+ if (timing.completedAt && !state.completedPhases.has(timing.phase)) {
158
+ state.completedPhases.add(timing.phase);
159
+ const event = {
160
+ eventType: NotificationEventType.PhaseCompleted,
161
+ agentRunId: runId,
162
+ featureName: state.featureName,
163
+ phaseName: timing.phase,
164
+ message: `Completed ${timing.phase} phase`,
165
+ severity: NotificationSeverity.Info,
166
+ timestamp: new Date().toISOString(),
167
+ };
168
+ this.notificationService.notify(event);
169
+ }
170
+ }
171
+ }
172
+ resolveFeatureName(run) {
173
+ // Feature name resolution from featureId would require IFeatureRepository.
174
+ // For now, use the agentName or a fallback. The NotificationService or
175
+ // a higher layer can enrich with the actual feature name if needed.
176
+ if (run.featureId) {
177
+ return `Feature ${run.featureId}`;
178
+ }
179
+ return `Agent ${run.id}`;
180
+ }
181
+ }
182
+ // --- Singleton accessors (follows getNotificationBus() pattern) ---
183
+ let watcherInstance = null;
184
+ /**
185
+ * Initialize the notification watcher singleton.
186
+ * Must be called once during web server startup.
187
+ *
188
+ * @throws Error if the watcher is already initialized
189
+ */
190
+ export function initializeNotificationWatcher(runRepository, phaseTimingRepository, notificationService, pollIntervalMs) {
191
+ if (watcherInstance !== null) {
192
+ throw new Error('Notification watcher already initialized. Cannot re-initialize.');
193
+ }
194
+ watcherInstance = new NotificationWatcherService(runRepository, phaseTimingRepository, notificationService, pollIntervalMs);
195
+ }
196
+ /**
197
+ * Get the notification watcher singleton.
198
+ *
199
+ * @returns The notification watcher service
200
+ * @throws Error if the watcher hasn't been initialized yet
201
+ */
202
+ export function getNotificationWatcher() {
203
+ if (watcherInstance === null) {
204
+ throw new Error('Notification watcher not initialized. Call initializeNotificationWatcher() during web server startup.');
205
+ }
206
+ return watcherInstance;
207
+ }
208
+ /**
209
+ * Check if the notification watcher has been initialized.
210
+ */
211
+ export function hasNotificationWatcher() {
212
+ return watcherInstance !== null;
213
+ }
214
+ /**
215
+ * Reset the notification watcher singleton (for testing purposes only).
216
+ * Stops the watcher if running before resetting.
217
+ *
218
+ * @internal
219
+ */
220
+ export function resetNotificationWatcher() {
221
+ if (watcherInstance !== null) {
222
+ watcherInstance.stop();
223
+ }
224
+ watcherInstance = null;
225
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Notification Service
3
+ *
4
+ * Implements INotificationService port interface. Fans out notification
5
+ * events to enabled channels:
6
+ * - Notification bus (for SSE → in-app toasts and browser notifications)
7
+ * - Desktop notifier (node-notifier for OS-level notifications)
8
+ *
9
+ * Channel enable/disable and event type filters are read from Settings.
10
+ */
11
+ import type { NotificationEvent } from '../../../domain/generated/output.js';
12
+ import type { INotificationService } from '../../../application/ports/output/services/notification-service.interface.js';
13
+ import type { NotificationBus } from './notification-bus.js';
14
+ import type { DesktopNotifier } from './desktop-notifier.js';
15
+ export declare class NotificationService implements INotificationService {
16
+ private readonly bus;
17
+ private readonly desktopNotifier;
18
+ constructor(bus: NotificationBus, desktopNotifier: DesktopNotifier);
19
+ notify(event: NotificationEvent): void;
20
+ }
21
+ //# sourceMappingURL=notification.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/notifications/notification.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACV,iBAAiB,EAElB,MAAM,qCAAqC,CAAC;AAE7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,8EAA8E,CAAC;AAEzH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAU7D,qBAAa,mBAAoB,YAAW,oBAAoB;IAC9D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkB;IACtC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;gBAEtC,GAAG,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;IAKlE,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI;CAmBvC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Notification Service
3
+ *
4
+ * Implements INotificationService port interface. Fans out notification
5
+ * events to enabled channels:
6
+ * - Notification bus (for SSE → in-app toasts and browser notifications)
7
+ * - Desktop notifier (node-notifier for OS-level notifications)
8
+ *
9
+ * Channel enable/disable and event type filters are read from Settings.
10
+ */
11
+ import { NotificationEventType } from '../../../domain/generated/output.js';
12
+ import { getSettings } from '../settings.service.js';
13
+ const EVENT_TYPE_TO_CONFIG_KEY = {
14
+ [NotificationEventType.AgentStarted]: 'agentStarted',
15
+ [NotificationEventType.PhaseCompleted]: 'phaseCompleted',
16
+ [NotificationEventType.WaitingApproval]: 'waitingApproval',
17
+ [NotificationEventType.AgentCompleted]: 'agentCompleted',
18
+ [NotificationEventType.AgentFailed]: 'agentFailed',
19
+ };
20
+ export class NotificationService {
21
+ bus;
22
+ desktopNotifier;
23
+ constructor(bus, desktopNotifier) {
24
+ this.bus = bus;
25
+ this.desktopNotifier = desktopNotifier;
26
+ }
27
+ notify(event) {
28
+ const { notifications } = getSettings();
29
+ // Check event type filter
30
+ const configKey = EVENT_TYPE_TO_CONFIG_KEY[event.eventType];
31
+ if (configKey && !notifications.events[configKey]) {
32
+ return;
33
+ }
34
+ // Emit to bus if in-app or browser channel is enabled
35
+ if (notifications.inApp.enabled || notifications.browser.enabled) {
36
+ this.bus.emit('notification', event);
37
+ }
38
+ // Dispatch to desktop notifier if desktop channel is enabled
39
+ if (notifications.desktop.enabled) {
40
+ this.desktopNotifier.send(event.featureName, event.message);
41
+ }
42
+ }
43
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"ui.command.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/cli/commands/ui.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,OAAO,EAAwB,MAAM,WAAW,CAAC;AAiB1D;;GAEG;AACH,wBAAgB,eAAe,IAAI,OAAO,CA0DzC"}
1
+ {"version":3,"file":"ui.command.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/cli/commands/ui.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,OAAO,EAAwB,MAAM,WAAW,CAAC;AAwB1D;;GAEG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAkEzC"}
@@ -19,6 +19,7 @@ import { findAvailablePort, DEFAULT_PORT } from '../../../../packages/core/src/i
19
19
  import { container } from '../../../../packages/core/src/infrastructure/di/container.js';
20
20
  import { setVersionEnvVars } from '../../../../packages/core/src/infrastructure/services/version.service.js';
21
21
  import { resolveWebDir } from '../../../../packages/core/src/infrastructure/services/web-server.service.js';
22
+ import { initializeNotificationWatcher, getNotificationWatcher, } from '../../../../packages/core/src/infrastructure/services/notifications/notification-watcher.service.js';
22
23
  import { colors, fmt, messages } from '../ui/index.js';
23
24
  function parsePort(value) {
24
25
  const port = parseInt(value, 10);
@@ -52,6 +53,12 @@ Examples:
52
53
  messages.newline();
53
54
  const service = container.resolve('IWebServerService');
54
55
  await service.start(port, dir, dev);
56
+ // Start notification watcher to detect agent status transitions
57
+ const runRepo = container.resolve('IAgentRunRepository');
58
+ const phaseTimingRepo = container.resolve('IPhaseTimingRepository');
59
+ const notificationService = container.resolve('INotificationService');
60
+ initializeNotificationWatcher(runRepo, phaseTimingRepo, notificationService);
61
+ getNotificationWatcher().start();
55
62
  messages.success(`Server ready at ${fmt.code(`http://localhost:${port}`)}`);
56
63
  messages.info('Press Ctrl+C to stop');
57
64
  messages.newline();
@@ -67,6 +74,7 @@ Examples:
67
74
  // Force exit after 5s if graceful shutdown stalls
68
75
  const forceExit = setTimeout(() => process.exit(0), 5000);
69
76
  forceExit.unref();
77
+ getNotificationWatcher().stop();
70
78
  await service.stop();
71
79
  process.exit(0);
72
80
  };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * SSE API Route: GET /api/agent-events
3
+ *
4
+ * Streams agent lifecycle notification events to connected web UI clients
5
+ * via Server-Sent Events (SSE). This is the first API route in the web UI.
6
+ *
7
+ * - Subscribes to the notification event bus (shared in-process singleton)
8
+ * - Formats events as SSE data frames (event: notification\ndata: JSON\n\n)
9
+ * - Sends heartbeat comments every 30 seconds to keep connection alive
10
+ * - Supports optional ?runId query parameter to filter events
11
+ * - Cleans up listeners and intervals on client disconnect
12
+ */
13
+ export declare function GET(request: Request): Response;
14
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/app/api/agent-events/route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAkBH,wBAAgB,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CA+D9C"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * SSE API Route: GET /api/agent-events
3
+ *
4
+ * Streams agent lifecycle notification events to connected web UI clients
5
+ * via Server-Sent Events (SSE). This is the first API route in the web UI.
6
+ *
7
+ * - Subscribes to the notification event bus (shared in-process singleton)
8
+ * - Formats events as SSE data frames (event: notification\ndata: JSON\n\n)
9
+ * - Sends heartbeat comments every 30 seconds to keep connection alive
10
+ * - Supports optional ?runId query parameter to filter events
11
+ * - Cleans up listeners and intervals on client disconnect
12
+ */
13
+ import { getNotificationBus, hasNotificationBus, } from '../../../../../../packages/core/src/infrastructure/services/notifications/notification-bus.js';
14
+ const HEARTBEAT_INTERVAL_MS = 30_000;
15
+ function formatSSEEvent(event) {
16
+ return `event: notification\ndata: ${JSON.stringify(event)}\n\n`;
17
+ }
18
+ function formatHeartbeat() {
19
+ return ': heartbeat\n\n';
20
+ }
21
+ export function GET(request) {
22
+ const url = new URL(request.url);
23
+ const runIdFilter = url.searchParams.get('runId');
24
+ const stream = new ReadableStream({
25
+ start(controller) {
26
+ const encoder = new TextEncoder();
27
+ // Check if bus is available
28
+ if (!hasNotificationBus()) {
29
+ controller.close();
30
+ return;
31
+ }
32
+ const bus = getNotificationBus();
33
+ // Notification event listener
34
+ const onNotification = (event) => {
35
+ // Apply runId filter if set
36
+ if (runIdFilter && event.agentRunId !== runIdFilter) {
37
+ return;
38
+ }
39
+ try {
40
+ controller.enqueue(encoder.encode(formatSSEEvent(event)));
41
+ }
42
+ catch {
43
+ // Stream may be closed — ignore enqueue errors
44
+ }
45
+ };
46
+ bus.on('notification', onNotification);
47
+ // Heartbeat to keep connection alive
48
+ const heartbeatInterval = setInterval(() => {
49
+ try {
50
+ controller.enqueue(encoder.encode(formatHeartbeat()));
51
+ }
52
+ catch {
53
+ // Stream may be closed — ignore enqueue errors
54
+ }
55
+ }, HEARTBEAT_INTERVAL_MS);
56
+ // Cleanup on client disconnect
57
+ const cleanup = () => {
58
+ bus.removeListener('notification', onNotification);
59
+ clearInterval(heartbeatInterval);
60
+ try {
61
+ controller.close();
62
+ }
63
+ catch {
64
+ // Stream may already be closed
65
+ }
66
+ };
67
+ request.signal.addEventListener('abort', cleanup, { once: true });
68
+ },
69
+ });
70
+ return new Response(stream, {
71
+ headers: {
72
+ 'Content-Type': 'text/event-stream',
73
+ 'Cache-Control': 'no-cache',
74
+ Connection: 'keep-alive',
75
+ },
76
+ });
77
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/app/page.tsx"],"names":[],"mappings":"AAQA,6EAA6E;AAC7E,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAqBvC,wBAA8B,QAAQ,qDAsFrC"}
1
+ {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/app/page.tsx"],"names":[],"mappings":"AAWA,6EAA6E;AAC7E,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAqBvC,wBAA8B,QAAQ,qDAmFrC"}