@shepai/cli 1.25.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 (241) 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/ide-launchers/antigravity.launcher.d.ts.map +1 -1
  26. package/dist/packages/core/src/infrastructure/services/ide-launchers/compute-worktree-path.d.ts +17 -0
  27. package/dist/packages/core/src/infrastructure/services/ide-launchers/compute-worktree-path.d.ts.map +1 -0
  28. package/dist/packages/core/src/infrastructure/services/ide-launchers/compute-worktree-path.js +24 -0
  29. package/dist/packages/core/src/infrastructure/services/ide-launchers/cursor.launcher.d.ts.map +1 -1
  30. package/dist/packages/core/src/infrastructure/services/ide-launchers/ide-launcher.interface.d.ts.map +1 -1
  31. package/dist/packages/core/src/infrastructure/services/ide-launchers/ide-launcher.registry.d.ts.map +1 -1
  32. package/dist/packages/core/src/infrastructure/services/ide-launchers/launch-ide.d.ts +30 -0
  33. package/dist/packages/core/src/infrastructure/services/ide-launchers/launch-ide.d.ts.map +1 -0
  34. package/dist/packages/core/src/infrastructure/services/ide-launchers/launch-ide.js +41 -0
  35. package/dist/packages/core/src/infrastructure/services/ide-launchers/vscode.launcher.d.ts.map +1 -1
  36. package/dist/packages/core/src/infrastructure/services/ide-launchers/windsurf.launcher.d.ts.map +1 -1
  37. package/dist/packages/core/src/infrastructure/services/ide-launchers/zed.launcher.d.ts.map +1 -1
  38. package/dist/packages/core/src/infrastructure/services/notifications/desktop-notifier.d.ts +22 -0
  39. package/dist/packages/core/src/infrastructure/services/notifications/desktop-notifier.d.ts.map +1 -0
  40. package/dist/packages/core/src/infrastructure/services/notifications/desktop-notifier.js +39 -0
  41. package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.d.ts +52 -0
  42. package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.d.ts.map +1 -0
  43. package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.js +64 -0
  44. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.d.ts +60 -0
  45. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.d.ts.map +1 -0
  46. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.js +225 -0
  47. package/dist/packages/core/src/infrastructure/services/notifications/notification.service.d.ts +21 -0
  48. package/dist/packages/core/src/infrastructure/services/notifications/notification.service.d.ts.map +1 -0
  49. package/dist/packages/core/src/infrastructure/services/notifications/notification.service.js +43 -0
  50. package/dist/packages/core/src/infrastructure/services/settings.service.d.ts +3 -1
  51. package/dist/packages/core/src/infrastructure/services/settings.service.d.ts.map +1 -1
  52. package/dist/packages/core/src/infrastructure/services/settings.service.js +27 -12
  53. package/dist/src/presentation/cli/commands/agent/show.command.d.ts.map +1 -1
  54. package/dist/src/presentation/cli/commands/agent/show.command.js +1 -8
  55. package/dist/src/presentation/cli/commands/feat/show.command.d.ts.map +1 -1
  56. package/dist/src/presentation/cli/commands/feat/show.command.js +1 -8
  57. package/dist/src/presentation/cli/commands/ide-open.command.d.ts.map +1 -1
  58. package/dist/src/presentation/cli/commands/ide-open.command.js +11 -21
  59. package/dist/src/presentation/cli/commands/ui.command.d.ts.map +1 -1
  60. package/dist/src/presentation/cli/commands/ui.command.js +8 -0
  61. package/dist/src/presentation/web/app/api/agent-events/route.d.ts +14 -0
  62. package/dist/src/presentation/web/app/api/agent-events/route.d.ts.map +1 -0
  63. package/dist/src/presentation/web/app/api/agent-events/route.js +77 -0
  64. package/dist/src/presentation/web/app/api/ide/open/route.d.ts +9 -0
  65. package/dist/src/presentation/web/app/api/ide/open/route.d.ts.map +1 -0
  66. package/dist/src/presentation/web/app/api/ide/open/route.js +29 -0
  67. package/dist/src/presentation/web/app/api/shell/open/route.d.ts +9 -0
  68. package/dist/src/presentation/web/app/api/shell/open/route.d.ts.map +1 -0
  69. package/dist/src/presentation/web/app/api/shell/open/route.js +51 -0
  70. package/dist/src/presentation/web/app/api/validate-toolbar-input.d.ts +17 -0
  71. package/dist/src/presentation/web/app/api/validate-toolbar-input.d.ts.map +1 -0
  72. package/dist/src/presentation/web/app/api/validate-toolbar-input.js +25 -0
  73. package/dist/src/presentation/web/app/page.d.ts.map +1 -1
  74. package/dist/src/presentation/web/app/page.js +11 -12
  75. package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.d.ts.map +1 -1
  76. package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.js +13 -4
  77. package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.stories.d.ts +8 -0
  78. package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.stories.d.ts.map +1 -1
  79. package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.stories.js +34 -3
  80. package/dist/src/presentation/web/components/common/feature-drawer/use-feature-actions.d.ts +14 -0
  81. package/dist/src/presentation/web/components/common/feature-drawer/use-feature-actions.d.ts.map +1 -0
  82. package/dist/src/presentation/web/components/common/feature-drawer/use-feature-actions.js +58 -0
  83. package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.d.ts +9 -2
  84. package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.d.ts.map +1 -1
  85. package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.js +9 -0
  86. package/dist/src/presentation/web/components/common/feature-node/feature-node.d.ts.map +1 -1
  87. package/dist/src/presentation/web/components/common/feature-node/feature-node.js +9 -7
  88. package/dist/src/presentation/web/components/common/feature-node/feature-node.stories.d.ts.map +1 -1
  89. package/dist/src/presentation/web/components/common/feature-node/feature-node.stories.js +18 -0
  90. package/dist/src/presentation/web/components/common/index.d.ts +1 -0
  91. package/dist/src/presentation/web/components/common/index.d.ts.map +1 -1
  92. package/dist/src/presentation/web/components/common/index.js +1 -0
  93. package/dist/src/presentation/web/components/features/control-center/control-center.stories.d.ts.map +1 -1
  94. package/dist/src/presentation/web/components/features/control-center/control-center.stories.js +16 -0
  95. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
  96. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +2 -0
  97. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.stories.d.ts.map +1 -1
  98. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.stories.js +44 -0
  99. package/dist/src/presentation/web/components/layouts/app-shell/app-shell.d.ts.map +1 -1
  100. package/dist/src/presentation/web/components/layouts/app-shell/app-shell.js +3 -0
  101. package/dist/src/presentation/web/components/ui/button.d.ts +1 -1
  102. package/dist/src/presentation/web/hooks/use-agent-events.d.ts +12 -0
  103. package/dist/src/presentation/web/hooks/use-agent-events.d.ts.map +1 -0
  104. package/dist/src/presentation/web/hooks/use-agent-events.js +58 -0
  105. package/dist/src/presentation/web/hooks/use-notifications.d.ts +10 -0
  106. package/dist/src/presentation/web/hooks/use-notifications.d.ts.map +1 -0
  107. package/dist/src/presentation/web/hooks/use-notifications.js +57 -0
  108. package/dist/src/presentation/web/hooks/use-notifications.stories.d.ts +6 -0
  109. package/dist/src/presentation/web/hooks/use-notifications.stories.d.ts.map +1 -0
  110. package/dist/src/presentation/web/hooks/use-notifications.stories.js +62 -0
  111. package/dist/tsconfig.build.tsbuildinfo +1 -1
  112. package/package.json +3 -1
  113. package/web/.next/BUILD_ID +1 -1
  114. package/web/.next/app-path-routes-manifest.json +3 -0
  115. package/web/.next/build-manifest.json +2 -2
  116. package/web/.next/cache/.previewinfo +1 -1
  117. package/web/.next/cache/.rscinfo +1 -1
  118. package/web/.next/cache/.tsbuildinfo +1 -1
  119. package/web/.next/cache/config.json +3 -3
  120. package/web/.next/fallback-build-manifest.json +2 -2
  121. package/web/.next/prerender-manifest.json +3 -3
  122. package/web/.next/required-server-files.js +1 -1
  123. package/web/.next/required-server-files.json +1 -1
  124. package/web/.next/routes-manifest.json +18 -0
  125. package/web/.next/server/app/_global-error/page.js +1 -1
  126. package/web/.next/server/app/_global-error/page.js.nft.json +1 -1
  127. package/web/.next/server/app/_global-error.html +2 -2
  128. package/web/.next/server/app/_global-error.rsc +1 -1
  129. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  130. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  131. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  132. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  133. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  134. package/web/.next/server/app/_not-found/page.js +1 -1
  135. package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  136. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  137. package/web/.next/server/app/_not-found.html +2 -2
  138. package/web/.next/server/app/_not-found.rsc +10 -10
  139. package/web/.next/server/app/_not-found.segments/_full.segment.rsc +10 -10
  140. package/web/.next/server/app/_not-found.segments/_head.segment.rsc +3 -3
  141. package/web/.next/server/app/_not-found.segments/_index.segment.rsc +6 -6
  142. package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  143. package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  144. package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  145. package/web/.next/server/app/api/agent-events/route/app-paths-manifest.json +3 -0
  146. package/web/.next/server/app/api/agent-events/route/build-manifest.json +11 -0
  147. package/web/.next/server/app/api/agent-events/route/server-reference-manifest.json +4 -0
  148. package/web/.next/server/app/api/agent-events/route.js +6 -0
  149. package/web/.next/server/app/api/agent-events/route.js.map +5 -0
  150. package/web/.next/server/app/api/agent-events/route.js.nft.json +1 -0
  151. package/web/.next/server/app/api/agent-events/route_client-reference-manifest.js +2 -0
  152. package/web/.next/server/app/api/dialog/pick-files/route.js +2 -1
  153. package/web/.next/server/app/api/dialog/pick-files/route.js.nft.json +1 -1
  154. package/web/.next/server/app/api/dialog/pick-folder/route.js +2 -1
  155. package/web/.next/server/app/api/dialog/pick-folder/route.js.nft.json +1 -1
  156. package/web/.next/server/app/api/features/create/route.js +2 -1
  157. package/web/.next/server/app/api/features/create/route.js.nft.json +1 -1
  158. package/web/.next/server/app/api/ide/open/route/app-paths-manifest.json +3 -0
  159. package/web/.next/server/app/api/ide/open/route/build-manifest.json +11 -0
  160. package/web/.next/server/app/api/ide/open/route/server-reference-manifest.json +4 -0
  161. package/web/.next/server/app/api/ide/open/route.js +8 -0
  162. package/web/.next/server/app/api/ide/open/route.js.map +5 -0
  163. package/web/.next/server/app/api/ide/open/route.js.nft.json +1 -0
  164. package/web/.next/server/app/api/ide/open/route_client-reference-manifest.js +2 -0
  165. package/web/.next/server/app/api/shell/open/route/app-paths-manifest.json +3 -0
  166. package/web/.next/server/app/api/shell/open/route/build-manifest.json +11 -0
  167. package/web/.next/server/app/api/shell/open/route/server-reference-manifest.json +4 -0
  168. package/web/.next/server/app/api/shell/open/route.js +7 -0
  169. package/web/.next/server/app/api/shell/open/route.js.map +5 -0
  170. package/web/.next/server/app/api/shell/open/route.js.nft.json +1 -0
  171. package/web/.next/server/app/api/shell/open/route_client-reference-manifest.js +2 -0
  172. package/web/.next/server/app/page.js +2 -2
  173. package/web/.next/server/app/page.js.nft.json +1 -1
  174. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  175. package/web/.next/server/app/version/page.js +1 -1
  176. package/web/.next/server/app/version/page.js.nft.json +1 -1
  177. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  178. package/web/.next/server/app/version.html +2 -2
  179. package/web/.next/server/app/version.rsc +5 -5
  180. package/web/.next/server/app/version.segments/_full.segment.rsc +5 -5
  181. package/web/.next/server/app/version.segments/_head.segment.rsc +1 -1
  182. package/web/.next/server/app/version.segments/_index.segment.rsc +4 -4
  183. package/web/.next/server/app/version.segments/_tree.segment.rsc +2 -2
  184. package/web/.next/server/app/version.segments/version/__PAGE__.segment.rsc +2 -2
  185. package/web/.next/server/app/version.segments/version.segment.rsc +1 -1
  186. package/web/.next/server/app-paths-manifest.json +3 -0
  187. package/web/.next/server/chunks/403f9_next_567de315._.js +17 -0
  188. package/web/.next/server/chunks/403f9_next_567de315._.js.map +1 -0
  189. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_12059e3e.js +3 -0
  190. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_12059e3e.js.map +1 -0
  191. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_agent-events_route_actions_686edb08.js +3 -0
  192. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_agent-events_route_actions_686edb08.js.map +1 -0
  193. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_shell_open_route_actions_814dc5b6.js +3 -0
  194. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_shell_open_route_actions_814dc5b6.js.map +1 -0
  195. package/web/.next/server/chunks/[root-of-the-server]__57104a4c._.js +7 -0
  196. package/web/.next/server/chunks/[root-of-the-server]__57104a4c._.js.map +1 -0
  197. package/web/.next/server/chunks/[root-of-the-server]__61349adc._.js +3 -0
  198. package/web/.next/server/chunks/[root-of-the-server]__61349adc._.js.map +1 -0
  199. package/web/.next/server/chunks/[root-of-the-server]__aa2074ca._.js +3 -0
  200. package/web/.next/server/chunks/[root-of-the-server]__aa2074ca._.js.map +1 -0
  201. package/web/.next/server/chunks/[root-of-the-server]__ecaf05bc._.js +3 -0
  202. package/web/.next/server/chunks/[root-of-the-server]__ecaf05bc._.js.map +1 -0
  203. package/web/.next/server/chunks/b1a17_presentation_web__next-internal_server_app_api_ide_open_route_actions_9fe6e1cd.js +3 -0
  204. package/web/.next/server/chunks/b1a17_presentation_web__next-internal_server_app_api_ide_open_route_actions_9fe6e1cd.js.map +1 -0
  205. package/web/.next/server/chunks/ssr/403f9_next_dist_esm_build_templates_app-page_dbf21e04.js +1 -1
  206. package/web/.next/server/chunks/ssr/[root-of-the-server]__80e5bed2._.js +1 -1
  207. package/web/.next/server/chunks/ssr/[root-of-the-server]__97377864._.js +3 -0
  208. package/web/.next/server/chunks/ssr/[root-of-the-server]__f12b60be._.js +1 -1
  209. package/web/.next/server/chunks/ssr/[root-of-the-server]__f285e474._.js +3 -0
  210. package/web/.next/server/chunks/ssr/[root-of-the-server]__fa87595c._.js +1 -1
  211. package/web/.next/server/chunks/ssr/_09afa42a._.js +1 -1
  212. package/web/.next/server/chunks/ssr/_09afa42a._.js.map +1 -1
  213. package/web/.next/server/chunks/ssr/_884ddade._.js +1 -1
  214. package/web/.next/server/chunks/ssr/_c5d377cc._.js +1 -1
  215. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  216. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
  217. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +1 -1
  218. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js.map +1 -1
  219. package/web/.next/server/pages/404.html +2 -2
  220. package/web/.next/server/pages/500.html +2 -2
  221. package/web/.next/server/server-reference-manifest.js +1 -1
  222. package/web/.next/server/server-reference-manifest.json +1 -1
  223. package/web/.next/static/chunks/b32b17f102226f32.js +1 -0
  224. package/web/.next/static/chunks/d8905884f147599c.css +2 -0
  225. package/web/.next/static/chunks/{1c236dcc303f3033.js → fd961ef835ff4867.js} +2 -2
  226. package/web/.next/trace +1 -1
  227. package/web/.next/trace-build +1 -1
  228. package/web/.next/types/link.d.ts +3 -0
  229. package/web/.next/types/routes.d.ts +4 -1
  230. package/web/.next/types/validator.ts +27 -0
  231. package/web/.next/server/chunks/[root-of-the-server]__ba6c5641._.js +0 -21
  232. package/web/.next/server/chunks/[root-of-the-server]__ba6c5641._.js.map +0 -1
  233. package/web/.next/server/chunks/ssr/[root-of-the-server]__757c7912._.js +0 -3
  234. package/web/.next/server/chunks/ssr/[root-of-the-server]__dbd220d2._.js +0 -3
  235. package/web/.next/static/chunks/74748b4d725c5c74.css +0 -2
  236. package/web/.next/static/chunks/78ef4acb26e4d6ea.js +0 -1
  237. /package/web/.next/server/chunks/ssr/{[root-of-the-server]__757c7912._.js.map → [root-of-the-server]__97377864._.js.map} +0 -0
  238. /package/web/.next/server/chunks/ssr/{[root-of-the-server]__dbd220d2._.js.map → [root-of-the-server]__f285e474._.js.map} +0 -0
  239. /package/web/.next/static/{-dFLqLT3RMg1ueqbAhsJk → AmQVHj-yhxaqpAg8gC7MH}/_buildManifest.js +0 -0
  240. /package/web/.next/static/{-dFLqLT3RMg1ueqbAhsJk → AmQVHj-yhxaqpAg8gC7MH}/_clientMiddlewareManifest.json +0 -0
  241. /package/web/.next/static/{-dFLqLT3RMg1ueqbAhsJk → AmQVHj-yhxaqpAg8gC7MH}/_ssgManifest.js +0 -0
