@shepai/cli 1.103.1 → 1.104.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 (175) hide show
  1. package/dist/packages/core/src/application/use-cases/upgrade/upgrade-cli.use-case.d.ts +27 -0
  2. package/dist/packages/core/src/application/use-cases/upgrade/upgrade-cli.use-case.d.ts.map +1 -0
  3. package/dist/packages/core/src/application/use-cases/upgrade/upgrade-cli.use-case.js +128 -0
  4. package/dist/packages/core/src/infrastructure/di/container.d.ts.map +1 -1
  5. package/dist/packages/core/src/infrastructure/di/container.js +5 -0
  6. package/dist/src/presentation/cli/ui/symbols.d.ts +1 -1
  7. package/dist/src/presentation/web/app/api/cli-upgrade/route.d.ts +3 -0
  8. package/dist/src/presentation/web/app/api/cli-upgrade/route.d.ts.map +1 -0
  9. package/dist/src/presentation/web/app/api/cli-upgrade/route.js +31 -0
  10. package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.d.ts.map +1 -1
  11. package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.js +1 -1
  12. package/dist/src/presentation/web/components/common/feature-drawer-tabs/activity-tab.d.ts.map +1 -1
  13. package/dist/src/presentation/web/components/common/feature-drawer-tabs/activity-tab.js +50 -8
  14. package/dist/src/presentation/web/components/common/feature-drawer-tabs/feature-drawer-tabs.d.ts +4 -1
  15. package/dist/src/presentation/web/components/common/feature-drawer-tabs/feature-drawer-tabs.d.ts.map +1 -1
  16. package/dist/src/presentation/web/components/common/feature-drawer-tabs/feature-drawer-tabs.js +31 -11
  17. package/dist/src/presentation/web/components/common/feature-drawer-tabs/overview-tab.d.ts.map +1 -1
  18. package/dist/src/presentation/web/components/common/feature-drawer-tabs/overview-tab.js +35 -2
  19. package/dist/src/presentation/web/components/common/version-badge/version-badge.d.ts.map +1 -1
  20. package/dist/src/presentation/web/components/common/version-badge/version-badge.js +6 -2
  21. package/dist/src/presentation/web/components/common/version-badge/version-badge.stories.d.ts +2 -2
  22. package/dist/src/presentation/web/components/common/version-badge/version-badge.stories.d.ts.map +1 -1
  23. package/dist/src/presentation/web/components/common/version-badge/version-badge.stories.js +20 -3
  24. package/dist/src/presentation/web/hooks/use-cli-upgrade.d.ts +11 -0
  25. package/dist/src/presentation/web/hooks/use-cli-upgrade.d.ts.map +1 -0
  26. package/dist/src/presentation/web/hooks/use-cli-upgrade.js +73 -0
  27. package/dist/tsconfig.build.tsbuildinfo +1 -1
  28. package/package.json +1 -1
  29. package/web/.next/BUILD_ID +1 -1
  30. package/web/.next/app-path-routes-manifest.json +1 -0
  31. package/web/.next/build-manifest.json +2 -2
  32. package/web/.next/fallback-build-manifest.json +2 -2
  33. package/web/.next/prerender-manifest.json +3 -3
  34. package/web/.next/required-server-files.js +2 -2
  35. package/web/.next/required-server-files.json +2 -2
  36. package/web/.next/routes-manifest.json +6 -0
  37. package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +20 -20
  38. package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  39. package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  40. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page/server-reference-manifest.json +25 -25
  41. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page.js.nft.json +1 -1
  42. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
  43. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +25 -25
  44. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  45. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  46. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +18 -18
  47. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  48. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  49. package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +20 -20
  50. package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  51. package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  52. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page/server-reference-manifest.json +25 -25
  53. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page.js.nft.json +1 -1
  54. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
  55. package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +25 -25
  56. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  57. package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  58. package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +18 -18
  59. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  60. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  61. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +18 -18
  62. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  63. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  64. package/web/.next/server/app/_global-error.html +2 -2
  65. package/web/.next/server/app/_global-error.rsc +1 -1
  66. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  67. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  68. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  69. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  70. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  71. package/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  72. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  73. package/web/.next/server/app/api/attachments/preview/route.js.nft.json +1 -1
  74. package/web/.next/server/app/api/cli-upgrade/route/app-paths-manifest.json +3 -0
  75. package/web/.next/server/app/api/cli-upgrade/route/build-manifest.json +11 -0
  76. package/web/.next/server/app/api/cli-upgrade/route/server-reference-manifest.json +4 -0
  77. package/web/.next/server/app/api/cli-upgrade/route.js +6 -0
  78. package/web/.next/server/app/api/cli-upgrade/route.js.map +5 -0
  79. package/web/.next/server/app/api/cli-upgrade/route.js.nft.json +1 -0
  80. package/web/.next/server/app/api/cli-upgrade/route_client-reference-manifest.js +2 -0
  81. package/web/.next/server/app/api/evidence/route.js.nft.json +1 -1
  82. package/web/.next/server/app/settings/page/server-reference-manifest.json +5 -5
  83. package/web/.next/server/app/settings/page.js.nft.json +1 -1
  84. package/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  85. package/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  86. package/web/.next/server/app/skills/page.js.nft.json +1 -1
  87. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  88. package/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  89. package/web/.next/server/app/tools/page.js.nft.json +1 -1
  90. package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  91. package/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  92. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  93. package/web/.next/server/app-paths-manifest.json +1 -0
  94. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_cli-upgrade_route_actions_dc509b52.js +3 -0
  95. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_cli-upgrade_route_actions_dc509b52.js.map +1 -0
  96. package/web/.next/server/chunks/[root-of-the-server]__d2c18946._.js +11 -0
  97. package/web/.next/server/chunks/[root-of-the-server]__d2c18946._.js.map +1 -0
  98. package/web/.next/server/chunks/[root-of-the-server]__f47bb8a5._.js +1 -1
  99. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
  100. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
  101. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_0a7546ed.js +1 -1
  102. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_0a7546ed.js.map +1 -1
  103. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_978cbb4c.js +1 -1
  104. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_978cbb4c.js.map +1 -1
  105. package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js +1 -1
  106. package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js.map +1 -1
  107. package/web/.next/server/chunks/ssr/[root-of-the-server]__24dd0927._.js +2 -2
  108. package/web/.next/server/chunks/ssr/[root-of-the-server]__357d99f9._.js +1 -1
  109. package/web/.next/server/chunks/ssr/[root-of-the-server]__51fc98b0._.js +1 -1
  110. package/web/.next/server/chunks/ssr/[root-of-the-server]__51fc98b0._.js.map +1 -1
  111. package/web/.next/server/chunks/ssr/[root-of-the-server]__57f37786._.js +1 -1
  112. package/web/.next/server/chunks/ssr/[root-of-the-server]__57f37786._.js.map +1 -1
  113. package/web/.next/server/chunks/ssr/[root-of-the-server]__5d87af2e._.js +1 -1
  114. package/web/.next/server/chunks/ssr/[root-of-the-server]__708dcad1._.js +1 -1
  115. package/web/.next/server/chunks/ssr/[root-of-the-server]__708dcad1._.js.map +1 -1
  116. package/web/.next/server/chunks/ssr/[root-of-the-server]__845a9d2a._.js +1 -1
  117. package/web/.next/server/chunks/ssr/[root-of-the-server]__845a9d2a._.js.map +1 -1
  118. package/web/.next/server/chunks/ssr/[root-of-the-server]__89e8a5cc._.js +2 -2
  119. package/web/.next/server/chunks/ssr/[root-of-the-server]__8e002910._.js +1 -1
  120. package/web/.next/server/chunks/ssr/[root-of-the-server]__8e002910._.js.map +1 -1
  121. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +2 -2
  122. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
  123. package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js +1 -1
  124. package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js.map +1 -1
  125. package/web/.next/server/chunks/ssr/_135edb9c._.js +1 -1
  126. package/web/.next/server/chunks/ssr/_135edb9c._.js.map +1 -1
  127. package/web/.next/server/chunks/ssr/{_b3352e34._.js → _1e9bf571._.js} +2 -2
  128. package/web/.next/server/chunks/ssr/{_b3352e34._.js.map → _1e9bf571._.js.map} +1 -1
  129. package/web/.next/server/chunks/ssr/_8dacd1e7._.js +1 -1
  130. package/web/.next/server/chunks/ssr/_8dacd1e7._.js.map +1 -1
  131. package/web/.next/server/chunks/ssr/{_23e5ab2c._.js → _c189deb2._.js} +2 -2
  132. package/web/.next/server/chunks/ssr/{_23e5ab2c._.js.map → _c189deb2._.js.map} +1 -1
  133. package/web/.next/server/chunks/ssr/_cc77a143._.js +1 -1
  134. package/web/.next/server/chunks/ssr/_cc77a143._.js.map +1 -1
  135. package/web/.next/server/chunks/ssr/{_82e0cb85._.js → _eafbd006._.js} +2 -2
  136. package/web/.next/server/chunks/ssr/{_82e0cb85._.js.map → _eafbd006._.js.map} +1 -1
  137. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js +1 -1
  138. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js.map +1 -1
  139. package/web/.next/server/chunks/ssr/src_presentation_web_7275ce5d._.js +2 -2
  140. package/web/.next/server/chunks/ssr/src_presentation_web_7275ce5d._.js.map +1 -1
  141. package/web/.next/server/chunks/ssr/src_presentation_web_758dfa10._.js +2 -2
  142. package/web/.next/server/chunks/ssr/src_presentation_web_758dfa10._.js.map +1 -1
  143. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_84803221.js +1 -1
  144. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_84803221.js.map +1 -1
  145. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js +1 -1
  146. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js.map +1 -1
  147. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js +1 -1
  148. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js.map +1 -1
  149. package/web/.next/server/chunks/ssr/src_presentation_web_components_bf50c527._.js +1 -1
  150. package/web/.next/server/chunks/ssr/src_presentation_web_components_bf50c527._.js.map +1 -1
  151. package/web/.next/server/chunks/ssr/src_presentation_web_components_de814675._.js +1 -1
  152. package/web/.next/server/chunks/ssr/src_presentation_web_components_de814675._.js.map +1 -1
  153. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  154. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
  155. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  156. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
  157. package/web/.next/server/pages/500.html +2 -2
  158. package/web/.next/server/server-reference-manifest.js +1 -1
  159. package/web/.next/server/server-reference-manifest.json +30 -30
  160. package/web/.next/static/chunks/114abb475679448b.js +2 -0
  161. package/web/.next/static/chunks/{8d98f1d9341e7698.js → 3596ba73f2cbc86f.js} +1 -1
  162. package/web/.next/static/chunks/{2016bacd1f5fcc12.js → 37b1807da4cd1ea0.js} +1 -1
  163. package/web/.next/static/chunks/3bb35e0689e8b6b7.js +1 -0
  164. package/web/.next/static/chunks/{4fd27329675da3a8.js → 4cef83745ab38234.js} +1 -1
  165. package/web/.next/static/chunks/{2e2bbd77e10e036c.js → 7f84301a5dd54554.js} +1 -1
  166. package/web/.next/static/chunks/{8b1d0f725365fc83.js → 85b34b1ecd6b8562.js} +1 -1
  167. package/web/.next/static/chunks/{a0f87e10178f4c0e.js → b352ef3d923492dc.js} +1 -1
  168. package/web/.next/static/chunks/{396a8b5222dfe0f1.js → dcf2a8f422f008df.js} +1 -1
  169. package/web/.next/static/chunks/{d7cc1b6cf702a840.js → e87a7aacbfc2862a.js} +2 -2
  170. package/web/.next/static/chunks/{3e1f5c0f67be293f.js → e8c5a627803b1701.js} +1 -1
  171. package/web/.next/static/chunks/0c1c3c49b2145c9b.js +0 -2
  172. package/web/.next/static/chunks/2a6f05312c4f37ce.js +0 -1
  173. /package/web/.next/static/{7gAZbyat8KbbWMGw1K8nJ → gjuhrSkwZrfQPA7lKPGEW}/_buildManifest.js +0 -0
  174. /package/web/.next/static/{7gAZbyat8KbbWMGw1K8nJ → gjuhrSkwZrfQPA7lKPGEW}/_clientMiddlewareManifest.json +0 -0
  175. /package/web/.next/static/{7gAZbyat8KbbWMGw1K8nJ → gjuhrSkwZrfQPA7lKPGEW}/_ssgManifest.js +0 -0
