@shepai/cli 1.108.1 → 1.109.0-pr333.db60eb7

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 (194) hide show
  1. package/apis/json-schema/NotificationEventConfig.yaml +5 -0
  2. package/apis/json-schema/NotificationEventType.yaml +1 -0
  3. package/dist/packages/core/src/application/use-cases/upgrade/upgrade-cli.use-case.d.ts +9 -1
  4. package/dist/packages/core/src/application/use-cases/upgrade/upgrade-cli.use-case.d.ts.map +1 -1
  5. package/dist/packages/core/src/application/use-cases/upgrade/upgrade-cli.use-case.js +31 -2
  6. package/dist/packages/core/src/domain/factories/settings-defaults.factory.d.ts.map +1 -1
  7. package/dist/packages/core/src/domain/factories/settings-defaults.factory.js +1 -0
  8. package/dist/packages/core/src/domain/generated/output.d.ts +6 -1
  9. package/dist/packages/core/src/domain/generated/output.d.ts.map +1 -1
  10. package/dist/packages/core/src/domain/generated/output.js +1 -0
  11. package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/settings.mapper.d.ts +1 -0
  12. package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/settings.mapper.d.ts.map +1 -1
  13. package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/settings.mapper.js +2 -0
  14. package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations.d.ts.map +1 -1
  15. package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations.js +28 -0
  16. package/dist/packages/core/src/infrastructure/repositories/sqlite-settings.repository.d.ts.map +1 -1
  17. package/dist/packages/core/src/infrastructure/repositories/sqlite-settings.repository.js +12 -0
  18. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/merge.node.d.ts.map +1 -1
  19. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/merge.node.js +14 -0
  20. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.d.ts +2 -0
  21. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.d.ts.map +1 -1
  22. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.js +35 -1
  23. package/dist/packages/core/src/infrastructure/services/notifications/notification.service.d.ts.map +1 -1
  24. package/dist/packages/core/src/infrastructure/services/notifications/notification.service.js +1 -0
  25. package/dist/packages/core/src/infrastructure/services/pr-sync/pr-sync-watcher.service.d.ts +13 -2
  26. package/dist/packages/core/src/infrastructure/services/pr-sync/pr-sync-watcher.service.d.ts.map +1 -1
  27. package/dist/packages/core/src/infrastructure/services/pr-sync/pr-sync-watcher.service.js +80 -3
  28. package/dist/src/presentation/cli/commands/ui.command.d.ts.map +1 -1
  29. package/dist/src/presentation/cli/commands/ui.command.js +3 -1
  30. package/dist/src/presentation/web/app/actions/get-feature-drawer-data.d.ts +3 -0
  31. package/dist/src/presentation/web/app/actions/get-feature-drawer-data.d.ts.map +1 -1
  32. package/dist/src/presentation/web/app/actions/get-feature-drawer-data.js +7 -32
  33. package/dist/src/presentation/web/app/api/agent-events/route.d.ts.map +1 -1
  34. package/dist/src/presentation/web/app/api/agent-events/route.js +20 -1
  35. package/dist/src/presentation/web/app/api/cli-upgrade/route.d.ts.map +1 -1
  36. package/dist/src/presentation/web/app/api/cli-upgrade/route.js +14 -1
  37. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts.map +1 -1
  38. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.js +2 -0
  39. package/dist/src/presentation/web/components/common/version-badge/version-badge.d.ts.map +1 -1
  40. package/dist/src/presentation/web/components/common/version-badge/version-badge.js +6 -1
  41. package/dist/src/presentation/web/components/common/version-badge/version-badge.stories.d.ts.map +1 -1
  42. package/dist/src/presentation/web/components/common/version-badge/version-badge.stories.js +7 -0
  43. package/dist/src/presentation/web/components/features/settings/notification-settings-section.d.ts.map +1 -1
  44. package/dist/src/presentation/web/components/features/settings/notification-settings-section.js +1 -0
  45. package/dist/src/presentation/web/components/features/settings/notification-settings-section.stories.d.ts.map +1 -1
  46. package/dist/src/presentation/web/components/features/settings/notification-settings-section.stories.js +2 -0
  47. package/dist/src/presentation/web/dev-server.js +3 -1
  48. package/dist/src/presentation/web/hooks/use-cli-upgrade.d.ts +1 -1
  49. package/dist/src/presentation/web/hooks/use-cli-upgrade.d.ts.map +1 -1
  50. package/dist/src/presentation/web/hooks/use-cli-upgrade.js +40 -1
  51. package/dist/src/presentation/web/hooks/use-notifications.d.ts.map +1 -1
  52. package/dist/src/presentation/web/hooks/use-notifications.js +7 -3
  53. package/dist/tsconfig.build.tsbuildinfo +1 -1
  54. package/package.json +1 -1
  55. package/web/.next/BUILD_ID +1 -1
  56. package/web/.next/build-manifest.json +2 -2
  57. package/web/.next/fallback-build-manifest.json +2 -2
  58. package/web/.next/prerender-manifest.json +3 -3
  59. package/web/.next/required-server-files.js +3 -3
  60. package/web/.next/required-server-files.json +3 -3
  61. package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +20 -20
  62. package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  63. package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  64. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page/server-reference-manifest.json +26 -26
  65. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page.js.nft.json +1 -1
  66. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
  67. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +26 -26
  68. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  69. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  70. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +18 -18
  71. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  72. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  73. package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +20 -20
  74. package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  75. package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  76. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page/server-reference-manifest.json +26 -26
  77. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page.js.nft.json +1 -1
  78. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
  79. package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +26 -26
  80. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  81. package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  82. package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +18 -18
  83. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  84. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  85. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +18 -18
  86. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  87. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  88. package/web/.next/server/app/_global-error.html +2 -2
  89. package/web/.next/server/app/_global-error.rsc +1 -1
  90. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  91. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  92. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  93. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  94. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  95. package/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  96. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  97. package/web/.next/server/app/settings/page/server-reference-manifest.json +5 -5
  98. package/web/.next/server/app/settings/page.js.nft.json +1 -1
  99. package/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  100. package/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  101. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  102. package/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  103. package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  104. package/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  105. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  106. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_370c43b1.js.map +1 -1
  107. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +2 -2
  108. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js.map +1 -1
  109. package/web/.next/server/chunks/[root-of-the-server]__7d5492a0._.js +1 -1
  110. package/web/.next/server/chunks/[root-of-the-server]__7d5492a0._.js.map +1 -1
  111. package/web/.next/server/chunks/[root-of-the-server]__a402b567._.js +1 -1
  112. package/web/.next/server/chunks/[root-of-the-server]__cd67a84c._.js +1 -1
  113. package/web/.next/server/chunks/[root-of-the-server]__cd67a84c._.js.map +1 -1
  114. package/web/.next/server/chunks/[root-of-the-server]__d2c18946._.js +7 -4
  115. package/web/.next/server/chunks/[root-of-the-server]__d2c18946._.js.map +1 -1
  116. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
  117. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
  118. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_a1104008.js +1 -1
  119. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_a1104008.js.map +1 -1
  120. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_92642200.js +1 -1
  121. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_92642200.js.map +1 -1
  122. package/web/.next/server/chunks/ssr/[root-of-the-server]__039967e3._.js +1 -1
  123. package/web/.next/server/chunks/ssr/[root-of-the-server]__039967e3._.js.map +1 -1
  124. package/web/.next/server/chunks/ssr/[root-of-the-server]__07a39ff2._.js +1 -1
  125. package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js +1 -1
  126. package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js.map +1 -1
  127. package/web/.next/server/chunks/ssr/[root-of-the-server]__24dd0927._.js +2 -2
  128. package/web/.next/server/chunks/ssr/[root-of-the-server]__2773312c._.js +1 -1
  129. package/web/.next/server/chunks/ssr/[root-of-the-server]__2773312c._.js.map +1 -1
  130. package/web/.next/server/chunks/ssr/[root-of-the-server]__5d87af2e._.js +1 -1
  131. package/web/.next/server/chunks/ssr/[root-of-the-server]__845a9d2a._.js +1 -1
  132. package/web/.next/server/chunks/ssr/[root-of-the-server]__845a9d2a._.js.map +1 -1
  133. package/web/.next/server/chunks/ssr/[root-of-the-server]__89e8a5cc._.js +2 -2
  134. package/web/.next/server/chunks/ssr/[root-of-the-server]__89e8a5cc._.js.map +1 -1
  135. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +2 -2
  136. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
  137. package/web/.next/server/chunks/ssr/[root-of-the-server]__cbd8062c._.js +1 -1
  138. package/web/.next/server/chunks/ssr/[root-of-the-server]__cbd8062c._.js.map +1 -1
  139. package/web/.next/server/chunks/ssr/[root-of-the-server]__e70c5627._.js +1 -1
  140. package/web/.next/server/chunks/ssr/[root-of-the-server]__e70c5627._.js.map +1 -1
  141. package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js +1 -1
  142. package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js.map +1 -1
  143. package/web/.next/server/chunks/ssr/{_62046d95._.js → _0624ebcd._.js} +2 -2
  144. package/web/.next/server/chunks/ssr/{_62046d95._.js.map → _0624ebcd._.js.map} +1 -1
  145. package/web/.next/server/chunks/ssr/{_c70d9b33._.js → _0ed59e22._.js} +2 -2
  146. package/web/.next/server/chunks/ssr/{_c70d9b33._.js.map → _0ed59e22._.js.map} +1 -1
  147. package/web/.next/server/chunks/ssr/_135edb9c._.js +1 -1
  148. package/web/.next/server/chunks/ssr/_135edb9c._.js.map +1 -1
  149. package/web/.next/server/chunks/ssr/{_d061afbe._.js → _1b5acc7e._.js} +2 -2
  150. package/web/.next/server/chunks/ssr/{_d061afbe._.js.map → _1b5acc7e._.js.map} +1 -1
  151. package/web/.next/server/chunks/ssr/_1bb131c4._.js +1 -1
  152. package/web/.next/server/chunks/ssr/_1bb131c4._.js.map +1 -1
  153. package/web/.next/server/chunks/ssr/_250a63ae._.js.map +1 -1
  154. package/web/.next/server/chunks/ssr/_8dacd1e7._.js +1 -1
  155. package/web/.next/server/chunks/ssr/_8dacd1e7._.js.map +1 -1
  156. package/web/.next/server/chunks/ssr/_cc77a143._.js +1 -1
  157. package/web/.next/server/chunks/ssr/_cc77a143._.js.map +1 -1
  158. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js +1 -1
  159. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js.map +1 -1
  160. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_888633e1.js +1 -1
  161. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_888633e1.js.map +1 -1
  162. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js +1 -1
  163. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js.map +1 -1
  164. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js +1 -1
  165. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js.map +1 -1
  166. package/web/.next/server/chunks/ssr/src_presentation_web_ce7cbc3c._.js +2 -2
  167. package/web/.next/server/chunks/ssr/src_presentation_web_ce7cbc3c._.js.map +1 -1
  168. package/web/.next/server/chunks/ssr/src_presentation_web_components_bf50c527._.js +1 -1
  169. package/web/.next/server/chunks/ssr/src_presentation_web_components_de814675._.js +1 -1
  170. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  171. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
  172. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  173. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
  174. package/web/.next/server/chunks/ssr/src_presentation_web_e2604413._.js +2 -2
  175. package/web/.next/server/chunks/ssr/src_presentation_web_e2604413._.js.map +1 -1
  176. package/web/.next/server/pages/500.html +2 -2
  177. package/web/.next/server/server-reference-manifest.js +1 -1
  178. package/web/.next/server/server-reference-manifest.json +31 -31
  179. package/web/.next/static/chunks/{2cbea56328a51d40.js → 3590127e07396566.js} +1 -1
  180. package/web/.next/static/chunks/{4903e892cb40b58f.js → 37e937603c1b585a.js} +1 -1
  181. package/web/.next/static/chunks/5543614d36607aef.js +1 -0
  182. package/web/.next/static/chunks/{fe25ae01bc30db68.js → 7f06b766fcb6d8b1.js} +1 -1
  183. package/web/.next/static/chunks/{75ee6e58a48c650a.js → 860393fe87514d4e.js} +1 -1
  184. package/web/.next/static/chunks/{6de088efae33ebe0.js → a7614b68a2b9df0d.js} +1 -1
  185. package/web/.next/static/chunks/{93dfac33de99e86d.js → b8469d4a1c789862.js} +1 -1
  186. package/web/.next/static/chunks/{1b75e16363eda185.js → da9dc6b3e3f22c81.js} +2 -2
  187. package/web/.next/static/chunks/{f9a371d3a0d6cd37.js → eb9caf4f6ad20ced.js} +1 -1
  188. package/web/.next/static/chunks/{429244a234d321a0.js → f294966570d82c47.js} +1 -1
  189. package/web/.next/static/chunks/{0e0f97b1c5896c76.js → f84a3d8a01fd0998.js} +2 -2
  190. package/web/.next/static/chunks/{0102ce60a2a42fab.js → fcde0cbb2976b933.js} +1 -1
  191. package/web/.next/static/chunks/3bb35e0689e8b6b7.js +0 -1
  192. /package/web/.next/static/{IF-6eFBS3sFqEI7cO3O8x → MTL4o8t-B0wwLjYHqHZTw}/_buildManifest.js +0 -0
  193. /package/web/.next/static/{IF-6eFBS3sFqEI7cO3O8x → MTL4o8t-B0wwLjYHqHZTw}/_clientMiddlewareManifest.json +0 -0
  194. /package/web/.next/static/{IF-6eFBS3sFqEI7cO3O8x → MTL4o8t-B0wwLjYHqHZTw}/_ssgManifest.js +0 -0