@@ -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
+ }
@@ -2,7 +2,9 @@
2
2
  * Settings Service
3
3
  *
4
4
  * Provides global access to application settings within the CLI.
5
- * Implements singleton pattern for consistent settings access.
5
+ * Uses globalThis/process storage so the singleton survives Turbopack
6
+ * module re-evaluations in Next.js API routes (same pattern as the
7
+ * use-cases bridge in di/use-cases-bridge.ts).
6
8
  *
7
9
  * Usage:
8
10
  * ```typescript
@@ -1 +1 @@
1
- {"version":3,"file":"settings.service.d.ts","sourceRoot":"","sources":["../../../../../../packages/core/src/infrastructure/services/settings.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAQjE;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAM3D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,IAAI,QAAQ,CAMtC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAEpC"}
1
+ {"version":3,"file":"settings.service.d.ts","sourceRoot":"","sources":["../../../../../../packages/core/src/infrastructure/services/settings.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAsBjE;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAM3D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,IAAI,QAAQ,CAOtC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAEpC"}
@@ -2,7 +2,9 @@
2
2
  * Settings Service
3
3
  *
4
4
  * Provides global access to application settings within the CLI.
5
- * Implements singleton pattern for consistent settings access.
5
+ * Uses globalThis/process storage so the singleton survives Turbopack
6
+ * module re-evaluations in Next.js API routes (same pattern as the
7
+ * use-cases bridge in di/use-cases-bridge.ts).
6
8
  *
7
9
  * Usage:
8
10
  * ```typescript