@@ -0,0 +1,27 @@
1
+ /**
2
+ * UpgradeCliUseCase
3
+ *
4
+ * Executes a self-upgrade of @shepai/cli to the latest published version.
5
+ * Mirrors the CLI upgrade command logic but designed for use from the web layer.
6
+ *
7
+ * Flow:
8
+ * 1. Check latest version from npm registry (fail-open)
9
+ * 2. Compare with current version — return early if up to date
10
+ * 3. Run `npm i -g @shepai/cli@latest` with streamed output
11
+ * 4. Return result with old/new version info
12
+ */
13
+ import type { IVersionService } from '../../ports/output/services/version-service.interface.js';
14
+ export interface UpgradeResult {
15
+ status: 'up-to-date' | 'upgraded' | 'error';
16
+ currentVersion: string;
17
+ latestVersion: string | null;
18
+ errorMessage?: string;
19
+ }
20
+ export declare class UpgradeCliUseCase {
21
+ private readonly versionService;
22
+ constructor(versionService: IVersionService);
23
+ execute(onOutput?: (data: string) => void): Promise<UpgradeResult>;
24
+ private getLatestVersion;
25
+ private runNpmInstall;
26
+ }
27
+ //# sourceMappingURL=upgrade-cli.use-case.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upgrade-cli.use-case.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/application/use-cases/upgrade/upgrade-cli.use-case.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0DAA0D,CAAC;AAEhG,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,YAAY,GAAG,UAAU,GAAG,OAAO,CAAC;IAC5C,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAID,qBACa,iBAAiB;IAG1B,OAAO,CAAC,QAAQ,CAAC,cAAc;gBAAd,cAAc,EAAE,eAAe;IAG5C,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC;IAkCxE,OAAO,CAAC,gBAAgB;IA6CxB,OAAO,CAAC,aAAa;CAuBtB"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * UpgradeCliUseCase
3
+ *
4
+ * Executes a self-upgrade of @shepai/cli to the latest published version.
5
+ * Mirrors the CLI upgrade command logic but designed for use from the web layer.
6
+ *
7
+ * Flow:
8
+ * 1. Check latest version from npm registry (fail-open)
9
+ * 2. Compare with current version — return early if up to date
10
+ * 3. Run `npm i -g @shepai/cli@latest` with streamed output
11
+ * 4. Return result with old/new version info
12
+ */
13
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
14
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
15
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
16
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
17
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
18
+ };
19
+ var __metadata = (this && this.__metadata) || function (k, v) {
20
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
21
+ };
22
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
23
+ return function (target, key) { decorator(target, key, paramIndex); }
24
+ };
25
+ import { injectable, inject } from 'tsyringe';
26
+ import { spawn } from 'node:child_process';
27
+ const VERSION_CHECK_TIMEOUT_MS = 10_000;
28
+ let UpgradeCliUseCase = class UpgradeCliUseCase {
29
+ versionService;
30
+ constructor(versionService) {
31
+ this.versionService = versionService;
32
+ }
33
+ async execute(onOutput) {
34
+ const { version: currentVersion } = this.versionService.getVersion();
35
+ // 1. Check latest version (fail-open)
36
+ const latestVersion = await this.getLatestVersion(onOutput);
37
+ // 2. Already up to date
38
+ if (latestVersion && latestVersion === currentVersion) {
39
+ onOutput?.(`Already up to date (v${currentVersion})\n`);
40
+ return { status: 'up-to-date', currentVersion, latestVersion };
41
+ }
42
+ // 3. Run upgrade
43
+ const target = latestVersion ? `v${latestVersion}` : 'latest';
44
+ onOutput?.(`Upgrading from v${currentVersion} to ${target}...\n`);
45
+ try {
46
+ const exitCode = await this.runNpmInstall(onOutput);
47
+ if (exitCode === 0) {
48
+ onOutput?.(`Successfully upgraded to ${target}\n`);
49
+ return { status: 'upgraded', currentVersion, latestVersion };
50
+ }
51
+ else {
52
+ const msg = `npm install exited with code ${exitCode}`;
53
+ onOutput?.(`Error: ${msg}\n`);
54
+ return { status: 'error', currentVersion, latestVersion, errorMessage: msg };
55
+ }
56
+ }
57
+ catch (error) {
58
+ const msg = error instanceof Error ? error.message : 'Installation failed';
59
+ onOutput?.(`Error: ${msg}\n`);
60
+ return { status: 'error', currentVersion, latestVersion, errorMessage: msg };
61
+ }
62
+ }
63
+ getLatestVersion(onOutput) {
64
+ return new Promise((resolve) => {
65
+ let output = '';
66
+ let settled = false;
67
+ const child = spawn('npm', ['view', '@shepai/cli', 'version'], {
68
+ stdio: ['ignore', 'pipe', 'pipe'],
69
+ });
70
+ const timeout = setTimeout(() => {
71
+ if (!settled) {
72
+ settled = true;
73
+ child.kill();
74
+ onOutput?.('Version check timed out, proceeding with upgrade...\n');
75
+ resolve(null);
76
+ }
77
+ }, VERSION_CHECK_TIMEOUT_MS);
78
+ child.stdout?.on('data', (data) => {
79
+ output += data.toString();
80
+ });
81
+ child.on('close', (code) => {
82
+ if (!settled) {
83
+ settled = true;
84
+ clearTimeout(timeout);
85
+ if (code === 0 && output.trim()) {
86
+ resolve(output.trim());
87
+ }
88
+ else {
89
+ resolve(null);
90
+ }
91
+ }
92
+ });
93
+ child.on('error', () => {
94
+ if (!settled) {
95
+ settled = true;
96
+ clearTimeout(timeout);
97
+ onOutput?.('Could not check latest version\n');
98
+ resolve(null);
99
+ }
100
+ });
101
+ });
102
+ }
103
+ runNpmInstall(onOutput) {
104
+ return new Promise((resolve, reject) => {
105
+ const child = spawn('npm', ['i', '-g', '@shepai/cli@latest'], {
106
+ stdio: ['ignore', 'pipe', 'pipe'],
107
+ });
108
+ child.stdout?.on('data', (data) => {
109
+ onOutput?.(data.toString());
110
+ });
111
+ child.stderr?.on('data', (data) => {
112
+ onOutput?.(data.toString());
113
+ });
114
+ child.on('close', (code) => {
115
+ resolve(code ?? 1);
116
+ });
117
+ child.on('error', (err) => {
118
+ reject(err);
119
+ });
120
+ });
121
+ }
122
+ };
123
+ UpgradeCliUseCase = __decorate([
124
+ injectable(),
125
+ __param(0, inject('IVersionService')),
126
+ __metadata("design:paramtypes", [Object])
127
+ ], UpgradeCliUseCase);
128
+ export { UpgradeCliUseCase };
@@ -1 +1 @@
1
- {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../../../../../../packages/core/src/infrastructure/di/container.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAiHrC;;;;;;GAMG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,SAAS,CAAC,CA0SrE;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../../../../../../packages/core/src/infrastructure/di/container.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAkHrC;;;;;;GAMG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,SAAS,CAAC,CA8SrE;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,CAAC"}
@@ -77,6 +77,7 @@ import { DeleteRepositoryUseCase } from '../../application/use-cases/repositorie
77
77
  import { CheckAndUnblockFeaturesUseCase } from '../../application/use-cases/features/check-and-unblock-features.use-case.js';
78
78
  import { UpdateFeatureLifecycleUseCase } from '../../application/use-cases/features/update/update-feature-lifecycle.use-case.js';
79
79
  import { CleanupFeatureWorktreeUseCase } from '../../application/use-cases/features/cleanup-feature-worktree.use-case.js';
80
+ import { UpgradeCliUseCase } from '../../application/use-cases/upgrade/upgrade-cli.use-case.js';
80
81
  // Session listing
81
82
  import { ClaudeCodeSessionRepository } from '../services/agents/sessions/claude-code-session.repository.js';
82
83
  import { StubSessionRepository } from '../services/agents/sessions/stub-session.repository.js';
@@ -279,6 +280,7 @@ export async function initializeContainer() {
279
280
  container.registerSingleton(CheckAndUnblockFeaturesUseCase);
280
281
  container.registerSingleton(UpdateFeatureLifecycleUseCase);
281
282
  container.registerSingleton(CleanupFeatureWorktreeUseCase);
283
+ container.registerSingleton(UpgradeCliUseCase);
282
284
  // Session repositories (per-AgentType string tokens)
283
285
  container.register(`IAgentSessionRepository:${AgentType.ClaudeCode}`, {
284
286
  useFactory: () => new ClaudeCodeSessionRepository(),
@@ -360,6 +362,9 @@ export async function initializeContainer() {
360
362
  container.register('CleanupFeatureWorktreeUseCase', {
361
363
  useFactory: (c) => c.resolve(CleanupFeatureWorktreeUseCase),
362
364
  });
365
+ container.register('UpgradeCliUseCase', {
366
+ useFactory: (c) => c.resolve(UpgradeCliUseCase),
367
+ });
363
368
  _initialized = true;
364
369
  return container;
365
370
  }
@@ -19,7 +19,7 @@ export declare const symbols: {
19
19
  /** Warning triangle */
20
20
  readonly warning: "⚠" | "‼";
21
21
  /** Info circle */
22
- readonly info: "" | "i";
22
+ readonly info: "i" | "";
23
23
  /** Arrow/pointer */
24
24
  readonly arrow: "→" | "->";
25
25
  /** Bullet point */
@@ -0,0 +1,3 @@
1
+ export declare const dynamic = "force-dynamic";
2
+ export declare function POST(): Promise<Response>;
3
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,31 @@
1
+ import { resolve } from '../../../lib/server-container.js';
2
+ // Force dynamic — SSE streams must never be statically optimized or cached
3
+ export const dynamic = 'force-dynamic';
4
+ export async function POST() {
5
+ const useCase = resolve('UpgradeCliUseCase');
6
+ const encoder = new TextEncoder();
7
+ const stream = new ReadableStream({
8
+ async start(controller) {
9
+ try {
10
+ const result = await useCase.execute((chunk) => {
11
+ controller.enqueue(encoder.encode(`data: ${chunk.replace(/\n/g, '\ndata: ')}\n\n`));
12
+ });
13
+ controller.enqueue(encoder.encode(`event: done\ndata: ${JSON.stringify(result)}\n\n`));
14
+ }
15
+ catch (error) {
16
+ const message = error instanceof Error ? error.message : 'Upgrade failed';
17
+ controller.enqueue(encoder.encode(`event: done\ndata: ${JSON.stringify({ status: 'error', currentVersion: '', latestVersion: null, errorMessage: message })}\n\n`));
18
+ }
19
+ finally {
20
+ controller.close();
21
+ }
22
+ },
23
+ });
24
+ return new Response(stream, {
25
+ headers: {
26
+ 'Content-Type': 'text/event-stream',
27
+ 'Cache-Control': 'no-cache',
28
+ Connection: 'keep-alive',
29
+ },
30
+ });
31
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"feature-drawer-client.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/control-center-drawer/feature-drawer-client.tsx"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAG/D,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,UAAU,CAAC;IACjB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,wBAAgB,mBAAmB,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,wBAAwB,2CA4iB1F"}
1
+ {"version":3,"file":"feature-drawer-client.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/control-center-drawer/feature-drawer-client.tsx"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAG/D,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,UAAU,CAAC;IACjB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,wBAAgB,mBAAmB,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,wBAAwB,2CA6iB1F"}
@@ -329,7 +329,7 @@ export function FeatureDrawerClient({ view: initialView, urlTab }) {
329
329
  // ── Body ──────────────────────────────────────────────────────────────
330
330
  let body = null;
331
331
  if (view.type === 'feature' && featureNode) {
332
- body = (_jsx(FeatureDrawerTabs, { featureNode: featureNode, featureId: featureNode.featureId, initialTab: view.initialTab, urlTab: urlTab, prdData: prdData, prdSelections: prdSelections, onPrdSelect: (qId, oId) => setPrdSelections((prev) => ({ ...prev, [qId]: oId })), onPrdApprove: handlePrdApprove, onPrdReject: handlePrdReject, isPrdLoading: isLoadingPrd, techData: techData, onTechApprove: handleTechApprove, onTechReject: handleTechReject, isTechLoading: isLoadingTech, productData: isLoadingTechProduct ? null : techProductData, mergeData: mergeData, onMergeApprove: handleMergeApprove, onMergeReject: handleMergeReject, isMergeLoading: isLoadingMerge, isRejecting: isRejecting, chatInput: chatInput, onChatInputChange: setChatInput }));
332
+ body = (_jsx(FeatureDrawerTabs, { featureNode: featureNode, featureId: featureNode.featureId, initialTab: view.initialTab, urlTab: urlTab, sseEvents: events, prdData: prdData, prdSelections: prdSelections, onPrdSelect: (qId, oId) => setPrdSelections((prev) => ({ ...prev, [qId]: oId })), onPrdApprove: handlePrdApprove, onPrdReject: handlePrdReject, isPrdLoading: isLoadingPrd, techData: techData, onTechApprove: handleTechApprove, onTechReject: handleTechReject, isTechLoading: isLoadingTech, productData: isLoadingTechProduct ? null : techProductData, mergeData: mergeData, onMergeApprove: handleMergeApprove, onMergeReject: handleMergeReject, isMergeLoading: isLoadingMerge, isRejecting: isRejecting, chatInput: chatInput, onChatInputChange: setChatInput }));
333
333
  }
334
334
  return (_jsx(BaseDrawer, { open: isOpen, onClose: attemptClose, size: "md", modal: false, header: header, "data-testid": view.type === 'feature' ? 'feature-drawer' : 'repository-drawer', children: body }));
335
335
  }
@@ -1 +1 @@
1
- {"version":3,"file":"activity-tab.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/activity-tab.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,eAAe,EACf,qBAAqB,EACtB,MAAM,yCAAyC,CAAC;AAIjD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,eAAe,EAAE,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,iBAAiB,CAAC,EAAE,qBAAqB,EAAE,CAAC;CAC7C;AA6BD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,8DAA8D;IAC9D,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,eAAe,EAAE,EAC1B,QAAQ,CAAC,EAAE,qBAAqB,EAAE,GACjC,iBAAiB,EAAE,CAgErB;AAMD,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAAE,gBAAgB,2CAyD3F"}
1
+ {"version":3,"file":"activity-tab.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/activity-tab.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,eAAe,EACf,qBAAqB,EACtB,MAAM,yCAAyC,CAAC;AA8CjD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,eAAe,EAAE,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,iBAAiB,CAAC,EAAE,qBAAqB,EAAE,CAAC;CAC7C;AA6BD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,8DAA8D;IAC9D,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,eAAe,EAAE,EAC1B,QAAQ,CAAC,EAAE,qBAAqB,EAAE,GACjC,iBAAiB,EAAE,CAgErB;AAMD,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAAE,gBAAgB,2CA4D3F"}
@@ -1,8 +1,48 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useState, useEffect, useRef } from 'react';
3
4
  import { Loader2, AlertCircle, Clock } from 'lucide-react';
4
5
  import { InlineAttachments } from '../../common/inline-attachments/index.js';
5
6
  import { formatDuration } from '../../../lib/format-duration.js';
7
+ /**
8
+ * Computes the effective duration for a phase timing entry.
9
+ * For completed phases, uses the server-provided durationMs.
10
+ * For in-progress phases (has startedAt but no completedAt), computes
11
+ * elapsed time client-side so the UI shows a live-updating timer.
12
+ */
13
+ function getEffectiveDuration(timing, now) {
14
+ if (timing.durationMs != null && timing.durationMs > 0)
15
+ return timing.durationMs;
16
+ if (!timing.completedAt && timing.startedAt) {
17
+ const started = new Date(timing.startedAt).getTime();
18
+ if (!Number.isNaN(started))
19
+ return Math.max(0, now - started);
20
+ }
21
+ return 0;
22
+ }
23
+ /** Returns true if any non-lifecycle phase is still in progress. */
24
+ function hasRunningPhase(timings) {
25
+ return timings.some((t) => !t.phase.startsWith('run:') && !t.completedAt && t.startedAt);
26
+ }
27
+ /** Hook that ticks every second while there are running phases. */
28
+ function useTickingNow(timings) {
29
+ const [now, setNow] = useState(Date.now);
30
+ const intervalRef = useRef(null);
31
+ useEffect(() => {
32
+ const running = timings ? hasRunningPhase(timings) : false;
33
+ if (running) {
34
+ // Tick every second
35
+ intervalRef.current = setInterval(() => setNow(Date.now()), 1000);
36
+ }
37
+ return () => {
38
+ if (intervalRef.current) {
39
+ clearInterval(intervalRef.current);
40
+ intervalRef.current = null;
41
+ }
42
+ };
43
+ }, [timings]);
44
+ return now;
45
+ }
6
46
  const NODE_TO_PHASE = {
7
47
  analyze: 'Analyzing',
8
48
  requirements: 'Requirements',
@@ -94,6 +134,7 @@ export function buildLifecycleTimeline(timings, feedback) {
94
134
  * Component
95
135
  * ------------------------------------------------------------------------- */
96
136
  export function ActivityTab({ timings, loading, error, rejectionFeedback }) {
137
+ const now = useTickingNow(timings);
97
138
  if (loading) {
98
139
  return (_jsx("div", { "data-testid": "activity-tab-loading", className: "flex items-center justify-center p-8", children: _jsx(Loader2, { className: "text-muted-foreground h-6 w-6 animate-spin" }) }));
99
140
  }
@@ -104,24 +145,24 @@ export function ActivityTab({ timings, loading, error, rejectionFeedback }) {
104
145
  return (_jsxs("div", { className: "flex flex-col items-center justify-center gap-2 p-8", children: [_jsx(Clock, { className: "text-muted-foreground h-8 w-8" }), _jsx("p", { className: "text-muted-foreground text-sm", children: "No activity recorded yet" })] }));
105
146
  }
106
147
  const nodeTimings = timings.filter((t) => !isLifecycleEvent(t.phase));
107
- const maxDurationMs = Math.max(...nodeTimings.map((t) => t.durationMs ?? 0), ...nodeTimings.map((t) => t.approvalWaitMs ?? 0), 0);
148
+ const maxDurationMs = Math.max(...nodeTimings.map((t) => getEffectiveDuration(t, now)), ...nodeTimings.map((t) => t.approvalWaitMs ?? 0), 0);
108
149
  const iterations = buildLifecycleTimeline(timings, rejectionFeedback);
109
150
  const hasMultipleIterations = iterations.length > 1;
110
- const totalExecMs = nodeTimings.reduce((sum, t) => sum + (t.durationMs ?? 0), 0);
151
+ const totalExecMs = nodeTimings.reduce((sum, t) => sum + getEffectiveDuration(t, now), 0);
111
152
  const totalWaitMs = nodeTimings.reduce((sum, t) => sum + (t.approvalWaitMs ?? 0), 0);
112
- return (_jsxs("div", { className: "flex flex-col gap-4 p-4", children: [_jsx("div", { "data-testid": "activity-timings", className: "flex flex-col gap-3", children: iterations.map((iteration) => (_jsx(IterationGroup, { iteration: iteration, showHeader: hasMultipleIterations, maxDurationMs: maxDurationMs }, iteration.number))) }), totalExecMs > 0 ? (_jsx(SummaryTotals, { totalExecMs: totalExecMs, totalWaitMs: totalWaitMs })) : null] }));
153
+ return (_jsxs("div", { className: "flex flex-col gap-4 p-4", children: [_jsx("div", { "data-testid": "activity-timings", className: "flex flex-col gap-3", children: iterations.map((iteration) => (_jsx(IterationGroup, { iteration: iteration, showHeader: hasMultipleIterations, maxDurationMs: maxDurationMs, now: now }, iteration.number))) }), totalExecMs > 0 ? (_jsx(SummaryTotals, { totalExecMs: totalExecMs, totalWaitMs: totalWaitMs })) : null] }));
113
154
  }
114
155
  /* ---------------------------------------------------------------------------
115
156
  * Iteration group — one cycle of work
116
157
  * ------------------------------------------------------------------------- */
117
- function IterationGroup({ iteration, showHeader, maxDurationMs, }) {
158
+ function IterationGroup({ iteration, showHeader, maxDurationMs, now, }) {
118
159
  return (_jsxs("div", { "data-testid": `iteration-${iteration.number}`, className: "flex flex-col gap-1.5", children: [showHeader ? (_jsxs("div", { className: "text-muted-foreground text-xs font-medium", children: ["Iteration ", iteration.number] })) : null, iteration.timings.map((t) => {
119
160
  if (isLifecycleEvent(t.phase)) {
120
161
  // For run:rejected, the message comes from the iteration, not a separate map
121
162
  const isRejection = t.phase === 'run:rejected';
122
163
  return (_jsx(LifecycleEventRow, { timing: t, message: isRejection ? iteration.rejectionMessage : undefined, attachments: isRejection ? iteration.rejectionAttachments : undefined }, `${t.agentRunId}-${t.phase}-${t.startedAt}`));
123
164
  }
124
- return (_jsx(NodeTimingRow, { timing: t, maxDurationMs: maxDurationMs }, `${t.agentRunId}-${t.phase}-${t.startedAt}`));
165
+ return (_jsx(NodeTimingRow, { timing: t, maxDurationMs: maxDurationMs, now: now }, `${t.agentRunId}-${t.phase}-${t.startedAt}`));
125
166
  })] }));
126
167
  }
127
168
  /* ---------------------------------------------------------------------------
@@ -134,14 +175,15 @@ function LifecycleEventRow({ timing, message, attachments, }) {
134
175
  const hasAttachments = attachments && attachments.length > 0;
135
176
  return (_jsxs("div", { className: `text-xs ${event.colorClass}`, children: [_jsx("span", { children: event.label }), message ? (_jsxs("span", { "data-testid": "rejection-feedback-text", className: "text-muted-foreground ml-2 font-normal italic", children: ["\u2014 ", message] })) : null, hasAttachments ? (_jsx("div", { "data-testid": "rejection-feedback-attachments", className: "mt-1.5 ml-2", children: _jsx(InlineAttachments, { text: "", attachmentPaths: attachments }) })) : null] }));
136
177
  }
137
- function NodeTimingRow({ timing, maxDurationMs, }) {
178
+ function NodeTimingRow({ timing, maxDurationMs, now, }) {
138
179
  const label = timing.phase.includes(':')
139
180
  ? `rev ${timing.phase.split(':')[1]}`
140
181
  : (NODE_TO_PHASE[timing.phase] ?? timing.phase);
141
- const durationMs = timing.durationMs ?? 0;
182
+ const durationMs = getEffectiveDuration(timing, now);
142
183
  const barPercent = maxDurationMs > 0 ? Math.max(2, (durationMs / maxDurationMs) * 100) : 2;
184
+ const isRunning = !timing.completedAt && !!timing.startedAt;
143
185
  const barColorClass = timing.completedAt ? 'bg-emerald-500' : 'bg-blue-500';
144
- return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("div", { "data-testid": `timing-bar-${timing.phase}`, className: "flex items-center gap-3", children: [_jsx("span", { className: "text-muted-foreground w-24 shrink-0 text-xs", children: label }), _jsx("div", { className: "bg-muted h-3 flex-1 overflow-hidden rounded-full", children: _jsx("div", { className: `h-full rounded-full ${barColorClass}`, style: { width: `${Math.min(barPercent, 100)}%` } }) }), _jsx("span", { className: "text-muted-foreground w-14 shrink-0 text-right text-xs", children: formatDuration(durationMs) })] }), timing.approvalWaitMs != null && timing.approvalWaitMs > 0 ? (_jsx(ApprovalWaitRow, { timing: timing, maxDurationMs: maxDurationMs })) : null] }));
186
+ return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("div", { "data-testid": `timing-bar-${timing.phase}`, className: "flex items-center gap-3", children: [_jsx("span", { className: "text-muted-foreground w-24 shrink-0 text-xs", children: label }), _jsx("div", { className: "bg-muted h-3 flex-1 overflow-hidden rounded-full", children: _jsx("div", { className: `h-full rounded-full ${barColorClass}${isRunning ? 'animate-pulse' : ''}`, style: { width: `${Math.min(barPercent, 100)}%` } }) }), _jsx("span", { className: "text-muted-foreground w-14 shrink-0 text-right text-xs", children: formatDuration(durationMs) })] }), timing.approvalWaitMs != null && timing.approvalWaitMs > 0 ? (_jsx(ApprovalWaitRow, { timing: timing, maxDurationMs: maxDurationMs })) : null] }));
145
187
  }
146
188
  function ApprovalWaitRow({ timing, maxDurationMs, }) {
147
189
  const waitMs = timing.approvalWaitMs ?? 0;
@@ -1,3 +1,4 @@
1
+ import type { NotificationEvent } from '../../../../../../packages/core/src/domain/generated/output.js';
1
2
  import type { FeatureNodeData } from '../../common/feature-node/index.js';
2
3
  import type { PrdQuestionnaireData } from '../../common/prd-questionnaire/index.js';
3
4
  import type { TechDecisionsReviewData } from '../../common/tech-decisions-review/index.js';
@@ -11,6 +12,8 @@ export interface FeatureDrawerTabsProps {
11
12
  initialTab?: FeatureTabKey;
12
13
  /** Tab key from URL path segment (e.g. /feature/[id]/activity → 'activity'). */
13
14
  urlTab?: FeatureTabKey;
15
+ /** SSE events from the agent events provider, used to trigger tab data refresh. */
16
+ sseEvents?: readonly NotificationEvent[];
14
17
  prdData?: PrdQuestionnaireData | null;
15
18
  prdSelections?: Record<string, string>;
16
19
  onPrdSelect?: (questionId: string, optionId: string) => void;
@@ -30,5 +33,5 @@ export interface FeatureDrawerTabsProps {
30
33
  chatInput?: string;
31
34
  onChatInputChange?: (value: string) => void;
32
35
  }
33
- export declare function FeatureDrawerTabs({ featureNode, featureId, initialTab, urlTab, prdData, prdSelections, onPrdSelect, onPrdApprove, onPrdReject, isPrdLoading, techData, onTechApprove, onTechReject, isTechLoading, productData, mergeData, onMergeApprove, onMergeReject, isMergeLoading, isRejecting, chatInput, onChatInputChange, }: FeatureDrawerTabsProps): import("react/jsx-runtime").JSX.Element;
36
+ export declare function FeatureDrawerTabs({ featureNode, featureId, initialTab, urlTab, prdData, prdSelections, onPrdSelect, onPrdApprove, onPrdReject, isPrdLoading, techData, onTechApprove, onTechReject, isTechLoading, productData, mergeData, onMergeApprove, onMergeReject, isMergeLoading, isRejecting, chatInput, onChatInputChange, sseEvents, }: FeatureDrawerTabsProps): import("react/jsx-runtime").JSX.Element;
34
37
  //# sourceMappingURL=feature-drawer-tabs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"feature-drawer-tabs.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/feature-drawer-tabs.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAClF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,2CAA2C,CAAC;AACzF,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,+CAA+C,CAAC;AACjG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAMxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAQ9E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uDAAuD,CAAC;AAiD3F,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,eAAe,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,gFAAgF;IAChF,MAAM,CAAC,EAAE,aAAa,CAAC;IAGvB,OAAO,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IAGvB,QAAQ,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAC;IAC1C,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IAC3E,aAAa,CAAC,EAAE,OAAO,CAAC;IAGxB,WAAW,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAC;IAGjD,SAAS,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IAC5E,cAAc,CAAC,EAAE,OAAO,CAAC;IAGzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C;AAwBD,wBAAgB,iBAAiB,CAAC,EAChC,WAAW,EACX,SAAS,EACT,UAAU,EACV,MAAM,EACN,OAAO,EACP,aAAa,EACb,WAAW,EACX,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,SAAS,EACT,cAAc,EACd,aAAa,EACb,cAAc,EACd,WAAW,EACX,SAAS,EACT,iBAAiB,GAClB,EAAE,sBAAsB,2CAmSxB"}
1
+ {"version":3,"file":"feature-drawer-tabs.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/feature-drawer-tabs.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAS9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAClF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,2CAA2C,CAAC;AACzF,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,+CAA+C,CAAC;AACjG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAMxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAQ9E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uDAAuD,CAAC;AAiD3F,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,eAAe,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,gFAAgF;IAChF,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,mFAAmF;IACnF,SAAS,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAGzC,OAAO,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IAGvB,QAAQ,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAC;IAC1C,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IAC3E,aAAa,CAAC,EAAE,OAAO,CAAC;IAGxB,WAAW,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAC;IAGjD,SAAS,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IAC5E,cAAc,CAAC,EAAE,OAAO,CAAC;IAGzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C;AAwBD,wBAAgB,iBAAiB,CAAC,EAChC,WAAW,EACX,SAAS,EACT,UAAU,EACV,MAAM,EACN,OAAO,EACP,aAAa,EACb,WAAW,EACX,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,SAAS,EACT,cAAc,EACd,aAAa,EACb,cAAc,EACd,WAAW,EACX,SAAS,EACT,iBAAiB,EACjB,SAAS,GACV,EAAE,sBAAsB,2CAwTxB"}
@@ -64,7 +64,7 @@ const TAB_FETCHERS = {
64
64
  activity: fetchActivity,
65
65
  plan: fetchPlan,
66
66
  };
67
- export function FeatureDrawerTabs({ featureNode, featureId, initialTab, urlTab, prdData, prdSelections, onPrdSelect, onPrdApprove, onPrdReject, isPrdLoading, techData, onTechApprove, onTechReject, isTechLoading, productData, mergeData, onMergeApprove, onMergeReject, isMergeLoading, isRejecting, chatInput, onChatInputChange, }) {
67
+ export function FeatureDrawerTabs({ featureNode, featureId, initialTab, urlTab, prdData, prdSelections, onPrdSelect, onPrdApprove, onPrdReject, isPrdLoading, techData, onTechApprove, onTechReject, isTechLoading, productData, mergeData, onMergeApprove, onMergeReject, isMergeLoading, isRejecting, chatInput, onChatInputChange, sseEvents, }) {
68
68
  const pathname = usePathname();
69
69
  const featureLogs = useFeatureLogs(featureId);
70
70
  const visibleTabs = useMemo(() => computeVisibleTabs(featureNode), [featureNode]);
@@ -159,6 +159,7 @@ export function FeatureDrawerTabs({ featureNode, featureId, initialTab, urlTab,
159
159
  }, [featureId]);
160
160
  // When initialTab changes (e.g. SSE updates lifecycle), switch to it if visible
161
161
  // and sync the URL with replaceState so the address bar reflects the new tab.
162
+ // Also trigger lazy fetch for the new tab so data is ready immediately.
162
163
  const prevInitialTabRef = useRef(initialTab);
163
164
  useEffect(() => {
164
165
  if (prevInitialTabRef.current !== initialTab &&
@@ -166,13 +167,17 @@ export function FeatureDrawerTabs({ featureNode, featureId, initialTab, urlTab,
166
167
  visibleTabs.includes(initialTab)) {
167
168
  prevInitialTabRef.current = initialTab;
168
169
  setActiveTab(initialTab);
170
+ // Trigger lazy fetch for the new tab if it's a lazy-loaded tab
171
+ if (initialTab === 'activity' || initialTab === 'plan') {
172
+ fetchTab(initialTab);
173
+ }
169
174
  // Sync URL to match the new tab
170
175
  const targetUrl = initialTab === 'overview' ? basePath : `${basePath}/${initialTab}`;
171
176
  if (targetUrl !== window.location.pathname) {
172
177
  window.history.replaceState(null, '', targetUrl);
173
178
  }
174
179
  }
175
- }, [initialTab, visibleTabs, basePath]);
180
+ }, [initialTab, visibleTabs, basePath, fetchTab]);
176
181
  // If the active tab becomes invisible (lifecycle changed), fall back to overview
177
182
  // and sync the URL accordingly.
178
183
  useEffect(() => {
@@ -183,19 +188,34 @@ export function FeatureDrawerTabs({ featureNode, featureId, initialTab, urlTab,
183
188
  }
184
189
  }
185
190
  }, [visibleTabs, activeTab, basePath]);
186
- // SSE refresh: re-fetch active lazy tab when featureNode reference changes
187
- const featureNodeRef = useRef(featureNode);
191
+ // SSE refresh: re-fetch lazy tab data when relevant SSE events arrive
192
+ // for this feature (e.g. PhaseCompleted, AgentStarted, AgentCompleted).
193
+ // Always refresh 'activity' data (even if not the active tab) so switching
194
+ // to the activity tab shows up-to-date timings without a loading flash.
188
195
  const activeTabRef = useRef(activeTab);
189
196
  activeTabRef.current = activeTab;
197
+ const sseProcessedRef = useRef(0);
190
198
  useEffect(() => {
191
- if (featureNodeRef.current !== featureNode) {
192
- featureNodeRef.current = featureNode;
193
- const current = activeTabRef.current;
194
- if (current === 'activity' || current === 'plan') {
195
- refreshTab(current);
196
- }
199
+ if (!sseEvents || sseEvents.length === 0)
200
+ return;
201
+ // Clamp cursor if events were pruned
202
+ if (sseProcessedRef.current > sseEvents.length) {
203
+ sseProcessedRef.current = 0;
204
+ }
205
+ if (sseEvents.length <= sseProcessedRef.current)
206
+ return;
207
+ const newEvents = sseEvents.slice(sseProcessedRef.current);
208
+ sseProcessedRef.current = sseEvents.length;
209
+ const hasRelevantEvent = newEvents.some((e) => e.featureId === featureId);
210
+ if (!hasRelevantEvent)
211
+ return;
212
+ // Always refresh activity data so it stays current for when the user switches tabs
213
+ refreshTab('activity');
214
+ const current = activeTabRef.current;
215
+ if (current === 'plan') {
216
+ refreshTab(current);
197
217
  }
198
- }, [featureNode, refreshTab]);
218
+ }, [sseEvents, featureId, refreshTab]);
199
219
  const handleTabChange = useCallback((value) => {
200
220
  const tab = value;
201
221
  isUserInteraction.current = true;
@@ -1 +1 @@
1
- {"version":3,"file":"overview-tab.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/overview-tab.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAMxE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,eAAe,CAAC;CACvB;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,gBAAgB,2CAwCrD"}
1
+ {"version":3,"file":"overview-tab.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/overview-tab.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAOxE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,eAAe,CAAC;CACvB;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,gBAAgB,2CAwCrD"}
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useState, useEffect, useRef } from 'react';
3
4
  import { AlertTriangle, ExternalLink, GitBranch, GitCommitHorizontal, Zap } from 'lucide-react';
4
5
  import { InlineAttachments } from '../../common/inline-attachments/index.js';
5
6
  import { PrStatus } from '../../../../../../packages/core/src/domain/generated/output.js';
@@ -10,6 +11,7 @@ import { CiStatusBadge } from '../../common/ci-status-badge/index.js';
10
11
  import { CometSpinner } from '../../ui/comet-spinner.js';
11
12
  import { featureNodeStateConfig, lifecycleDisplayLabels } from '../../common/feature-node/index.js';
12
13
  import { getAgentTypeIcon, agentTypeLabels, } from '../../common/feature-node/agent-type-icons.js';
14
+ import { formatDuration } from '../../../lib/format-duration.js';
13
15
  export function OverviewTab({ data }) {
14
16
  const isCompleted = data.lifecycle === 'maintain';
15
17
  return (_jsxs(_Fragment, { children: [_jsxs("div", { "data-testid": "feature-drawer-status", className: "flex flex-col gap-3 p-4", children: [_jsx("div", { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: lifecycleDisplayLabels[data.lifecycle] }), _jsx(FeatureStateBadge, { data: data }), !isCompleted && data.progress > 0 ? (_jsxs("div", { "data-testid": "feature-drawer-progress", className: "flex flex-col gap-1", children: [_jsxs("div", { className: "text-muted-foreground flex items-center justify-between text-xs", children: [_jsx("span", { children: "Progress" }), _jsxs("span", { children: [data.progress, "%"] })] }), _jsx("div", { className: "bg-muted h-2 w-full overflow-hidden rounded-full", children: _jsx("div", { className: cn('h-full rounded-full transition-all', featureNodeStateConfig[data.state].progressClass), style: { width: `${data.progress}%` } }) })] })) : null, isCompleted && data.pr ? _jsx(FeaturePrInfo, { pr: data.pr }) : null] }), _jsx(FeatureInfo, { data: data }), !isCompleted && data.pr ? (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsx("div", { className: "p-4", children: _jsx(FeaturePrInfo, { pr: data.pr }) })] })) : null, _jsx(FeatureDetails, { data: data })] }));
@@ -63,11 +65,42 @@ function FeaturePrInfo({ pr }) {
63
65
  return (_jsx("div", { "data-testid": "feature-drawer-pr", children: _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("a", { href: pr.url, target: "_blank", rel: "noopener noreferrer", className: "text-primary flex items-center gap-1.5 text-sm font-semibold underline underline-offset-2", children: ["PR #", pr.number, _jsx(ExternalLink, { className: "h-3.5 w-3.5" })] }), _jsx(Badge, { className: prStatusStyles[pr.status], children: pr.status })] }), pr.ciStatus ? (_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "CI Status" }), _jsx(CiStatusBadge, { status: pr.ciStatus })] })) : null, pr.mergeable === false ? (_jsxs("div", { "data-testid": "pr-merge-conflict", className: "flex items-center justify-between", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "Merge Status" }), _jsxs(Badge, { className: "border-transparent bg-orange-50 text-orange-700 hover:bg-orange-50", children: [_jsx(AlertTriangle, { className: "mr-1 h-3.5 w-3.5" }), "Conflicts"] })] })) : null, pr.commitHash ? (_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "Commit" }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(GitCommitHorizontal, { className: "text-muted-foreground h-3.5 w-3.5" }), _jsx("code", { className: "bg-muted text-foreground rounded-md px-1.5 py-0.5 font-mono text-[11px]", children: pr.commitHash.slice(0, 7) })] })] })) : null] }) }));