@@ -11,6 +11,8 @@
11
11
  */
12
12
  import { SdlcLifecycle, PrStatus, CiStatus, AgentRunStatus, NotificationEventType, NotificationSeverity, } from '../../../domain/generated/output.js';
13
13
  const DEFAULT_POLL_INTERVAL_MS = 30_000;
14
+ const LOCK_TTL_MS = 60_000;
15
+ const RATE_LIMIT_BACKOFF_MS = 5 * 60_000;
14
16
  const TAG = '[PrSyncWatcher]';
15
17
  const CI_STATUS_MAP = {
16
18
  success: CiStatus.Success,
@@ -25,13 +27,19 @@ export class PrSyncWatcherService {
25
27
  pollIntervalMs;
26
28
  trackedFeatures = new Map();
27
29
  skippedRepos = new Set();
30
+ rateLimitedUntil = new Map();
31
+ db;
32
+ processId;
28
33
  intervalId = null;
29
- constructor(featureRepo, agentRunRepo, gitPrService, notificationService, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS) {
34
+ pollCycle = 0;
35
+ constructor(featureRepo, agentRunRepo, gitPrService, notificationService, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS, db = null) {
30
36
  this.featureRepo = featureRepo;
31
37
  this.agentRunRepo = agentRunRepo;
32
38
  this.gitPrService = gitPrService;
33
39
  this.notificationService = notificationService;
34
40
  this.pollIntervalMs = pollIntervalMs;
41
+ this.db = db;
42
+ this.processId = `${process.pid}-${Date.now()}`;
35
43
  }
36
44
  isRunning() {
37
45
  return this.intervalId !== null;
@@ -55,8 +63,40 @@ export class PrSyncWatcherService {
55
63
  console.log(`${TAG} Stopped`);
56
64
  }
57
65
  }
66
+ /** Attempt to acquire the cross-process poll lock. Returns true if acquired. */
67
+ tryAcquireLock() {
68
+ if (!this.db)
69
+ return true; // no DB → single-process mode, always proceed
70
+ const now = Date.now();
71
+ const expiresAt = now + LOCK_TTL_MS;
72
+ const stmt = this.db.prepare(`
73
+ INSERT OR REPLACE INTO pr_sync_lock (id, locked_by, locked_at, expires_at)
74
+ SELECT 1, ?, ?, ?
75
+ WHERE NOT EXISTS (
76
+ SELECT 1 FROM pr_sync_lock WHERE id = 1 AND locked_by != ? AND expires_at > ?
77
+ )
78
+ `);
79
+ const result = stmt.run(this.processId, now, expiresAt, this.processId, now);
80
+ return result.changes > 0;
81
+ }
82
+ /** Check if a feature should be polled this cycle based on its unchanged cycles count. */
83
+ shouldPollFeature(featureId) {
84
+ const tracked = this.trackedFeatures.get(featureId);
85
+ if (!tracked)
86
+ return true; // new feature, always poll
87
+ const uc = tracked.unchangedCycles;
88
+ if (uc <= 2)
89
+ return true; // poll every cycle
90
+ if (uc <= 5)
91
+ return this.pollCycle % 2 === 0; // poll every 2nd cycle
92
+ return this.pollCycle % 4 === 0; // poll every 4th cycle
93
+ }
58
94
  async poll() {
59
95
  try {
96
+ if (!this.tryAcquireLock()) {
97
+ return; // another process holds the lock — skip this cycle
98
+ }
99
+ this.pollCycle++;
60
100
  const allFeatures = await this.featureRepo.list({ lifecycle: SdlcLifecycle.Review });
61
101
  // Include features with a valid repositoryPath (with or without PR data)
62
102
  const features = allFeatures.filter((f) => f.repositoryPath);
@@ -89,9 +129,32 @@ export class PrSyncWatcherService {
89
129
  console.warn(`${TAG} Poll failed: ${msg}`);
90
130
  }
91
131
  }
132
+ isRateLimited(repoPath) {
133
+ const until = this.rateLimitedUntil.get(repoPath);
134
+ if (until === undefined)
135
+ return false;
136
+ if (Date.now() >= until) {
137
+ this.rateLimitedUntil.delete(repoPath);
138
+ // eslint-disable-next-line no-console
139
+ console.log(`${TAG} Rate limit backoff expired for ${repoPath}`);
140
+ return false;
141
+ }
142
+ return true;
143
+ }
144
+ handleRateLimitError(repoPath, error) {
145
+ const msg = error.message;
146
+ if (msg.includes('API rate limit exceeded') || msg.includes('rate limit')) {
147
+ const backoffUntil = Date.now() + RATE_LIMIT_BACKOFF_MS;
148
+ this.rateLimitedUntil.set(repoPath, backoffUntil);
149
+ // eslint-disable-next-line no-console
150
+ console.warn(`${TAG} Rate limited for ${repoPath}, backing off until ${new Date(backoffUntil).toISOString()}`);
151
+ }
152
+ }
92
153
  async processRepository(repoPath, features) {
93
154
  if (this.skippedRepos.has(repoPath))
94
155
  return;
156
+ if (this.isRateLimited(repoPath))
157
+ return;
95
158
  // Skip repos without a git remote — gh pr list will always fail
96
159
  try {
97
160
  if (!(await this.gitPrService.hasRemote(repoPath))) {
@@ -110,6 +173,8 @@ export class PrSyncWatcherService {
110
173
  }
111
174
  catch (error) {
112
175
  const msg = error instanceof Error ? error.message : String(error);
176
+ if (error instanceof Error)
177
+ this.handleRateLimitError(repoPath, error);
113
178
  // eslint-disable-next-line no-console
114
179
  console.warn(`${TAG} listPrStatuses failed for ${repoPath}: ${msg}`);
115
180
  return;
@@ -153,10 +218,15 @@ export class PrSyncWatcherService {
153
218
  ciStatus: undefined,
154
219
  mergeable: undefined,
155
220
  featureName: feature.name,
221
+ unchangedCycles: 0,
156
222
  });
157
223
  await this.featureRepo.update(feature);
158
224
  return;
159
225
  }
226
+ // Check exponential backoff — skip features that haven't changed recently
227
+ if (!this.shouldPollFeature(feature.id)) {
228
+ return;
229
+ }
160
230
  const pr = feature.pr;
161
231
  const prStatusInfo = statusByNumber.get(pr.number);
162
232
  let needsUpdate = false;
@@ -169,6 +239,7 @@ export class PrSyncWatcherService {
169
239
  ciStatus: pr.ciStatus,
170
240
  mergeable: pr.mergeable,
171
241
  featureName: feature.name,
242
+ unchangedCycles: 0,
172
243
  });
173
244
  }
174
245
  const tracked = this.trackedFeatures.get(feature.id);
@@ -223,12 +294,18 @@ export class PrSyncWatcherService {
223
294
  }
224
295
  catch (error) {
225
296
  const msg = error instanceof Error ? error.message : String(error);
297
+ if (error instanceof Error)
298
+ this.handleRateLimitError(feature.repositoryPath, error);
226
299
  // eslint-disable-next-line no-console
227
300
  console.warn(`${TAG} getCiStatus failed for "${feature.name}": ${msg}`);
228
301
  }
229
302
  if (needsUpdate) {
303
+ tracked.unchangedCycles = 0;
230
304
  await this.featureRepo.update(feature);
231
305
  }
306
+ else {
307
+ tracked.unchangedCycles++;
308
+ }
232
309
  }
233
310
  /** Mark associated agent run as completed so the UI reflects "done" state. */
234
311
  async completeAgentRun(feature) {
@@ -264,11 +341,11 @@ let watcherInstance = null;
264
341
  *
265
342
  * @throws Error if the watcher is already initialized
266
343
  */
267
- export function initializePrSyncWatcher(featureRepo, agentRunRepo, gitPrService, notificationService, pollIntervalMs) {
344
+ export function initializePrSyncWatcher(featureRepo, agentRunRepo, gitPrService, notificationService, pollIntervalMs, db) {
268
345
  if (watcherInstance !== null) {
269
346
  throw new Error('PR sync watcher already initialized. Cannot re-initialize.');
270
347
  }
271
- watcherInstance = new PrSyncWatcherService(featureRepo, agentRunRepo, gitPrService, notificationService, pollIntervalMs);
348
+ watcherInstance = new PrSyncWatcherService(featureRepo, agentRunRepo, gitPrService, notificationService, pollIntervalMs, db ?? null);
272
349
  }
273
350
  /**
274
351
  * Get the PR sync watcher singleton.
@@ -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;AA+B1D;;GAEG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAkFzC"}
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;AAgC1D;;GAEG;AACH,wBAAgB,eAAe,IAAI,OAAO,CA0FzC"}
@@ -21,6 +21,7 @@ import { setVersionEnvVars } from '../../../../packages/core/src/infrastructure/
21
21
  import { resolveWebDir } from '../../../../packages/core/src/infrastructure/services/web-server.service.js';
22
22
  import { initializeNotificationWatcher, getNotificationWatcher, } from '../../../../packages/core/src/infrastructure/services/notifications/notification-watcher.service.js';
23
23
  import { initializePrSyncWatcher, getPrSyncWatcher, } from '../../../../packages/core/src/infrastructure/services/pr-sync/pr-sync-watcher.service.js';
24
+ import { getExistingConnection } from '../../../../packages/core/src/infrastructure/persistence/sqlite/connection.js';
24
25
  import { BrowserOpenerService } from '../../../../packages/core/src/infrastructure/services/browser-opener.service.js';
25
26
  import { colors, fmt, messages } from '../ui/index.js';
26
27
  function parsePort(value) {
@@ -66,7 +67,8 @@ Examples:
66
67
  getNotificationWatcher().start();
67
68
  // Start PR sync watcher to detect PR/CI status transitions on GitHub
68
69
  const gitPrService = container.resolve('IGitPrService');
69
- initializePrSyncWatcher(featureRepo, runRepo, gitPrService, notificationService);
70
+ const db = getExistingConnection();
71
+ initializePrSyncWatcher(featureRepo, runRepo, gitPrService, notificationService, undefined, db);
70
72
  getPrSyncWatcher().start();
71
73
  const url = `http://localhost:${port}`;
72
74
  messages.success(`Server ready at ${fmt.code(url)}`);
@@ -3,6 +3,9 @@ import type { FeatureNodeData } from '../../components/common/feature-node/index
3
3
  * Fetches full FeatureNodeData for a given feature ID.
4
4
  * Used by the drawer for targeted data syncing without triggering
5
5
  * a full router.refresh() / server component re-render.
6
+ *
7
+ * CI status and mergeable status are read from the DB (already updated
8
+ * by PrSyncWatcherService) instead of making duplicate GitHub API calls.
6
9
  */
7
10
  export declare function getFeatureDrawerData(featureId: string): Promise<FeatureNodeData | null>;
8
11
  //# sourceMappingURL=get-feature-drawer-data.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"get-feature-drawer-data.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/get-feature-drawer-data.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAQxE;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAsD7F"}
1
+ {"version":3,"file":"get-feature-drawer-data.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/get-feature-drawer-data.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAExE;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAgC7F"}
@@ -1,16 +1,13 @@
1
1
  'use server';
2
2
  import { resolve } from '../../lib/server-container.js';
3
- import { CiStatus } from '../../../../../packages/core/src/domain/generated/output.js';
4
3
  import { buildFeatureNodeData } from '../build-feature-node-data.js';
5
- const CI_STATUS_MAP = {
6
- success: CiStatus.Success,
7
- failure: CiStatus.Failure,
8
- pending: CiStatus.Pending,
9
- };
10
4
  /**
11
5
  * Fetches full FeatureNodeData for a given feature ID.
12
6
  * Used by the drawer for targeted data syncing without triggering
13
7
  * a full router.refresh() / server component re-render.
8
+ *
9
+ * CI status and mergeable status are read from the DB (already updated
10
+ * by PrSyncWatcherService) instead of making duplicate GitHub API calls.
14
11
  */
15
12
  export async function getFeatureDrawerData(featureId) {
16
13
  try {
@@ -23,37 +20,15 @@ export async function getFeatureDrawerData(featureId) {
23
20
  if (!feature)
24
21
  return null;
25
22
  const run = feature.agentRunId ? await agentRunRepo.findById(feature.agentRunId) : null;
26
- const [repo, baseBranch, artifact, remoteUrl, liveMergeable, liveCiResult] = await Promise.all([
23
+ // CI status and mergeable status are read from the feature record (updated by
24
+ // PrSyncWatcherService every 30s) — no duplicate GitHub API calls needed.
25
+ // Only getDefaultBranch and getRemoteUrl are kept as they are local git operations.
26
+ const [repo, baseBranch, artifact, remoteUrl] = await Promise.all([
27
27
  repoRepo.findByPath(feature.repositoryPath).catch(() => null),
28
28
  gitPrService.getDefaultBranch(feature.repositoryPath).catch(() => 'main'),
29
29
  getArtifact.execute(featureId).catch(() => null),
30
30
  gitPrService.getRemoteUrl(feature.repositoryPath).catch(() => null),
31
- feature.pr?.number
32
- ? gitPrService
33
- .getMergeableStatus(feature.repositoryPath, feature.pr.number)
34
- .catch(() => undefined)
35
- : Promise.resolve(undefined),
36
- feature.branch
37
- ? gitPrService.getCiStatus(feature.repositoryPath, feature.branch).catch(() => undefined)
38
- : Promise.resolve(undefined),
39
31
  ]);
40
- // Merge live PR status into feature data
41
- if (feature.pr) {
42
- const liveCiStatus = liveCiResult
43
- ? (CI_STATUS_MAP[liveCiResult.status] ?? CiStatus.Pending)
44
- : undefined;
45
- const updates = {};
46
- if (liveMergeable !== undefined)
47
- updates.mergeable = liveMergeable;
48
- if (liveCiStatus !== undefined)
49
- updates.ciStatus = liveCiStatus;
50
- if (Object.keys(updates).length > 0) {
51
- feature.pr = { ...feature.pr, ...updates };
52
- featureRepo.update(feature).catch(() => {
53
- /* best-effort persist */
54
- });
55
- }
56
- }
57
32
  return buildFeatureNodeData(feature, run, {
58
33
  repositoryName: repo?.name,
59
34
  baseBranch,
@@ -1 +1 @@
1
- {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/app/api/agent-events/route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAiBH,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAqEvC,wBAAgB,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CAuQ9C"}
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/app/api/agent-events/route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAiBH,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAqEvC,wBAAgB,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CA4R9C"}
@@ -191,9 +191,28 @@ export function GET(request) {
191
191
  }
192
192
  // Check for lifecycle change (agent stays "running" but moves through phases)
193
193
  if (prev.lifecycle !== feature.lifecycle) {
194
+ const prevLifecycle = prev.lifecycle;
194
195
  prev.lifecycle = feature.lifecycle;
195
196
  const nodeName = LIFECYCLE_TO_NODE[feature.lifecycle];
196
- if (nodeName) {
197
+ // Emit MergeReviewReady when lifecycle transitions TO Review
198
+ if (feature.lifecycle === SdlcLifecycle.Review &&
199
+ prevLifecycle !== SdlcLifecycle.Review) {
200
+ const prUrl = feature.pr?.url;
201
+ const message = prUrl
202
+ ? `Ready for merge review — PR: ${prUrl}`
203
+ : 'Ready for merge review';
204
+ emitEvent({
205
+ eventType: NotificationEventType.MergeReviewReady,
206
+ agentRunId: run.id,
207
+ featureId: feature.id,
208
+ featureName: feature.name,
209
+ phaseName: 'merge',
210
+ message,
211
+ severity: NotificationSeverity.Info,
212
+ timestamp: new Date().toISOString(),
213
+ });
214
+ }
215
+ else if (nodeName) {
197
216
  emitEvent({
198
217
  eventType: NotificationEventType.PhaseCompleted,
199
218
  agentRunId: run.id,
@@ -1 +1 @@
1
- {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/app/api/cli-upgrade/route.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAEvC,wBAAsB,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC,CA+B9C"}
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/app/api/cli-upgrade/route.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAEvC,wBAAsB,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC,CA4C9C"}
@@ -11,13 +11,26 @@ export async function POST() {
11
11
  controller.enqueue(encoder.encode(`data: ${chunk.replace(/\n/g, '\ndata: ')}\n\n`));
12
12
  });
13
13
  controller.enqueue(encoder.encode(`event: done\ndata: ${JSON.stringify(result)}\n\n`));
14
+ // If the upgrade succeeded, schedule a daemon self-restart so the
15
+ // new version is loaded automatically — no manual restart needed.
16
+ if (result.status === 'upgraded') {
17
+ controller.enqueue(encoder.encode(`event: restarting\ndata: restarting\n\n`));
18
+ controller.close();
19
+ await useCase.scheduleDaemonRestart();
20
+ return;
21
+ }
14
22
  }
15
23
  catch (error) {
16
24
  const message = error instanceof Error ? error.message : 'Upgrade failed';
17
25
  controller.enqueue(encoder.encode(`event: done\ndata: ${JSON.stringify({ status: 'error', currentVersion: '', latestVersion: null, errorMessage: message })}\n\n`));
18
26
  }
19
27
  finally {
20
- controller.close();
28
+ try {
29
+ controller.close();
30
+ }
31
+ catch {
32
+ // Already closed (e.g. after restart path)
33
+ }
21
34
  }
22
35
  },
23
36
  });
@@ -1 +1 @@
1
- {"version":3,"file":"derive-feature-state.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-node/derive-feature-state.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAIL,qBAAqB,EACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAE3F;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,EAC1B,OAAO,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GACjC,gBAAgB,CAkDlB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAYvD;AAED,oGAAoG;AACpG,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,qBAAqB,GAAG,gBAAgB,CAoBtF;AAED,wEAAwE;AACxE,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAOlE,CAAC;AAcF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,IAAI,GAAG,qBAAqB,CAQ7F;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACpC,SAAS,EAAE,qBAAqB,GAAG,SAAS,CAAC;IAC7C,SAAS,EAAE,qBAAqB,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,qBAAqB,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,GAC7F,cAAc,EAAE,CAmBlB;AAED,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,qBAAqB,GAAG,SAAS,CAGnC"}
1
+ {"version":3,"file":"derive-feature-state.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-node/derive-feature-state.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAIL,qBAAqB,EACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAE3F;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,EAC1B,OAAO,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GACjC,gBAAgB,CAkDlB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAYvD;AAED,oGAAoG;AACpG,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,qBAAqB,GAAG,gBAAgB,CAsBtF;AAED,wEAAwE;AACxE,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAOlE,CAAC;AAcF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,IAAI,GAAG,qBAAqB,CAQ7F;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACpC,SAAS,EAAE,qBAAqB,GAAG,SAAS,CAAC;IAC7C,SAAS,EAAE,qBAAqB,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,qBAAqB,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,GAC7F,cAAc,EAAE,CAmBlB;AAED,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,qBAAqB,GAAG,SAAS,CAGnC"}
@@ -99,6 +99,8 @@ export function mapEventTypeToState(eventType) {
99
99
  return 'action-required';
100
100
  case NotificationEventType.PrBlocked:
101
101
  return 'blocked';
102
+ case NotificationEventType.MergeReviewReady:
103
+ return 'action-required';
102
104
  }
103
105
  }
104
106
  /** Map domain SdlcLifecycle enum values to UI FeatureLifecyclePhase. */
@@ -1 +1 @@
1
- {"version":3,"file":"version-badge.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/version-badge/version-badge.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,MAAM,EACN,UAAU,EACV,KAAa,EACb,WAA2B,EAC3B,WAAW,EACX,YAAY,GACb,EAAE,iBAAiB,2CA2EnB"}
1
+ {"version":3,"file":"version-badge.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/version-badge/version-badge.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,MAAM,EACN,UAAU,EACV,KAAa,EACb,WAA2B,EAC3B,WAAW,EACX,YAAY,GACb,EAAE,iBAAiB,2CAyFnB"}
@@ -10,9 +10,14 @@ export function VersionBadge({ version, branch, commitHash, isDev = false, packa
10
10
  // In dev mode show "1.92.2-dev", in production show "v1.92.2"
11
11
  const displayVersion = isDev ? `${version}-dev` : `v${version}`;
12
12
  const isUpgrading = upgradeStatus === 'upgrading';
13
+ const isRestarting = upgradeStatus === 'restarting';
13
14
  const didUpgrade = upgradeStatus === 'upgraded';
14
15
  const upgradeError = upgradeStatus === 'error';
15
- return (_jsx(TooltipProvider, { delayDuration: 200, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("span", { className: "text-muted-foreground/80 hover:text-muted-foreground relative cursor-default text-[11px] leading-tight transition-colors", "data-testid": "version-label", children: [displayVersion, updateAvailable && !didUpgrade ? (_jsx("span", { className: "absolute -top-0.5 -right-1.5 size-1.5 rounded-full bg-emerald-400", "data-testid": "update-dot" })) : null] }) }), _jsxs(TooltipContent, { side: "right", className: "max-w-[280px] space-y-1 p-3 text-left", children: [_jsx("div", { className: "mb-1.5 text-xs font-semibold", children: packageName }), description ? (_jsx("div", { className: "text-[10px] leading-snug opacity-70", children: description })) : null, _jsxs("div", { className: "border-t border-white/10 pt-1.5", children: [_jsx(Row, { label: "Version", value: displayVersion }), isDev && branch ? _jsx(Row, { label: "Branch", value: branch }) : null, isDev && shortHash ? _jsx(Row, { label: "Commit", value: shortHash, mono: true }) : null, isDev && instancePath ? _jsx(Row, { label: "Path", value: instancePath, mono: true }) : null, latest ? (_jsx(Row, { label: "Latest", value: `v${latest}`, highlight: updateAvailable })) : null] }), didUpgrade ? (_jsx("div", { className: "border-t border-white/10 pt-1.5", children: _jsx("span", { className: "text-[10px] font-medium text-emerald-400", "data-testid": "upgrade-success", children: "Upgraded successfully \u2014 restart to apply" }) })) : upgradeError ? (_jsx("div", { className: "border-t border-white/10 pt-1.5", children: _jsx("span", { className: "text-[10px] font-medium text-red-400", "data-testid": "upgrade-error", children: "Upgrade failed" }) })) : updateAvailable ? (_jsx("div", { className: "border-t border-white/10 pt-1.5", children: _jsx("button", { type: "button", onClick: startUpgrade, disabled: isUpgrading, className: "inline-flex items-center gap-1 text-[10px] font-medium text-emerald-400 transition-colors hover:text-emerald-300 disabled:opacity-50", "data-testid": "upgrade-button", children: isUpgrading ? 'Upgrading...' : `Upgrade to v${latest}` }) })) : null] })] }) }));
16
+ return (_jsx(TooltipProvider, { delayDuration: 200, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("span", { className: "text-muted-foreground/80 hover:text-muted-foreground relative cursor-default text-[11px] leading-tight transition-colors", "data-testid": "version-label", children: [displayVersion, updateAvailable && !didUpgrade && !isRestarting ? (_jsx("span", { className: "absolute -top-0.5 -right-1.5 size-1.5 rounded-full bg-emerald-400", "data-testid": "update-dot" })) : null] }) }), _jsxs(TooltipContent, { side: "right", className: "max-w-[280px] space-y-1 p-3 text-left", children: [_jsx("div", { className: "mb-1.5 text-xs font-semibold", children: packageName }), description ? (_jsx("div", { className: "text-[10px] leading-snug opacity-70", children: description })) : null, _jsxs("div", { className: "border-t border-white/10 pt-1.5", children: [_jsx(Row, { label: "Version", value: displayVersion }), isDev && branch ? _jsx(Row, { label: "Branch", value: branch }) : null, isDev && shortHash ? _jsx(Row, { label: "Commit", value: shortHash, mono: true }) : null, isDev && instancePath ? _jsx(Row, { label: "Path", value: instancePath, mono: true }) : null, latest ? (_jsx(Row, { label: "Latest", value: `v${latest}`, highlight: updateAvailable })) : null] }), isRestarting ? (_jsx("div", { className: "border-t border-white/10 pt-1.5", children: _jsx("span", { className: "text-[10px] font-medium text-emerald-400", "data-testid": "upgrade-restarting", children: "Restarting..." }) })) : didUpgrade ? (_jsx("div", { className: "border-t border-white/10 pt-1.5", children: _jsx("span", { className: "text-[10px] font-medium text-emerald-400", "data-testid": "upgrade-success", children: "Upgraded successfully" }) })) : upgradeError ? (_jsx("div", { className: "border-t border-white/10 pt-1.5", children: _jsx("span", { className: "text-[10px] font-medium text-red-400", "data-testid": "upgrade-error", children: "Upgrade failed" }) })) : updateAvailable ? (_jsx("div", { className: "border-t border-white/10 pt-1.5", children: _jsx("button", { type: "button", onClick: startUpgrade, disabled: isUpgrading || isRestarting, className: "inline-flex items-center gap-1 text-[10px] font-medium text-emerald-400 transition-colors hover:text-emerald-300 disabled:opacity-50", "data-testid": "upgrade-button", children: isUpgrading
17
+ ? 'Upgrading...'
18
+ : isRestarting
19
+ ? 'Restarting...'
20
+ : `Upgrade to v${latest}` }) })) : null] })] }) }));
16
21
  }
17
22
  function Row({ label, value, highlight, mono, }) {
18
23
  return (_jsxs("div", { className: "flex items-center justify-between gap-4 text-[10px]", children: [_jsx("span", { className: "opacity-60", children: label }), _jsx("span", { className: [
@@ -1 +1 @@
1
- {"version":3,"file":"version-badge.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/version-badge/version-badge.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAa,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAwD/C,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,YAAY,CAOnC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,YAAY,CAAC,CAAC;AAE3C,mFAAmF;AACnF,eAAO,MAAM,UAAU,EAAE,KAOxB,CAAC;AAEF,wFAAwF;AACxF,eAAO,MAAM,WAAW,EAAE,KAWzB,CAAC;AAEF,mEAAmE;AACnE,eAAO,MAAM,UAAU,EAAE,KAWxB,CAAC;AAEF,yCAAyC;AACzC,eAAO,MAAM,OAAO,EAAE,KAKrB,CAAC;AAEF,sFAAsF;AACtF,eAAO,MAAM,eAAe,EAAE,KAO7B,CAAC;AAEF,oFAAoF;AACpF,eAAO,MAAM,kBAAkB,EAAE,KAWhC,CAAC;AAEF,kEAAkE;AAClE,eAAO,MAAM,UAAU,EAAE,KAOxB,CAAC"}
1
+ {"version":3,"file":"version-badge.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/version-badge/version-badge.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAa,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AA+D/C,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,YAAY,CAOnC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,YAAY,CAAC,CAAC;AAE3C,mFAAmF;AACnF,eAAO,MAAM,UAAU,EAAE,KAOxB,CAAC;AAEF,wFAAwF;AACxF,eAAO,MAAM,WAAW,EAAE,KAWzB,CAAC;AAEF,mEAAmE;AACnE,eAAO,MAAM,UAAU,EAAE,KAWxB,CAAC;AAEF,yCAAyC;AACzC,eAAO,MAAM,OAAO,EAAE,KAKrB,CAAC;AAEF,sFAAsF;AACtF,eAAO,MAAM,eAAe,EAAE,KAO7B,CAAC;AAEF,oFAAoF;AACpF,eAAO,MAAM,kBAAkB,EAAE,KAWhC,CAAC;AAEF,kEAAkE;AAClE,eAAO,MAAM,UAAU,EAAE,KAOxB,CAAC"}
@@ -22,6 +22,7 @@ function withNpmVersion(latest) {
22
22
  controller.enqueue(encoder.encode(`data: Upgrading to v${latest}...\n\n`));
23
23
  setTimeout(() => {
24
24
  controller.enqueue(encoder.encode(`event: done\ndata: ${JSON.stringify({ status: 'upgraded', currentVersion: '1.90.0', latestVersion: latest })}\n\n`));
25
+ controller.enqueue(encoder.encode(`event: restarting\ndata: restarting\n\n`));
25
26
  controller.close();
26
27
  }, 1000);
27
28
  },
@@ -31,6 +32,12 @@ function withNpmVersion(latest) {
31
32
  headers: { 'Content-Type': 'text/event-stream' },
32
33
  });
33
34
  }
35
+ if (url.includes('/api/version')) {
36
+ return new Response(JSON.stringify({ version: latest }), {
37
+ status: 200,
38
+ headers: { 'Content-Type': 'application/json' },
39
+ });
40
+ }
34
41
  return originalFetch(input, init);
35
42
  });
36
43
  return _jsx(Story, {});
@@ -1 +1 @@
1
- {"version":3,"file":"notification-settings-section.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/settings/notification-settings-section.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AAkBpF,MAAM,WAAW,gCAAgC;IAC/C,aAAa,EAAE,uBAAuB,CAAC;CACxC;AAED,wBAAgB,2BAA2B,CAAC,EAAE,aAAa,EAAE,EAAE,gCAAgC,2CAkJ9F"}
1
+ {"version":3,"file":"notification-settings-section.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/settings/notification-settings-section.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AAmBpF,MAAM,WAAW,gCAAgC;IAC/C,aAAa,EAAE,uBAAuB,CAAC;CACxC;AAED,wBAAgB,2BAA2B,CAAC,EAAE,aAAa,EAAE,EAAE,gCAAgC,2CAkJ9F"}
@@ -16,6 +16,7 @@ const AGENT_EVENT_TOGGLES = [
16
16
  { key: 'agentFailed', label: 'Agent Failed' },
17
17
  ];
18
18
  const PR_EVENT_TOGGLES = [
19
+ { key: 'mergeReviewReady', label: 'Merge Review Ready' },
19
20
  { key: 'prMerged', label: 'PR Merged' },
20
21
  { key: 'prClosed', label: 'PR Closed' },
21
22
  { key: 'prChecksPassed', label: 'PR Checks Passed' },
@@ -1 +1 @@
1
- {"version":3,"file":"notification-settings-section.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/settings/notification-settings-section.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,2BAA2B,EAAE,MAAM,iCAAiC,CAAC;AAE9E,QAAA,MAAM,IAAI;;;;;;;CAO0C,CAAC;AAErD,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AA4BnC,eAAO,MAAM,OAAO,EAAE,KASrB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,KASxB,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KASzB,CAAC"}
1
+ {"version":3,"file":"notification-settings-section.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/settings/notification-settings-section.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,2BAA2B,EAAE,MAAM,iCAAiC,CAAC;AAE9E,QAAA,MAAM,IAAI;;;;;;;CAO0C,CAAC;AAErD,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AA8BnC,eAAO,MAAM,OAAO,EAAE,KASrB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,KASxB,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KASzB,CAAC"}
@@ -14,6 +14,7 @@ const allEvents = {
14
14
  waitingApproval: true,
15
15
  agentCompleted: true,
16
16
  agentFailed: true,
17
+ mergeReviewReady: true,
17
18
  prMerged: true,
18
19
  prClosed: true,
19
20
  prChecksPassed: true,
@@ -26,6 +27,7 @@ const noEvents = {
26
27
  waitingApproval: false,
27
28
  agentCompleted: false,
28
29
  agentFailed: false,
30
+ mergeReviewReady: false,
29
31
  prMerged: false,
30
32
  prClosed: false,
31
33
  prChecksPassed: false,
@@ -19,6 +19,7 @@ import { InitializeSettingsUseCase } from '../../../packages/core/src/applicatio
19
19
  import { initializeSettings } from '../../../packages/core/src/infrastructure/services/settings.service.js';
20
20
  import { initializeNotificationWatcher, getNotificationWatcher, } from '../../../packages/core/src/infrastructure/services/notifications/notification-watcher.service.js';
21
21
  import { initializePrSyncWatcher, getPrSyncWatcher, } from '../../../packages/core/src/infrastructure/services/pr-sync/pr-sync-watcher.service.js';
22
+ import { getExistingConnection } from '../../../packages/core/src/infrastructure/persistence/sqlite/connection.js';
22
23
  const DEFAULT_PORT = 3000;
23
24
  async function isPortAvailable(port) {
24
25
  return new Promise((resolve) => {
@@ -64,7 +65,8 @@ async function main() {
64
65
  getNotificationWatcher().start();
65
66
  // Start PR sync watcher to detect PR/CI status transitions on GitHub
66
67
  const gitPrService = container.resolve('IGitPrService');
67
- initializePrSyncWatcher(featureRepo, runRepo, gitPrService, notificationService);
68
+ const db = getExistingConnection();
69
+ initializePrSyncWatcher(featureRepo, runRepo, gitPrService, notificationService, undefined, db);
68
70
  getPrSyncWatcher().start();
69
71
  }
70
72
  catch (error) {
@@ -1,4 +1,4 @@
1
- export type UpgradeStatus = 'idle' | 'upgrading' | 'upgraded' | 'up-to-date' | 'error';
1
+ export type UpgradeStatus = 'idle' | 'upgrading' | 'restarting' | 'upgraded' | 'up-to-date' | 'error';
2
2
  export interface CliUpgradeState {
3
3
  status: UpgradeStatus;
4
4
  output: string;
@@ -1 +1 @@
1
- {"version":3,"file":"use-cli-upgrade.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/hooks/use-cli-upgrade.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,YAAY,GAAG,OAAO,CAAC;AAEvF,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED,wBAAgB,aAAa,IAAI,eAAe,GAAG,iBAAiB,CA6EnE"}
1
+ {"version":3,"file":"use-cli-upgrade.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/hooks/use-cli-upgrade.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,WAAW,GACX,YAAY,GACZ,UAAU,GACV,YAAY,GACZ,OAAO,CAAC;AAOZ,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,IAAI,CAAC;CAC1B;AA4BD,wBAAgB,aAAa,IAAI,eAAe,GAAG,iBAAiB,CAuFnE"}
@@ -1,16 +1,46 @@
1
1
  'use client';
2
2
  import { useCallback, useRef, useState } from 'react';
3
+ /** How often to poll the server during restart (ms). */
4
+ const RESTART_POLL_MS = 500;
5
+ /** Maximum time to wait for the server to come back after restart (ms). */
6
+ const RESTART_TIMEOUT_MS = 30_000;
7
+ /**
8
+ * Poll the server until it responds, then reload the page.
9
+ */
10
+ function waitForServerAndReload(timeoutMs, pollMs) {
11
+ const deadline = Date.now() + timeoutMs;
12
+ const poll = () => {
13
+ if (Date.now() > deadline)
14
+ return;
15
+ fetch('/api/version', { method: 'GET', cache: 'no-store' })
16
+ .then((res) => {
17
+ if (res.ok) {
18
+ window.location.reload();
19
+ }
20
+ else {
21
+ setTimeout(poll, pollMs);
22
+ }
23
+ })
24
+ .catch(() => {
25
+ setTimeout(poll, pollMs);
26
+ });
27
+ };
28
+ // Brief initial delay to let the old server shut down
29
+ setTimeout(poll, pollMs);
30
+ }
3
31
  export function useCliUpgrade() {
4
32
  const [status, setStatus] = useState('idle');
5
33
  const [output, setOutput] = useState('');
6
34
  const [errorMessage, setErrorMessage] = useState();
7
35
  const abortRef = useRef(null);
36
+ const restartingRef = useRef(false);
8
37
  const startUpgrade = useCallback(() => {
9
- if (status === 'upgrading')
38
+ if (status === 'upgrading' || status === 'restarting')
10
39
  return;
11
40
  setStatus('upgrading');
12
41
  setOutput('');
13
42
  setErrorMessage(undefined);
43
+ restartingRef.current = false;
14
44
  const controller = new AbortController();
15
45
  abortRef.current = controller;
16
46
  fetch('/api/cli-upgrade', { method: 'POST', signal: controller.signal })
@@ -31,6 +61,12 @@ export function useCliUpgrade() {
31
61
  const lines = buffer.split('\n');
32
62
  buffer = lines.pop() ?? '';
33
63
  for (const line of lines) {
64
+ if (line.startsWith('event: restarting')) {
65
+ restartingRef.current = true;
66
+ setStatus('restarting');
67
+ waitForServerAndReload(RESTART_TIMEOUT_MS, RESTART_POLL_MS);
68
+ continue;
69
+ }
34
70
  if (line.startsWith('event: done')) {
35
71
  // Next data line contains the final result
36
72
  continue;
@@ -60,6 +96,9 @@ export function useCliUpgrade() {
60
96
  }
61
97
  })
62
98
  .catch((err) => {
99
+ // If we're in restarting state, the connection dropping is expected
100
+ if (restartingRef.current)
101
+ return;
63
102
  if (err instanceof DOMException && err.name === 'AbortError')
64
103
  return;
65
104
  setStatus('error');
@@ -1 +1 @@
1
- {"version":3,"file":"use-notifications.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/hooks/use-notifications.ts"],"names":[],"mappings":"AA0CA,wBAAgB,gBAAgB,IAAI,IAAI,CA8CvC"}
1
+ {"version":3,"file":"use-notifications.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/hooks/use-notifications.ts"],"names":[],"mappings":"AA4CA,wBAAgB,gBAAgB,IAAI,IAAI,CAiDvC"}
@@ -13,7 +13,8 @@ const SEVERITY_TO_TOAST = {
13
13
  };
14
14
  function dispatchToast(event, navigate) {
15
15
  const method = SEVERITY_TO_TOAST[event.severity] ?? 'info';
16
- const isActionable = event.eventType === NotificationEventType.WaitingApproval;
16
+ const isActionable = event.eventType === NotificationEventType.WaitingApproval ||
17
+ event.eventType === NotificationEventType.MergeReviewReady;
17
18
  toast[method](event.featureName, {
18
19
  description: event.message,
19
20
  ...(isActionable &&
@@ -56,8 +57,11 @@ export function useNotifications() {
56
57
  const newEvents = events.slice(processedCountRef.current);
57
58
  processedCountRef.current = events.length;
58
59
  for (const event of newEvents) {
59
- // Only notify for actionable events and completion celebrations
60
- if (event.severity !== NotificationSeverity.Error &&
60
+ // Only notify for actionable events and completion celebrations.
61
+ // MergeReviewReady is Info severity but always shown as it requires user action.
62
+ const isAlwaysShown = event.eventType === NotificationEventType.MergeReviewReady;
63
+ if (!isAlwaysShown &&
64
+ event.severity !== NotificationSeverity.Error &&
61
65
  event.severity !== NotificationSeverity.Warning &&
62
66
  event.severity !== NotificationSeverity.Success) {
63
67
  continue;