@@ -12,11 +14,23 @@
12
14
  * console.log(settings.models.analyze); // 'claude-opus-4'
13
15
  * ```
14
16
  */
15
- /**
16
- * Singleton settings instance.
17
- * Set during CLI initialization, accessed throughout application lifecycle.
18
- */
19
- let settingsInstance = null;
17
+ /** The globalThis / process key for the settings singleton. */
18
+ const SHEP_SETTINGS_KEY = '__shepSettings';
19
+ /** Read the settings instance from globalThis, falling back to process. */
20
+ function readSettings() {
21
+ const fromGlobal = globalThis[SHEP_SETTINGS_KEY];
22
+ if (fromGlobal != null)
23
+ return fromGlobal;
24
+ const fromProcess = process[SHEP_SETTINGS_KEY];
25
+ if (fromProcess != null)
26
+ return fromProcess;
27
+ return null;
28
+ }
29
+ /** Write the settings instance to both globalThis and process. */
30
+ function writeSettings(value) {
31
+ globalThis[SHEP_SETTINGS_KEY] = value;
32
+ process[SHEP_SETTINGS_KEY] = value;
33
+ }
20
34
  /**
21
35
  * Initialize the settings service with loaded settings.
22
36
  * Must be called once during CLI bootstrap.
@@ -25,10 +39,10 @@ let settingsInstance = null;
25
39
  * @throws Error if settings are already initialized
26
40
  */
27
41
  export function initializeSettings(settings) {
28
- if (settingsInstance !== null) {
42
+ if (readSettings() !== null) {
29
43
  throw new Error('Settings already initialized. Cannot re-initialize.');
30
44
  }
31
- settingsInstance = settings;
45
+ writeSettings(settings);
32
46
  }
33
47
  /**
34
48
  * Get the current application settings.
@@ -43,10 +57,11 @@ export function initializeSettings(settings) {
43
57
  * ```