64
66
  }
65
67
  // ── Details section ──────────────────────────────────────────────────
68
+ /** Hook that returns a live-updating elapsed time string for running features. */
69
+ function useElapsedTime(startedAt) {
70
+ const [elapsed, setElapsed] = useState(null);
71
+ const intervalRef = useRef(null);
72
+ useEffect(() => {
73
+ if (!startedAt) {
74
+ setElapsed(null);
75
+ return;
76
+ }
77
+ // Compute immediately
78
+ setElapsed(formatDuration(Math.max(0, Date.now() - startedAt)));
79
+ // Tick every second
80
+ intervalRef.current = setInterval(() => {
81
+ setElapsed(formatDuration(Math.max(0, Date.now() - startedAt)));
82
+ }, 1000);
83
+ return () => {
84
+ if (intervalRef.current) {
85
+ clearInterval(intervalRef.current);
86
+ intervalRef.current = null;
87
+ }
88
+ };
89
+ }, [startedAt]);
90
+ return elapsed;
91
+ }
66
92
  function FeatureDetails({ data }) {
67
- const hasAnyDetail = data.fastMode ?? data.agentType ?? data.runtime ?? data.blockedBy ?? data.errorMessage;
93
+ const isRunning = data.state === 'running' || data.state === 'action-required';
94
+ const elapsedTime = useElapsedTime(isRunning ? data.startedAt : undefined);
95
+ const hasAnyDetail = data.fastMode ??
96
+ data.agentType ??
97
+ data.runtime ??
98
+ elapsedTime ??
99
+ data.blockedBy ??
100
+ data.errorMessage;
68
101
  if (!hasAnyDetail)
69
102
  return null;
70
- return (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { "data-testid": "feature-drawer-details", className: "flex flex-col gap-3 p-4", children: [data.fastMode ? (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "Mode" }), _jsxs("span", { className: "flex items-center gap-2 text-sm", children: [_jsx(Zap, { className: "h-4 w-4 shrink-0 text-amber-500" }), "Fast Mode"] })] })) : null, data.agentType ? _jsx(AgentDetailRow, { agentType: data.agentType }) : null, data.runtime ? _jsx(DetailRow, { label: "Runtime", value: data.runtime }) : null, data.blockedBy ? _jsx(DetailRow, { label: "Blocked by", value: data.blockedBy }) : null, data.errorMessage ? _jsx(DetailRow, { label: "Error", value: data.errorMessage }) : null] })] }));
103
+ return (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { "data-testid": "feature-drawer-details", className: "flex flex-col gap-3 p-4", children: [data.fastMode ? (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "Mode" }), _jsxs("span", { className: "flex items-center gap-2 text-sm", children: [_jsx(Zap, { className: "h-4 w-4 shrink-0 text-amber-500" }), "Fast Mode"] })] })) : null, data.agentType ? _jsx(AgentDetailRow, { agentType: data.agentType }) : null, data.runtime ? _jsx(DetailRow, { label: "Runtime", value: data.runtime }) : null, !data.runtime && elapsedTime ? (_jsx(DetailRow, { label: "Running for", value: elapsedTime })) : null, data.blockedBy ? _jsx(DetailRow, { label: "Blocked by", value: data.blockedBy }) : null, data.errorMessage ? _jsx(DetailRow, { label: "Error", value: data.errorMessage }) : null] })] }));
71
104
  }
72
105
  function AgentDetailRow({ agentType }) {
73
106
  const Icon = getAgentTypeIcon(agentType);
@@ -1 +1 @@
1
- {"version":3,"file":"version-badge.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/version-badge/version-badge.tsx"],"names":[],"mappings":"AAKA,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,2CAyDnB"}
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"}
@@ -2,13 +2,17 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../ui/tooltip.js';
4
4
  import { useNpmVersionCheck } from '../../../hooks/use-npm-version-check.js';
5
+ import { useCliUpgrade } from '../../../hooks/use-cli-upgrade.js';
5
6
  export function VersionBadge({ version, branch, commitHash, isDev = false, packageName = '@shepai/cli', description, instancePath, }) {
6
7
  const shortHash = commitHash?.slice(0, 7);
7
8
  const { latest, updateAvailable } = useNpmVersionCheck(version);
9
+ const { status: upgradeStatus, startUpgrade } = useCliUpgrade();
8
10
  // In dev mode show "1.92.2-dev", in production show "v1.92.2"
9
11
  const displayVersion = isDev ? `${version}-dev` : `v${version}`;
10
- const npmUrl = `https://www.npmjs.com/package/${packageName}`;
11
- 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 ? (_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] }), updateAvailable ? (_jsx("div", { className: "border-t border-white/10 pt-1.5", children: _jsxs("a", { href: npmUrl, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-1 text-[10px] font-medium text-emerald-400 transition-colors hover:text-emerald-300", "data-testid": "upgrade-link", children: ["Upgrade to v", latest, " \u2192"] }) })) : null] })] }) }));
12
+ const isUpgrading = upgradeStatus === 'upgrading';
13
+ const didUpgrade = upgradeStatus === 'upgraded';
14
+ 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] })] }) }));
12
16
  }
13
17
  function Row({ label, value, highlight, mono, }) {
14
18
  return (_jsxs("div", { className: "flex items-center justify-between gap-4 text-[10px]", children: [_jsx("span", { className: "opacity-60", children: label }), _jsx("span", { className: [
@@ -11,9 +11,9 @@ export declare const Development: Story;
11
11
  export declare const LongBranch: Story;
12
12
  /** Minimal info — no optional fields. */
13
13
  export declare const Minimal: Story;
14
- /** Update available — green dot appears, tooltip shows "Upgrade to v2.0.0" link. */
14
+ /** Update available — green dot appears, tooltip shows "Upgrade to v2.0.0" button. */
15
15
  export declare const UpdateAvailable: Story;
16
- /** Update available in dev mode — green dot + upgrade link alongside dev info. */
16
+ /** Update available in dev mode — green dot + upgrade button alongside dev info. */
17
17
  export declare const UpdateAvailableDev: Story;
18
18
  /** Fetch error — no update info shown, badge behaves normally. */
19
19
  export declare const FetchError: Story;