44
58
  */
45
59
  export function getSettings() {
46
- if (settingsInstance === null) {
60
+ const instance = readSettings();
61
+ if (instance === null) {
47
62
  throw new Error('Settings not initialized. Call initializeSettings() during CLI bootstrap.');
48
63
  }
49
- return settingsInstance;
64
+ return instance;
50
65
  }
51
66
  /**
52
67
  * Check if settings have been initialized.
@@ -54,7 +69,7 @@ export function getSettings() {
54
69
  * @returns True if settings are initialized, false otherwise
55
70
  */
56
71
  export function hasSettings() {
57
- return settingsInstance !== null;
72
+ return readSettings() !== null;
58
73
  }
59
74
  /**
60
75
  * Reset settings instance (for testing purposes only).
@@ -63,5 +78,5 @@ export function hasSettings() {
63
78
  * @internal
64
79
  */
65
80
  export function resetSettings() {
66
- settingsInstance = null;
81
+ writeSettings(null);
67
82
  }
@@ -1 +1 @@
1
- {"version":3,"file":"show.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/agent/show.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAyBpC,wBAAgB,iBAAiB,IAAI,OAAO,CAoG3C"}
1
+ {"version":3,"file":"show.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/agent/show.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiBpC,wBAAgB,iBAAiB,IAAI,OAAO,CAoG3C"}
@@ -8,17 +8,10 @@
8
8
  * shep agent show <id>
9
9
  */
10
10
  import { Command } from 'commander';
11
- import { createHash } from 'node:crypto';
12
- import { join } from 'node:path';
13
11
  import { colors, messages, symbols, renderDetailView } from '../../ui/index.js';
14
12
  import { resolveAgentRun } from './resolve-run.js';
15
13
  import { container } from '../../../../../packages/core/src/infrastructure/di/container.js';
16
- import { SHEP_HOME_DIR } from '../../../../../packages/core/src/infrastructure/services/filesystem/shep-directory.service.js';
17
- function computeWorktreePath(repoPath, branch) {
18
- const repoHash = createHash('sha256').update(repoPath).digest('hex').slice(0, 16);
19
- const slug = branch.replace(/\//g, '-');
20
- return join(SHEP_HOME_DIR, 'repos', repoHash, 'wt', slug);
21
- }
14
+ import { computeWorktreePath } from '../../../../../packages/core/src/infrastructure/services/ide-launchers/compute-worktree-path.js';
22
15
  function isProcessAlive(pid) {
23
16
  try {
24
17
  process.kill(pid, 0);
@@ -1 +1 @@
1
- {"version":3,"file":"show.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/feat/show.command.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuEpC,wBAAgB,iBAAiB,IAAI,OAAO,CA4I3C"}
1
+ {"version":3,"file":"show.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/feat/show.command.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+DpC,wBAAgB,iBAAiB,IAAI,OAAO,CA4I3C"}
@@ -7,17 +7,10 @@
7
7
  * Usage: shep feat show <id>
8
8
  */
9
9
  import { Command } from 'commander';
10
- import { createHash } from 'node:crypto';
11
- import { join } from 'node:path';
12
10
  import { container } from '../../../../../packages/core/src/infrastructure/di/container.js';
13
11
  import { ShowFeatureUseCase } from '../../../../../packages/core/src/application/use-cases/features/show-feature.use-case.js';
14
12
  import { colors, symbols, messages, renderDetailView } from '../../ui/index.js';
15
- import { SHEP_HOME_DIR } from '../../../../../packages/core/src/infrastructure/services/filesystem/shep-directory.service.js';
16
- function computeWorktreePath(repoPath, branch) {
17
- const repoHash = createHash('sha256').update(repoPath).digest('hex').slice(0, 16);
18
- const slug = branch.replace(/\//g, '-');
19
- return join(SHEP_HOME_DIR, 'repos', repoHash, 'wt', slug);
20
- }
13
+ import { computeWorktreePath } from '../../../../../packages/core/src/infrastructure/services/ide-launchers/compute-worktree-path.js';
21
14
  /** Map graph node names to human-readable phase labels (active). */
22
15
  const NODE_TO_PHASE = {
23
16
  analyze: 'Analyzing',
@@ -1 +1 @@
1
- {"version":3,"file":"ide-open.command.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/cli/commands/ide-open.command.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+CpC,wBAAgB,oBAAoB,IAAI,OAAO,CAwC9C"}
1
+ {"version":3,"file":"ide-open.command.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/cli/commands/ide-open.command.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsCpC,wBAAgB,oBAAoB,IAAI,OAAO,CAkC9C"}
@@ -6,20 +6,12 @@
6
6
  * Usage: shep ide <feat-id> [--vscode|--cursor|--windsurf|--zed|--antigravity]
7
7
  */
8
8
  import { Command } from 'commander';
9
- import { createHash } from 'node:crypto';
10
- import { join } from 'node:path';
11
9
  import { EditorType } from '../../../../packages/core/src/domain/generated/output.js';
12
10
  import { container } from '../../../../packages/core/src/infrastructure/di/container.js';
13
11
  import { ShowFeatureUseCase } from '../../../../packages/core/src/application/use-cases/features/show-feature.use-case.js';
14
12
  import { getSettings } from '../../../../packages/core/src/infrastructure/services/settings.service.js';
15
- import { createLauncherRegistry } from '../../../../packages/core/src/infrastructure/services/ide-launchers/ide-launcher.registry.js';
16
- import { SHEP_HOME_DIR } from '../../../../packages/core/src/infrastructure/services/filesystem/shep-directory.service.js';
13
+ import { launchIde } from '../../../../packages/core/src/infrastructure/services/ide-launchers/launch-ide.js';
17
14
  import { messages } from '../ui/index.js';
18
- function computeWorktreePath(repoPath, branch) {
19
- const repoHash = createHash('sha256').update(repoPath).digest('hex').slice(0, 16);
20
- const slug = branch.replace(/\//g, '-');
21
- return join(SHEP_HOME_DIR, 'repos', repoHash, 'wt', slug);
22
- }
23
15
  /** IDE flag names mapped to their EditorType values. */
24
16
  const IDE_FLAG_MAP = {
25
17
  vscode: EditorType.VsCode,
@@ -51,22 +43,20 @@ export function createIdeOpenCommand() {
51
43
  .option('--antigravity', 'Open in Antigravity')
52
44
  .action(async (featId, options) => {
53
45
  try {
54
- // Resolve the editor
55
46
  const editorId = resolveEditorId(options);
56
- const registry = createLauncherRegistry();
57
- const launcher = registry.get(editorId);
58
- if (!launcher) {
59
- messages.error(`Unknown editor "${editorId}". Supported: ${[...registry.keys()].join(', ')}`, new Error(`Unknown editor: ${editorId}`));
47
+ const useCase = container.resolve(ShowFeatureUseCase);
48
+ const feature = await useCase.execute(featId);
49
+ const result = await launchIde({
50
+ editorId,
51
+ repositoryPath: feature.repositoryPath,
52
+ branch: feature.branch,
53
+ });
54
+ if (!result.ok) {
55
+ messages.error(result.message, new Error(result.message));
60
56
  process.exitCode = 1;
61
57
  return;
62
58
  }
63
- // Resolve the feature
64
- const useCase = container.resolve(ShowFeatureUseCase);
65
- const feature = await useCase.execute(featId);
66
- // Compute worktree path and launch
67
- const worktreePath = computeWorktreePath(feature.repositoryPath, feature.branch);
68
- await launcher.launch(worktreePath);
69
- messages.success(`Opened ${launcher.name} at ${worktreePath}`);
59
+ messages.success(`Opened ${result.editorName} at ${result.worktreePath}`);
70
60
  }
71
61
  catch (error) {
72
62
  const err = error instanceof Error ? error : new Error(String(error));
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ import { NextResponse } from 'next/server';
2
+ export declare function POST(request: Request): Promise<NextResponse<{
3
+ error: string;
4
+ }> | NextResponse<{
5
+ success: boolean;
6
+ editor: string;
7
+ path: string;
8
+ }>>;
9
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../../src/presentation/web/app/api/ide/open/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK3C,wBAAsB,IAAI,CAAC,OAAO,EAAE,OAAO;;;;;;IA8B1C"}
@@ -0,0 +1,29 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getSettings } from '../../../../../../../packages/core/src/infrastructure/services/settings.service.js';
3
+ import { launchIde } from '../../../../../../../packages/core/src/infrastructure/services/ide-launchers/launch-ide.js';
4
+ import { validateToolbarInput } from '../../validate-toolbar-input.js';
5
+ export async function POST(request) {
6
+ const body = await request.json();
7
+ const validation = validateToolbarInput(body);
8
+ if ('error' in validation) {
9
+ return NextResponse.json({ error: validation.error }, { status: validation.status });
10
+ }
11
+ const { repositoryPath, branch } = validation;
12
+ const settings = getSettings();
13
+ const editor = settings.environment.defaultEditor;
14
+ const result = await launchIde({
15
+ editorId: editor,
16
+ repositoryPath,
17
+ branch,
18
+ checkAvailability: true,
19
+ });
20
+ if (!result.ok) {
21
+ const status = result.code === 'launch_failed' ? 500 : 404;
22
+ return NextResponse.json({ error: result.message }, { status });
23
+ }
24
+ return NextResponse.json({
25
+ success: true,
26
+ editor: result.editorName,
27
+ path: result.worktreePath,
28
+ });
29
+ }
@@ -0,0 +1,9 @@
1
+ import { NextResponse } from 'next/server';
2
+ export declare function POST(request: Request): Promise<NextResponse<{
3
+ error: string;
4
+ }> | NextResponse<{
5
+ success: boolean;
6
+ path: string;
7
+ shell: string;
8
+ }>>;
9
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../../src/presentation/web/app/api/shell/open/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAO3C,wBAAsB,IAAI,CAAC,OAAO,EAAE,OAAO;;;;;;IAsD1C"}
@@ -0,0 +1,51 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { existsSync } from 'node:fs';
3
+ import { spawn } from 'node:child_process';
4
+ import { getSettings } from '../../../../../../../packages/core/src/infrastructure/services/settings.service.js';
5
+ import { computeWorktreePath } from '../../../../../../../packages/core/src/infrastructure/services/ide-launchers/compute-worktree-path.js';
6
+ import { validateToolbarInput } from '../../validate-toolbar-input.js';
7
+ export async function POST(request) {
8
+ const body = await request.json();
9
+ const validation = validateToolbarInput(body);
10
+ if ('error' in validation) {
11
+ return NextResponse.json({ error: validation.error }, { status: validation.status });
12
+ }
13
+ const { repositoryPath, branch } = validation;
14
+ try {
15
+ const settings = getSettings();
16
+ const shell = settings.environment.shellPreference;
17
+ const worktreePath = computeWorktreePath(repositoryPath, branch);
18
+ if (!existsSync(worktreePath)) {
19
+ return NextResponse.json({ error: `Worktree path does not exist: ${worktreePath}` }, { status: 404 });
20
+ }
21
+ const platform = process.platform;
22
+ if (platform === 'darwin') {
23
+ const child = spawn('open', ['-a', 'Terminal', worktreePath], {
24
+ detached: true,
25
+ stdio: 'ignore',
26
+ });
27
+ child.unref();
28
+ }
29
+ else if (platform === 'linux') {
30
+ const child = spawn('x-terminal-emulator', [`--working-directory=${worktreePath}`], {
31
+ detached: true,
32
+ stdio: 'ignore',
33
+ });
34
+ child.unref();
35
+ }
36
+ else {
37
+ return NextResponse.json({
38
+ error: `Unsupported platform: ${platform}. Shell launch is supported on macOS and Linux only.`,
39
+ }, { status: 501 });
40
+ }
41
+ return NextResponse.json({
42
+ success: true,
43
+ path: worktreePath,
44
+ shell,
45
+ });
46
+ }
47
+ catch (error) {
48
+ const message = error instanceof Error ? error.message : 'Failed to open shell';
49
+ return NextResponse.json({ error: message }, { status: 500 });
50
+ }
51
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Shared input validation for feature toolbar API routes.
3
+ *
4
+ * Validates that repositoryPath is a non-empty absolute path and branch is a
5
+ * non-empty string without path traversal sequences or null bytes.
6
+ */
7
+ interface ValidInput {
8
+ repositoryPath: string;
9
+ branch: string;
10
+ }
11
+ interface ValidationError {
12
+ error: string;
13
+ status: number;
14
+ }
15
+ export declare function validateToolbarInput(body: Record<string, unknown>): ValidInput | ValidationError;
16
+ export {};
17
+ //# sourceMappingURL=validate-toolbar-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-toolbar-input.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/api/validate-toolbar-input.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,UAAU,UAAU;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AACD,UAAU,eAAe;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,UAAU,GAAG,eAAe,CAwBhG"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Shared input validation for feature toolbar API routes.
3
+ *
4
+ * Validates that repositoryPath is a non-empty absolute path and branch is a
5
+ * non-empty string without path traversal sequences or null bytes.
6
+ */
7
+ export function validateToolbarInput(body) {
8
+ const { repositoryPath, branch } = body;
9
+ if (typeof repositoryPath !== 'string' || repositoryPath.length === 0) {
10
+ return { error: 'repositoryPath is required and must be a non-empty string', status: 400 };
11
+ }
12
+ if (!repositoryPath.startsWith('/')) {
13
+ return { error: 'repositoryPath must be an absolute path (starting with /)', status: 400 };
14
+ }
15
+ if (typeof branch !== 'string' || branch.length === 0) {
16
+ return { error: 'branch is required and must be a non-empty string', status: 400 };
17
+ }
18
+ if (branch.includes('..')) {
19
+ return { error: 'branch must not contain path traversal sequences (..)', status: 400 };
20
+ }
21
+ if (branch.includes('\0')) {
22
+ return { error: 'branch must not contain null bytes', status: 400 };
23
+ }
24
+ return { repositoryPath, branch };
25
+ }
@@ -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,qDAoFrC"}
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"}
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { ControlCenter } from '../components/features/control-center/index.js';
3
3
  import { getAgentRun, getFeatures } from '../../../../packages/core/src/infrastructure/di/use-cases-bridge.js';
4
- import { deriveState } from './derive-state.js';
4
+ import { deriveNodeState, deriveProgress, } from '../components/common/feature-node/derive-feature-state.js';
5
5
  import { layoutWithDagre } from '../lib/layout-with-dagre.js';
6
6
  /** Force request-time rendering so the globalThis DI bridge is available. */
7
7
  export const dynamic = 'force-dynamic';
@@ -26,12 +26,7 @@ export default async function HomePage() {
26
26
  const features = await getFeatures();
27
27
  const featuresWithRuns = await Promise.all(features.map(async (feature) => {
28
28
  const run = feature.agentRunId ? await getAgentRun(feature.agentRunId) : null;
29
- return {
30
- feature,
31
- agentStatus: run?.status,
32
- agentError: run?.error,
33
- agentResult: run?.result,
34
- };
29
+ return { feature, run };
35
30
  }));
36
31
  // Group features by repository path
37
32
  const featuresByRepo = {};
@@ -53,9 +48,9 @@ export default async function HomePage() {
53
48
  position: { x: 0, y: 0 },
54
49
  data: { name: repoName },
55
50
  });
56
- repoFeatures.forEach(({ feature, agentStatus, agentError, agentResult }) => {
57
- const agentNode = agentResult?.startsWith('node:') ? agentResult.slice(5) : undefined;
58
- const lifecycle = agentStatus === 'completed'
51
+ repoFeatures.forEach(({ feature, run }) => {
52
+ const agentNode = run?.result?.startsWith('node:') ? run.result.slice(5) : undefined;
53
+ const lifecycle = run?.status === 'completed'
59
54
  ? 'maintain'
60
55
  : ((agentNode ? nodeToLifecyclePhase[agentNode] : undefined) ??
61
56
  lifecycleMap[feature.lifecycle] ??
@@ -65,8 +60,12 @@ export default async function HomePage() {
65
60
  description: feature.description ?? feature.slug,
66
61
  featureId: feature.id,
67
62
  lifecycle,
68
- ...deriveState(lifecycle, agentStatus),
69
- ...(agentError && { errorMessage: agentError }),
63
+ repositoryPath: feature.repositoryPath,
64
+ branch: feature.branch,
65
+ state: deriveNodeState(feature, run),
66
+ progress: deriveProgress(feature),
67
+ ...(run?.agentType && { agentType: run.agentType }),
68
+ ...(run?.error && { errorMessage: run.error }),
70
69
  };
71
70
  const featureNodeId = `feat-${feature.id}`;
72
71
  nodes.push({
@@ -1 +1 @@
1
- {"version":3,"file":"feature-drawer.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer/feature-drawer.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAExE,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,eAAe,GAAG,IAAI,CAAC;IACrC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE,kBAAkB,2CAmE1E"}
1
+ {"version":3,"file":"feature-drawer.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer/feature-drawer.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAGxE,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,eAAe,GAAG,IAAI,CAAC;IACrC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE,kBAAkB,2CA2E1E"}