hdsp-jupyter-extension 2.0.7__py3-none-any.whl → 2.0.8__py3-none-any.whl

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 (78) hide show
  1. agent_server/core/embedding_service.py +67 -46
  2. agent_server/core/rag_manager.py +31 -17
  3. agent_server/core/retriever.py +13 -8
  4. agent_server/core/vllm_embedding_service.py +243 -0
  5. agent_server/langchain/agent.py +8 -0
  6. agent_server/langchain/custom_middleware.py +58 -31
  7. agent_server/langchain/hitl_config.py +6 -1
  8. agent_server/langchain/logging_utils.py +53 -14
  9. agent_server/langchain/prompts.py +47 -16
  10. agent_server/langchain/tools/__init__.py +13 -0
  11. agent_server/langchain/tools/file_tools.py +285 -7
  12. agent_server/langchain/tools/file_utils.py +334 -0
  13. agent_server/langchain/tools/lsp_tools.py +264 -0
  14. agent_server/main.py +7 -0
  15. agent_server/routers/langchain_agent.py +115 -19
  16. agent_server/routers/rag.py +8 -3
  17. hdsp_agent_core/models/rag.py +15 -1
  18. hdsp_agent_core/services/rag_service.py +6 -1
  19. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
  20. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +3 -2
  21. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js +160 -3
  22. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js.map +1 -0
  23. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.29cf4312af19e86f82af.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js +1759 -221
  24. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +1 -0
  25. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.61343eb4cf0577e74b50.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js +14 -12
  26. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +1 -0
  27. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +2 -209
  28. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +1 -0
  29. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +209 -2
  30. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +1 -0
  31. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +212 -3
  32. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +1 -0
  33. {hdsp_jupyter_extension-2.0.7.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/METADATA +1 -1
  34. {hdsp_jupyter_extension-2.0.7.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/RECORD +66 -63
  35. jupyter_ext/__init__.py +18 -0
  36. jupyter_ext/_version.py +1 -1
  37. jupyter_ext/handlers.py +176 -1
  38. jupyter_ext/labextension/build_log.json +1 -1
  39. jupyter_ext/labextension/package.json +3 -2
  40. jupyter_ext/labextension/static/{frontend_styles_index_js.4770ec0fb2d173b6deb4.js → frontend_styles_index_js.8740a527757068814573.js} +160 -3
  41. jupyter_ext/labextension/static/frontend_styles_index_js.8740a527757068814573.js.map +1 -0
  42. jupyter_ext/labextension/static/{lib_index_js.29cf4312af19e86f82af.js → lib_index_js.e4ff4b5779b5e049f84c.js} +1759 -221
  43. jupyter_ext/labextension/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +1 -0
  44. jupyter_ext/labextension/static/{remoteEntry.61343eb4cf0577e74b50.js → remoteEntry.020cdb0b864cfaa4e41e.js} +14 -12
  45. jupyter_ext/labextension/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +1 -0
  46. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js → jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +2 -209
  47. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +1 -0
  48. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js → jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +209 -2
  49. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +1 -0
  50. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js → jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +212 -3
  51. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +1 -0
  52. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js.map +0 -1
  53. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.29cf4312af19e86f82af.js.map +0 -1
  54. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.61343eb4cf0577e74b50.js.map +0 -1
  55. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +0 -1
  56. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +0 -1
  57. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +0 -1
  58. jupyter_ext/labextension/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js.map +0 -1
  59. jupyter_ext/labextension/static/lib_index_js.29cf4312af19e86f82af.js.map +0 -1
  60. jupyter_ext/labextension/static/remoteEntry.61343eb4cf0577e74b50.js.map +0 -1
  61. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +0 -1
  62. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +0 -1
  63. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +0 -1
  64. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
  65. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
  66. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js +0 -0
  67. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js.map +0 -0
  68. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js +0 -0
  69. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js.map +0 -0
  70. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
  71. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js +0 -0
  72. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js.map +0 -0
  73. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +0 -0
  74. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js.map +0 -0
  75. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
  76. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +0 -0
  77. {hdsp_jupyter_extension-2.0.7.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/WHEEL +0 -0
  78. {hdsp_jupyter_extension-2.0.7.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib_index_js.e4ff4b5779b5e049f84c.js","mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AACA;AACA;AACA;AACyG;AACxC;AACK;AACtB;AAC6D;AAC3C;AACD;AACA;AACL;AAC5D;AAC0D;AAC1D;AACA,wBAAwB,8DAAO;AAC/B;AACA,YAAY,mDAAa;AACzB,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB,iDAAU,IAAI,6CAA6C;AAC7E;AACA,oCAAoC,+CAAQ;AAC5C,8BAA8B,+CAAQ;AACtC,sCAAsC,+CAAQ;AAC9C,0CAA0C,+CAAQ;AAClD,wDAAwD,+CAAQ;AAChE,gDAAgD,+CAAQ;AACxD,4CAA4C,+CAAQ;AACpD,sCAAsC,+CAAQ;AAC9C;AACA,gDAAgD,+CAAQ;AACxD,8DAA8D,+CAAQ;AACtE,gDAAgD,+CAAQ;AACxD;AACA,sCAAsC,+CAAQ;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL,oDAAoD,+CAAQ;AAC5D;AACA,oDAAoD,+CAAQ;AAC5D;AACA,oDAAoD,+CAAQ;AAC5D,4EAA4E,+CAAQ;AACpF;AACA,8DAA8D,+CAAQ;AACtE,0DAA0D,+CAAQ;AAClE;AACA,0CAA0C,+CAAQ;AAClD,8CAA8C,+CAAQ;AACtD;AACA,8BAA8B,+CAAQ;AACtC,gDAAgD,+CAAQ;AACxD;AACA,8CAA8C,+CAAQ;AACtD;AACA,kDAAkD,+CAAQ;AAC1D,sEAAsE,+CAAQ;AAC9E,kCAAkC,6CAAM;AACxC,+BAA+B,6CAAM;AACrC,gCAAgC,6CAAM;AACtC,mCAAmC,6CAAM;AACzC;AACA;AACA;AACA,8BAA8B,+DAAa;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB,iEAAe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC;AAClC;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,oBAAoB;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC,KAAK;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,8CAA8C;AACzF;AACA;AACA;AACA,uBAAuB,cAAc,GAAG,wCAAwC;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL,2BAA2B,6CAAM;AACjC,gCAAgC,6CAAM;AACtC,6BAA6B,6CAAM;AACnC,6BAA6B,6CAAM;AACnC,gCAAgC,6CAAM;AACtC,4BAA4B,6CAAM;AAClC;AACA,wBAAwB,WAAW,GAAG,uCAAuC;AAC7E,2BAA2B,KAAK,GAAG,OAAO;AAC1C;AACA;AACA,IAAI,0DAAmB;AACvB;AACA;AACA,SAAS;AACT;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,IAAI,gDAAS;AACb;AACA,KAAK;AACL;AACA;AACA;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,iCAAiC,kDAAW;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,GAAG,wEAAyB;AACrD,iCAAiC,0EAAiB;AAClD;AACA,wCAAwC,WAAW;AACnD;AACA;AACA;AACA;AACA,sBAAsB,6CAA6C;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,qEAAY,MAAM,4EAAmB;AACvF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,mCAAmC;AAC9E;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,yDAAyD;AACvF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,uBAAuB,qEAAY;AACnC;AACA;AACA,kCAAkC,4EAAmB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,qBAAqB,QAAQ,gLAA+B;AAChF;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,qBAAqB,QAAQ,gLAA+B;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,yCAAyC;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,eAAe;AACvD;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,QAAQ,GAAG,WAAW,UAAU,WAAW;AACvF;AACA;AACA;AACA,wCAAwC,2BAA2B;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,wCAAwC;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,8BAA8B;AACtD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,SAAS;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yCAAyC,SAAS;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,QAAQ;AACnC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,uEAAc;AACzD;AACA,QAAQ,sEAAa;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,sBAAsB;AAClE,qBAAqB;AACrB;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,mCAAmC,iBAAiB,UAAU,kBAAkB;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,sBAAsB;AAClE,qBAAqB;AACrB;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,gDAAS;AACb;AACA;AACA,qDAAqD,kBAAkB;AACvE;AACA;AACA;AACA,qDAAqD,oBAAoB;AACzE;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,qCAAqC,wDAAwD;AAC7F;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,qEAAY,MAAM,4EAAmB;AACjE;AACA;AACA;AACA,0BAA0B,uEAAc;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oDAAoD,sFAAsF;AAC1I;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA,4BAA4B;AAC5B;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA,4BAA4B;AAC5B;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,QAAQ;AAC1C;AACA;AACA;AACA;AACA,6DAA6D,QAAQ;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,mBAAmB;AACjE;AACA;AACA;AACA;AACA;AACA;AACA,gEAAgE,QAAQ;AACxE;AACA,+DAA+D,SAAS;AACxE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA,+CAA+C,mBAAmB;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,QAAQ,MAAM,YAAY;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8FAA8F,QAAQ;AACtG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA,mGAAmG,kBAAkB;AACrH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,QAAQ;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB;AACpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA,6DAA6D,kCAAkC;AAC/F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,WAAW,GAAG,wCAAwC;AACtF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,oBAAoB;AACtE;AACA,mEAAmE,GAAG;AACtE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uCAAuC,qBAAqB,UAAU,mBAAmB;AACzF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,UAAU;AAC9B;AACA,yCAAyC,gBAAgB,iCAAiC,qBAAqB,kBAAkB,wBAAwB;AACzJ,gBAAgB,oBAAoB,EAAE;AACtC;AACA;AACA;AACA,aAAa;AACb;AACA;AACA,wCAAwC;AACxC,wCAAwC,gBAAgB,iBAAiB,iBAAiB;AAC1F;AACA;AACA,+BAA+B,gBAAgB,gBAAgB;AAC/D;AACA;AACA;AACA,mEAAmE;AACnE,YAAY;AACZ;AACA,mCAAmC,WAAW,0BAA0B;AACxE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,QAAQ;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wDAAwD,qBAAqB;AAC7E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,QAAQ;AACnC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,gBAAgB,EAAE,OAAO,EAAE,KAAK;AAC5D;AACA,SAAS;AACT;AACA;AACA;AACA,4EAA4E,0BAA0B;AACtG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,2BAA2B,QAAQ,KAAK;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAiE;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB;AACrB;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gDAAgD;AAChD,0BAA0B,iDAAiD;AAC3E;AACA;AACA;AACA;AACA,oBAAoB,qBAAqB,QAAQ,gLAA+B;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,OAAO,GAAG,iBAAiB;AACzE;AACA;AACA;AACA,aAAa;AACb;AACA,yBAAyB,mDAAmD,gBAAgB;AAC5F;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uEAAuE,eAAe;AACtF;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA,yBAAyB,2CAA2C,YAAY;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oDAAoD,WAAW;AAC/D,sBAAsB,MAAM,EAAE,UAAU,EAAE,4BAA4B;AACtE,SAAS;AACT;AACA;AACA;AACA,wBAAwB;AACxB;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA,uEAAuE,+BAA+B;AACtG;AACA,qBAAqB;AACrB;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB,+CAA+C,YAAY;AAChF;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,yBAAyB;AACzC;AACA;AACA;AACA;AACA,8CAA8C,oBAAoB;AAClE;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA,kDAAkD,oBAAoB;AACtE;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA,+CAA+C,iCAAiC;AAChF;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB;AACxB;AACA,aAAa;AACb;AACA,aAAa;AACb;AACA;AACA,8EAA8E,qEAAY,MAAM,4EAAmB;AACnH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA,aAAa;AACb;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,QAAQ;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,WAAW;AAC3B;AACA;AACA,yCAAyC;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sGAAsG;AACtG;AACA,sEAAsE,wBAAwB;AAC9F;AACA;AACA,0DAA0D,aAAa;AACvE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,UAAU;AACpD,0CAA0C,UAAU;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+CAA+C,iBAAiB;AAChE;AACA;AACA;AACA,+CAA+C,YAAY;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC,kBAAkB;AACtD;AACA;AACA;AACA;AACA,yGAAyG;AACzG;AACA,qDAAqD,MAAM;AAC3D;AACA;AACA;AACA;AACA,qDAAqD,MAAM,mBAAmB,aAAa;AAC3F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,UAAU;AACpD,0CAA0C,UAAU;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+CAA+C,iBAAiB;AAChE;AACA;AACA;AACA,+CAA+C,YAAY;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+CAA+C,cAAc,gBAAgB,oBAAoB,GAAG;AACpG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sCAAsC,SAAS;AAC/C;AACA;AACA;AACA;AACA;AACA,wBAAwB;AACxB;AACA,aAAa;AACb;AACA,aAAa;AACb;AACA;AACA,8EAA8E,qEAAY,MAAM,4EAAmB;AACnH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA,aAAa;AACb;AACA,aAAa;AACb;AACA;AACA;AACA,kCAAkC,QAAQ;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA,uCAAuC,QAAQ;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wDAAwD;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uCAAuC,aAAa;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,6DAA6D;AAC7E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,0DAAmB,UAAU,yCAAyC;AACtF,YAAY,0DAAmB,UAAU,wCAAwC;AACjF,gBAAgB,0DAAmB,UAAU,uCAAuC;AACpF,oBAAoB,0DAAmB,UAAU,uEAAuE;AACxH,wBAAwB,0DAAmB,WAAW,+EAA+E;AACrI,wBAAwB,0DAAmB,WAAW,sGAAsG;AAC5J;AACA,gBAAgB,0DAAmB,WAAW,yCAAyC;AACvF,YAAY,0DAAmB,UAAU,mEAAmE,aAAa,GAAG;AAC5H,4BAA4B,0DAAmB,UAAU,yCAAyC;AAClG,iDAAiD,0DAAmB,UAAU,2FAA2F;AACzK,oBAAoB,0DAAmB,WAAW,sIAAsI;AACxL,8CAA8C,0DAAmB,UAAU,yFAAyF;AACpK,oBAAoB,0DAAmB,WAAW,4LAA4L;AAC9O,gBAAgB,0DAAmB,WAAW,6CAA6C;AAC3F;AACA;AACA;AACA;AACA,qBAAqB,0DAAmB,UAAU,sCAAsC;AACxF,gBAAgB,0DAAmB,UAAU,6CAA6C;AAC1F,oBAAoB,0DAAmB;AACvC,oBAAoB,0DAAmB,WAAW,+CAA+C;AACjG;AACA;AACA;AACA,gBAAgB,0DAAmB,UAAU,8CAA8C;AAC3F,oBAAoB,0DAAmB,UAAU,wDAAwD,UAAU,gBAAgB,MAAM;AACzI,gBAAgB,0DAAmB,UAAU,uCAAuC;AACpF;AACA,4BAA4B,0DAAmB,UAAU,qFAAqF,WAAW;AACzJ;AACA;AACA,oDAAoD,sCAAsC;AAC1F;AACA,2BAA2B;AAC3B,wBAAwB,0DAAmB,UAAU,gDAAgD;AACrG,2DAA2D,0DAAmB,UAAU,4CAA4C;AACpI,gCAAgC,0DAAmB,WAAW,sIAAsI;AACpM,wDAAwD,0DAAmB,UAAU,4CAA4C;AACjI,gCAAgC,0DAAmB,WAAW,4LAA4L;AAC1P,wDAAwD,0DAAmB,UAAU,8CAA8C;AACnI,wDAAwD,0DAAmB;AAC3E,wBAAwB,0DAAmB,UAAU,8CAA8C;AACnG,4BAA4B,0DAAmB,WAAW,2CAA2C,uEAAuE,GAAG;AAC/K,4BAA4B,0DAAmB,UAAU,4CAA4C;AACrG;AACA,iDAAiD,0DAAmB,WAAW,kDAAkD;AACjI,iBAAiB;AACjB,uBAAuB,0DAAmB,UAAU,mEAAmE,qCAAqC,GAAG;AAC/J,uCAAuC,0DAAmB,UAAU,iGAAiG,QAAQ,6EAAoB,wBAAwB;AACzN,iCAAiC,0DAAmB,QAAQ,8CAA8C;AAC1G,gBAAgB,0DAAmB,UAAU,8CAA8C;AAC3F,oBAAoB,0DAAmB;AACvC;AACA;AACA,oBAAoB,0DAAmB;AACvC;AACA;AACA,6CAA6C,0DAAmB;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,qBAAqB;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,0DAAmB,UAAU,6BAA6B;AACtE,yBAAyB,0DAAmB,CAAC,yDAAa,IAAI,wGAAwG;AACtK,QAAQ,0DAAmB,UAAU,8BAA8B;AACnE,YAAY,0DAAmB,UAAU,8DAA8D,QAAQ,mDAAa,IAAI;AAChI,YAAY,0DAAmB,UAAU,sCAAsC;AAC/E,gBAAgB,0DAAmB,aAAa,kGAAkG;AAClJ,oBAAoB,0DAAmB,UAAU,0JAA0J;AAC3M,wBAAwB,0DAAmB,eAAe,wBAAwB;AAClF,wBAAwB,0DAAmB,WAAW,qFAAqF;AAC3I,wBAAwB,0DAAmB,WAAW,wCAAwC;AAC9F,wBAAwB,0DAAmB,WAAW,wCAAwC;AAC9F,gBAAgB,0DAAmB,aAAa,yGAAyG;AACzJ,oBAAoB,0DAAmB,UAAU,0JAA0J;AAC3M,wBAAwB,0DAAmB,WAAW,sCAAsC;AAC5F,wBAAwB,0DAAmB,WAAW,qCAAqC;AAC3F,wBAAwB,0DAAmB,WAAW,wCAAwC;AAC9F,wBAAwB,0DAAmB,WAAW,sCAAsC;AAC5F,wBAAwB,0DAAmB,WAAW,wCAAwC;AAC9F,wBAAwB,0DAAmB,WAAW,uCAAuC;AAC7F,wBAAwB,0DAAmB,WAAW,sCAAsC;AAC5F,wBAAwB,0DAAmB,WAAW,qCAAqC;AAC3F,wBAAwB,0DAAmB,WAAW,wCAAwC;AAC9F,QAAQ,0DAAmB,UAAU,gCAAgC;AACrE,qCAAqC,0DAAmB,UAAU,mCAAmC;AACrG,gBAAgB,0DAAmB;AACnC,gBAAgB,0DAAmB,QAAQ,kCAAkC;AAC7E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,0DAAmB,UAAU;AACzD;AACA,mEAAmE,SAAS,EAAE,sDAAsD,GAAG;AACvI,yCAAyC,0DAAmB,UAAU,sCAAsC;AAC5G,4BAA4B,0DAAmB,WAAW,oCAAoC;AAC9F,4BAA4B,0DAAmB,WAAW,oCAAoC;AAC9F,wBAAwB,0DAAmB,UAAU,sCAAsC,kDAAkD,EAAE,uDAAuD,GAAG,sDAAsD,0DAAmB,UAAU,uCAAuC,iFAAiF,GAAG;AACvZ,uEAAuE,0DAAmB,UAAU,6CAA6C;AACjJ,4BAA4B,0DAAmB,UAAU,wCAAwC;AACjG,gCAAgC,0DAAmB,UAAU,6CAA6C;AAC1G;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,UAAU,gBAAgB,UAAU;AAC7F,qFAAqF,KAAK;AAC1F,qFAAqF,KAAK;AAC1F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oFAAoF,cAAc,IAAI,aAAa;AACnH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mDAAmD,6EAAoB,YAAY,SAAS,IAAI,QAAQ;AACxG;AACA;AACA,gLAAgL,SAAS;AACzL;AACA;AACA;AACA,mLAAmL,SAAS;AAC5L;AACA;AACA,sFAAsF,WAAW;AACjG,qCAAqC;AACrC,4CAA4C,0DAAmB,UAAU,6CAA6C,kBAAkB,6BAA6B,sBAAsB;AAC3L;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C;AAC3C,iCAAiC;AACjC;AACA,wBAAwB,0DAAmB,UAAU,6CAA6C,kBAAkB,6BAA6B,QAAQ,6EAAoB,iBAAiB;AAC9L;AACA,wBAAwB,0DAAmB,UAAU,SAAS,0BAA0B;AACxF;AACA;AACA;AACA,4BAA4B,0DAAmB,UAAU,6EAA6E;AACtI;AACA,aAAa;AACb,iEAAiE,0DAAmB,UAAU,kDAAkD;AAChJ,gBAAgB,0DAAmB,UAAU,4CAA4C;AACzF,oBAAoB,0DAAmB,UAAU,uEAAuE;AACxH,wBAAwB,0DAAmB,WAAW,qPAAqP;AAC3S,oBAAoB,0DAAmB;AACvC,gBAAgB,0DAAmB,UAAU,6CAA6C;AAC1F;AACA;AACA,gBAAgB,0DAAmB,UAAU,6CAA6C;AAC1F,oBAAoB,0DAAmB,aAAa;AACpD;AACA,+DAA+D,iBAAiB;AAChF;AACA;AACA;AACA;AACA;AACA,2BAA2B;AAC3B,oBAAoB,0DAAmB,aAAa,wGAAwG;AAC5J,4CAA4C,0DAAmB,UAAU,kCAAkC;AAC3G,gBAAgB,0DAAmB,UAAU,yCAAyC;AACtF,oBAAoB,0DAAmB,UAAU,uEAAuE;AACxH,wBAAwB,0DAAmB,WAAW,0IAA0I;AAChM,wBAAwB,0DAAmB,WAAW,iIAAiI;AACvL,oBAAoB,0DAAmB;AACvC;AACA;AACA;AACA,gBAAgB,0DAAmB,UAAU,uCAAuC,wCAAwC,0DAAmB,UAAU,iDAAiD;AAC1M,oBAAoB,0DAAmB,UAAU,qCAAqC;AACtF,wBAAwB,0DAAmB,UAAU,uEAAuE;AAC5H,4BAA4B,0DAAmB,WAAW,8MAA8M;AACxQ,wBAAwB,0DAAmB,WAAW,qCAAqC;AAC3F,oBAAoB,0DAAmB,aAAa,kFAAkF,UAAU,YAAY;AAC5J,gBAAgB,0DAAmB,aAAa,mIAAmI;AACnL,YAAY,0DAAmB,UAAU,qBAAqB;AAC9D,qDAAqD,0DAAmB,UAAU,oCAAoC;AACtH,YAAY,0DAAmB,UAAU,8FAA8F;AACvI,gBAAgB,0DAAmB,UAAU,yCAAyC;AACtF,oBAAoB,0DAAmB,UAAU,wCAAwC,4DAA4D,0EAA0E;AAC/N,wBAAwB,0DAAmB,WAAW,oBAAoB;AAC1E;AACA;AACA;AACA;AACA,oCAAoC,0DAAmB,CAAC,uDAAc;AACtE,gCAAgC,0DAAmB,UAAU,4CAA4C;AACzG,gCAAgC,0DAAmB,WAAW,4CAA4C;AAC1G;AACA;AACA,oCAAoC,0DAAmB,CAAC,uDAAc;AACtE,gCAAgC,0DAAmB,UAAU,4CAA4C;AACzG,gCAAgC,0DAAmB,WAAW,4CAA4C;AAC1G;AACA;AACA,oCAAoC,0DAAmB,WAAW,4CAA4C;AAC9G;AACA,qBAAqB;AACrB,gBAAgB,0DAAmB,WAAW,6CAA6C;AAC3F;AACA;AACA;AACA,6CAA6C,0DAAmB,UAAU,oFAAoF,oEAAoE,GAAG;AACrO,gBAAgB,0DAAmB,UAAU,qCAAqC;AAClF,oBAAoB,0DAAmB,UAAU,2DAA2D;AAC5G,oBAAoB,0DAAmB,WAAW,kCAAkC;AACpF,sDAAsD,0DAAmB,WAAW,6DAA6D;AACjJ,+BAA+B,0DAAmB,UAAU,qCAAqC,8BAA8B,0DAAmB,UAAU,iEAAiE,YAAY,GAAG;AAC5O,gBAAgB,0DAAmB,UAAU,2CAA2C;AACxF,oDAAoD,0DAAmB,UAAU,uEAAuE;AACxJ,wBAAwB,0DAAmB,WAAW,sIAAsI;AAC5L,qDAAqD,0DAAmB,UAAU,yCAAyC;AAC3H,iDAAiD,0DAAmB,WAAW,wCAAwC;AACvH,gBAAgB,0DAAmB,WAAW,sCAAsC,mEAAmE,GAAG;AAC1J,0CAA0C,0DAAmB,UAAU,yFAAyF,oEAAoE,GAAG;AACvO,YAAY,0DAAmB,UAAU,qCAAqC;AAC9E,gBAAgB,0DAAmB,WAAW,kCAAkC;AAChF,kDAAkD,0DAAmB,WAAW,6DAA6D;AAC7I,QAAQ,0DAAmB,UAAU,uCAAuC;AAC5E,YAAY,0DAAmB,UAAU,qCAAqC;AAC9E,gBAAgB,0DAAmB,eAAe,6BAA6B,0DAA0D,EAAE,wDAAwD;AACnM;AACA;AACA;AACA;AACA;AACA,oGAAoG;AACpG,gBAAgB,0DAAmB,aAAa,qNAAqN;AACrQ,YAAY,0DAAmB,UAAU,gCAAgC;AACzE,gBAAgB,0DAAmB,UAAU,6CAA6C;AAC1F,oBAAoB,0DAAmB,aAAa,mCAAmC,2DAA2D,kCAAkC,8EAA8E,YAAY;AAC9Q,wBAAwB,0DAAmB,UAAU,wGAAwG;AAC7J;AACA,wBAAwB,0DAAmB,WAAW,kNAAkN;AACxQ;AACA,wBAAwB,0DAAmB,WAAW,siBAAsiB;AAC5lB;AACA,wBAAwB,0DAAmB,WAAW,uNAAuN;AAC7Q,wBAAwB,0DAAmB,WAAW,kCAAkC;AACxF,wBAAwB,0DAAmB,UAAU,2GAA2G;AAChK,4BAA4B,0DAAmB,WAAW,yHAAyH;AACnL,yCAAyC,0DAAmB,UAAU,qCAAqC;AAC3G,wBAAwB,0DAAmB,aAAa,mCAAmC,6DAA6D,oBAAoB,sBAAsB,+BAA+B;AACjO,4BAA4B,0DAAmB,UAAU,uEAAuE;AAChI,gCAAgC,0DAAmB,WAAW,kNAAkN;AAChR,4BAA4B,0DAAmB;AAC/C,4BAA4B,0DAAmB,WAAW,qCAAqC;AAC/F,wBAAwB,0DAAmB,aAAa,mCAAmC,8DAA8D,oBAAoB,uBAAuB,+BAA+B;AACnO,4BAA4B,0DAAmB,UAAU,uEAAuE;AAChI,gCAAgC,0DAAmB,WAAW,0HAA0H;AACxL,4BAA4B,0DAAmB;AAC/C,4BAA4B,0DAAmB,WAAW,qCAAqC;AAC/F,wBAAwB,0DAAmB,aAAa,mCAAmC,iEAAiE,oBAAoB,0BAA0B,+BAA+B;AACzO,4BAA4B,0DAAmB,UAAU,uEAAuE;AAChI,gCAAgC,0DAAmB,WAAW,uNAAuN;AACrR,4BAA4B,0DAAmB;AAC/C,4BAA4B,0DAAmB,WAAW,qCAAqC;AAC/F,gBAAgB,0DAAmB,UAAU,kCAAkC;AAC/E,oBAAoB,0DAAmB,WAAW,iCAAiC;AACnF,kCAAkC,0DAAmB,CAAC,qEAAmB,IAAI,uLAAuL;AACpQ,CAAC;AACD;AACA;AACA;AACA;AACO,+BAA+B,kEAAW;AACjD;AACA;AACA,4BAA4B,sDAAe;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,0DAAmB,cAAc,iIAAiI;AAClL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,+CAA+C,WAAW;AAC1D;AACA;;AAEA;AACA,YAAY,WAAW;AACvB,cAAc,gBAAgB;AAC9B,kBAAkB,iBAAiB;AACnC,kBAAkB,aAAa;;AAE/B;;AAEA;AACA;AACA,kCAAkC,WAAW,OAAO,QAAQ;AAC5D;AACA,EAAE;AACF;AACA;AACA;AACA;AACA;AACA;AACA,EAAE;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,WAAW,MAAM,gCAAgC;AACzF,iBAAiB;AACjB;AACA;AACA,SAAS;AACT;AACA;;AAEA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA,SAAS;AACT;AACA;;;;;;;;;;;;;;;;;AC5jHA;AACA;AACA;AACA;AACA;AAC0B;AACnB,+BAA+B,gDAAgD;AACtF,YAAY,0DAAmB,UAAU,oCAAoC;AAC7E,QAAQ,0DAAmB,UAAU,wDAAwD;AAC7F,QAAQ,0DAAmB,UAAU,qCAAqC;AAC1E,YAAY,0DAAmB;AAC/B,YAAY,0DAAmB,QAAQ,qCAAqC;AAC5E,YAAY,0DAAmB,UAAU,qCAAqC,gCAAgC,0DAAmB,aAAa,6EAA6E;AAC3N,gBAAgB,0DAAmB,WAAW,iCAAiC;AAC/E,gBAAgB,0DAAmB,WAAW,+BAA+B;AAC7E,YAAY,0DAAmB,UAAU,qCAAqC;AAC9E,gBAAgB,0DAAmB,aAAa,uDAAuD;AACvG,QAAQ,0DAAmB;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;AC3IA;AACA;AACA;AACA;AACwC;AACoF;AAC9D;AAC9D;AACA;AACA;AACA;AACA;AACA;AACO,kCAAkC,2BAA2B;AACpE,gCAAgC,+CAAQ;AACxC,4CAA4C,+CAAQ;AACpD,oCAAoC,+CAAQ;AAC5C,8CAA8C,+CAAQ;AACtD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,0DAAmB,CAAC,iDAAM,IAAI;AAC1C;AACA;AACA;AACA;AACA,WAAW;AACX,QAAQ,0DAAmB,CAAC,sDAAW;AACvC,YAAY,0DAAmB,CAAC,8CAAG,IAAI,+CAA+C;AACtF,gBAAgB,0DAAmB,CAAC,uEAAe,IAAI,kBAAkB;AACzE,gBAAgB,0DAAmB,CAAC,qDAAU,IAAI,eAAe;AACjE,QAAQ,0DAAmB,CAAC,wDAAa;AACzC,YAAY,0DAAmB,CAAC,8CAAG,IAAI,yDAAyD;AAChG,gBAAgB,0DAAmB,CAAC,qDAAU,IAAI,2CAA2C;AAC7F,gBAAgB,0DAAmB,CAAC,oDAAS,IAAI;AACjD;AACA;AACA;AACA,uBAAuB;AACvB,gBAAgB,0DAAmB,CAAC,8CAAG;AACvC,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,sEAAsE;AAC5H,oBAAoB,0DAAmB,CAAC,8CAAG,IAAI,2CAA2C,2CAA2C,0DAAmB,CAAC,+CAAI,IAAI;AACjK;AACA;AACA;AACA;AACA,2BAA2B;AAC3B,gBAAgB,0DAAmB,CAAC,8CAAG,IAAI;AAC3C;AACA;AACA;AACA,uBAAuB;AACvB,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,6CAA6C;AACnG;AACA,wBAAwB,0DAAmB;AAC3C,wBAAwB,0DAAmB;AAC3C;AACA,wBAAwB,0DAAmB;AAC3C;AACA,wBAAwB,0DAAmB;AAC3C;AACA,wBAAwB,0DAAmB;AAC3C;AACA,QAAQ,0DAAmB,CAAC,wDAAa,IAAI,MAAM,6BAA6B;AAChF,YAAY,0DAAmB,CAAC,iDAAM,IAAI,0CAA0C;AACpF,YAAY,0DAAmB,CAAC,iDAAM,IAAI,oGAAoG,0DAAmB,CAAC,uEAAe,SAAS;AAC1L;;;;;;;;;;;;;;;;;;AChFA;AACA;AACA;AACA;AACA;AACA;AACA;AACmD;AACuF;AACnI,yBAAyB,gCAAgC;AAChE;AACA,wCAAwC,qEAAY,MAAM,4EAAmB;AAC7E,oCAAoC,+CAAQ;AAC5C,sCAAsC,+CAAQ;AAC9C,0CAA0C,+CAAQ,GAAG;AACrD;AACA,gDAAgD,+CAAQ;AACxD;AACA,8CAA8C,+CAAQ;AACtD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,+CAAQ;AAClD;AACA,4CAA4C,+CAAQ;AACpD,wCAAwC,+CAAQ;AAChD,sCAAsC,+CAAQ;AAC9C;AACA,4CAA4C,+CAAQ;AACpD,0CAA0C,+CAAQ;AAClD,4CAA4C,+CAAQ;AACpD,8CAA8C,+CAAQ;AACtD,0CAA0C,+CAAQ;AAClD,wDAAwD,+CAAQ;AAChE;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0DAA0D,4EAAmB;AAC7E;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA,6DAA6D,kBAAkB;AAC/E,2BAA2B,KAAK;AAChC,yBAAyB,aAAa;AACtC,0CAA0C,6BAA6B;AACvE;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,yCAAyC,mEAAU;AACnD,8CAA8C,wDAAwD;AACtG;AACA;AACA,8CAA8C,2BAA2B;AACzE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,mEAAU;AAC/C,iCAAiC,yCAAyC;AAC1E;AACA;AACA,iCAAiC,YAAY;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ,sEAAa;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,0DAAmB,UAAU,wCAAwC;AACjF,QAAQ,0DAAmB,UAAU,uCAAuC;AAC5E,YAAY,0DAAmB,UAAU,uCAAuC;AAChF,gBAAgB,0DAAmB;AACnC,gBAAgB,0DAAmB,aAAa,+EAA+E;AAC/H,YAAY,0DAAmB,UAAU,wCAAwC;AACjF,gBAAgB,0DAAmB,UAAU,uCAAuC;AACpF,oBAAoB,0DAAmB;AACvC,gBAAgB,0DAAmB,UAAU,sCAAsC;AACnF,oBAAoB,0DAAmB,YAAY,sCAAsC;AACzF,oBAAoB,0DAAmB,aAAa,sGAAsG;AAC1J,wBAAwB,0DAAmB,aAAa,iBAAiB;AACzE,wBAAwB,0DAAmB,aAAa,eAAe;AACvE,wBAAwB,0DAAmB,aAAa,iBAAiB;AACzE,gBAAgB,0DAAmB,UAAU,sCAAsC;AACnF,oBAAoB,0DAAmB,YAAY,0EAA0E;AAC7H;AACA,wBAAwB,0DAAmB,YAAY,SAAS,0DAA0D;AAC1H,oBAAoB,0DAAmB,YAAY,8OAA8O;AACjS,0CAA0C,0DAAmB,UAAU,yCAAyC;AAChH,oBAAoB,0DAAmB;AACvC,oBAAoB,0DAAmB,UAAU,sCAAsC;AACvF,wBAAwB,0DAAmB,YAAY,sCAAsC;AAC7F;AACA;AACA;AACA,4BAA4B,0DAAmB,YAAY,SAAS,0DAA0D;AAC9H,2DAA2D,0DAAmB,UAAU;AACxF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B;AAC/B,4BAA4B,0DAAmB,YAAY,qOAAqO,qDAAqD;AACrV,4BAA4B,0DAAmB,WAAW;AAC1D;AACA;AACA;AACA,mCAAmC;AACnC;AACA;AACA,4BAA4B,0DAAmB,YAAY,iEAAiE,SAAS,+FAA+F;AACpO,yDAAyD,0DAAmB,WAAW,SAAS,sCAAsC;AACtI,yDAAyD,0DAAmB,aAAa;AACzF;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC;AACnC,sDAAsD,0DAAmB,aAAa,0HAA0H,oBAAoB;AACpO,oBAAoB,0DAAmB,UAAU,sCAAsC;AACvF,wBAAwB,0DAAmB,YAAY,sCAAsC;AAC7F,wBAAwB,0DAAmB,aAAa,4GAA4G;AACpK,4BAA4B,0DAAmB,aAAa,2BAA2B;AACvF,4BAA4B,0DAAmB,aAAa,yBAAyB;AACrF,wCAAwC,0DAAmB,UAAU,yCAAyC;AAC9G,oBAAoB,0DAAmB;AACvC,oBAAoB,0DAAmB,UAAU,sCAAsC;AACvF,wBAAwB,0DAAmB,YAAY,sCAAsC;AAC7F,wBAAwB,0DAAmB,YAAY,iKAAiK;AACxN,oBAAoB,0DAAmB,UAAU,sCAAsC;AACvF,wBAAwB,0DAAmB,YAAY,sCAAsC;AAC7F,wBAAwB,0DAAmB,YAAY,yMAAyM;AAChQ,oBAAoB,0DAAmB,UAAU,sCAAsC;AACvF,wBAAwB,0DAAmB,YAAY,sCAAsC;AAC7F,wBAAwB,0DAAmB,YAAY,mKAAmK;AAC1N,0CAA0C,0DAAmB,UAAU,yCAAyC;AAChH,oBAAoB,0DAAmB;AACvC,oBAAoB,0DAAmB,UAAU,sCAAsC;AACvF,wBAAwB,0DAAmB,YAAY,sCAAsC;AAC7F,wBAAwB,0DAAmB,YAAY,sJAAsJ;AAC7M,oBAAoB,0DAAmB,UAAU,sCAAsC;AACvF,wBAAwB,0DAAmB,YAAY,sCAAsC;AAC7F,wBAAwB,0DAAmB,aAAa,4GAA4G;AACpK,4BAA4B,0DAAmB,aAAa,gBAAgB;AAC5E,4BAA4B,0DAAmB,aAAa,sBAAsB;AAClF,4BAA4B,0DAAmB,aAAa,wBAAwB;AACpF,gBAAgB,0DAAmB,UAAU,sCAAsC;AACnF,oBAAoB,0DAAmB,YAAY,sCAAsC;AACzF,oBAAoB,0DAAmB,YAAY,yCAAyC;AAC5F,wBAAwB,0DAAmB,YAAY,mIAAmI;AAC1L,wBAAwB,0DAAmB;AAC3C,gBAAgB,0DAAmB,UAAU,sCAAsC;AACnF,oBAAoB,0DAAmB,YAAY,wEAAwE;AAC3H;AACA,wBAAwB,0DAAmB,YAAY,SAAS,0DAA0D;AAC1H,oBAAoB,0DAAmB,YAAY,iPAAiP,gBAAgB,uCAAuC;AAC3V,gBAAgB,0DAAmB,UAAU,sCAAsC;AACnF,oBAAoB,0DAAmB,YAAY,sCAAsC;AACzF;AACA,wBAAwB,0DAAmB,YAAY,SAAS,0DAA0D;AAC1H,oBAAoB,0DAAmB,eAAe,iJAAiJ;AACvM,oBAAoB,0DAAmB,UAAU,+CAA+C;AAChG,wBAAwB,0DAAmB,aAAa,0JAA0J,oFAA+B,GAAG;AACpP,YAAY,0DAAmB,UAAU,uCAAuC;AAChF,gBAAgB,0DAAmB,aAAa,4FAA4F;AAC5I,gBAAgB,0DAAmB,aAAa,+GAA+G;AAC/J,gBAAgB,0DAAmB,aAAa,6FAA6F;AAC7I;;;;;;;;;;;;;;;;;;;;;;;;;ACnRA;AACA;AACA;AACA;AACwC;AAC+E;AACrE;AACU;AACA;AACE;AACZ;AACE;AAC7C,8BAA8B,+CAA+C;AACpF,oCAAoC,+CAAQ;AAC5C,0CAA0C,+CAAQ;AAClD,YAAY,yDAAyD;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,0DAAmB,CAAC,uEAAe,IAAI,mBAAmB;AAC/F,kCAAkC,0DAAmB,CAAC,iEAAS,IAAI,mBAAmB;AACtF,qCAAqC,0DAAmB,CAAC,kEAAU,IAAI,mBAAmB;AAC1F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,0DAAmB,CAAC,+CAAI,IAAI;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW;AACX,QAAQ,0DAAmB,CAAC,sDAAW,IAAI,MAAM,8BAA8B,sBAAsB;AACrG,YAAY,0DAAmB,CAAC,8CAAG,IAAI,+EAA+E;AACtH,gBAAgB,0DAAmB,CAAC,8CAAG,IAAI,+CAA+C;AAC1F,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,0CAA0C;AAChG,oBAAoB,0DAAmB,CAAC,+CAAI,IAAI,sHAAsH;AACtK,gBAAgB,0DAAmB,CAAC,8CAAG,IAAI,iBAAiB;AAC5D,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,4DAA4D,iBAAiB,0DAAmB,CAAC,sEAAc,IAAI,mBAAmB,MAAM,0DAAmB,CAAC,sEAAc,IAAI,mBAAmB;AAC3P,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,iCAAiC;AACvF,wBAAwB,0DAAmB,CAAC,iEAAS,IAAI,mBAAmB;AAC5E,wBAAwB,0DAAmB,CAAC,8CAAG,IAAI,OAAO;AAC1D,gBAAgB,0DAAmB,CAAC,yDAAc,IAAI,+CAA+C,8BAA8B;AACnI,gBAAgB,0DAAmB,CAAC,8CAAG,IAAI,2DAA2D;AACtG,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,6CAA6C;AACnG,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,6CAA6C;AACnG;AACA;AACA,2BAA2B,0DAAmB,CAAC,8CAAG,IAAI,OAAO;AAC7D,gBAAgB,0DAAmB,CAAC,qDAAU,IAAI,yCAAyC;AAC3F;AACA;AACA,kCAAkC,0DAAmB,CAAC,8CAAG,IAAI,OAAO;AACpE,gBAAgB,0DAAmB,CAAC,qDAAU,IAAI,uCAAuC;AACzF;AACA;AACA,YAAY,0DAAmB,CAAC,mDAAQ,IAAI,iBAAiB;AAC7D,gBAAgB,0DAAmB,CAAC,8CAAG,IAAI;AAC3C;AACA;AACA;AACA;AACA,uBAAuB;AACvB,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,wEAAwE;AAC9H,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,wBAAwB,2BAA2B;AACzG,qCAAqC,0DAAmB,CAAC,uDAAc;AACvE,wBAAwB,0DAAmB,CAAC,qDAAU,IAAI,+EAA+E;AACzI,wBAAwB,0DAAmB,CAAC,qDAAU,IAAI,0BAA0B,mDAAmD;AACvI,YAAY,0DAAmB,CAAC,8CAAG,IAAI,qDAAqD;AAC5F,8BAA8B,0DAAmB,CAAC,iDAAM,IAAI,uDAAuD;AACnH,+CAA+C,0DAAmB,CAAC,iDAAM,IAAI,8DAA8D;AAC3I,2BAA2B,0DAAmB,CAAC,iDAAM,IAAI,kDAAkD;AAC3G;;;;;;;;;;;;;;;;;;;;;ACpGA;AACA;AACA;AACA;AACyD;AACS;AACU;AACF;AACR;AACJ;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,kEAAa;AACjB,IAAI,2EAAiB;AACrB,IAAI,qFAAsB;AAC1B,IAAI,mFAAqB;AACzB,IAAI,2EAAiB;AACrB,IAAI,uEAAe;AACnB;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;ACzBvB;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;ACjBA;AACA;AACA;AACA;AACA;AACwD;AAClB;AACtC;AACA;AACA;AACO;AACP;AACA;AACA,eAAe,kEAAgB;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,6BAA6B;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,YAAY;AAChC;AACA;AACA;AACA;AACA,yBAAyB,8CAAU;AACnC,KAAK;AACL;AACA;AACA,yBAAyB,8CAAU;AACnC,KAAK;AACL;AACA;AACA,yBAAyB,8CAAU;AACnC,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,8CAAU;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,8CAAU;AAC3B,6BAA6B,UAAU;AACvC;AACA;AACA,iBAAiB,8CAAU;AAC3B,6BAA6B,UAAU;AACvC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,oBAAoB;AACxC;AACA,0BAA0B;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,oBAAoB;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,WAAW,GAAG,wCAAwC;AAC/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa,8CAAU;AACvB;AACA,+BAA+B,UAAU;AACzC;AACA;;AAEA;AACA,EAAE;AACF;AACA;AACA,aAAa,8CAAU;AACvB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,UAAU;AAC7C;;AAEA;AACA;AACA,EAAE;AACF;;AAEA;AACA;AACA,EAAE;AACF;;AAEA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,mCAAmC,UAAU;AAC7C;;AAEA;AACA;AACA,EAAE;AACF;;AAEA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD,UAAU;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sCAAsC;AACtC,uCAAuC,gBAAgB,iBAAiB,iBAAiB;AACzF,YAAY;AACZ;AACA,6BAA6B,gBAAgB,iBAAiB,iBAAiB;AAC/E,YAAY;AACZ;AACA;AACA,iCAAiC,WAAW,0BAA0B;AACtE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,SAAS;AACT;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC;AACpC,oCAAoC,gBAAgB,iBAAiB,iBAAiB;AACtF;AACA;AACA,2BAA2B,gBAAgB,gBAAgB;AAC3D,oBAAoB,cAAc;AAClC;AACA;AACA,oCAAoC;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,WAAW,0BAA0B;AACpE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,UAAU,QAAQ,WAAW;AAC9D;AACA,2BAA2B,WAAW,qBAAqB,YAAY;AACvE;AACA,gDAAgD,WAAW;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gDAAgD,8CAAU;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;AChyBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACwD;AACA;AACL;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C;AAC5C,wCAAwC;AACxC;AACA,yCAAyC;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uDAAuD;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,cAAc;AACd,eAAe;AACf;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA,aAAa,qBAAqB;AAClC,cAAc,wBAAwB;AACtC,eAAe,qBAAqB;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2DAA2D,iBAAiB;AAC5E;AACA;AACA;AACA;AACA;AACA,6DAA6D,iBAAiB;AAC9E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,4BAA4B,eAAe,uCAAuC,aAAa,sCAAsC,eAAe,iBAAiB;AACnN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,wBAAwB;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,6DAAU;AACtC,8BAA8B,QAAQ;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA,eAAe,kEAAgB,EAAE,kEAAgB;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;ACzhBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACwD;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA,CAAC,gDAAgD;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uCAAuC;AACvC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA,oCAAoC;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,gCAAgC,EAAE,KAAK;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA,eAAe,kEAAgB;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,eAAe,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7X/B;AACA;AACA;AACA;AACiD;AACiC;AACxB;AACN;AACX;AACf;AACiC;AACH;AACsB;AACR;AAChB;AACtD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,8DAAO;AAC5B;AACA,YAAY,wDAAW;AACvB,CAAC;AACD;AACA;AACA;AACA;AACA,gCAAgC,6DAAW;AAC3C;AACA;AACA;AACA;AACA,+BAA+B,+DAAW;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uEAAuE,QAAQ;AAC/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,8DAAY,uBAAuB,qCAAqC;AAC5F;AACA,qBAAqB;AACrB;AACA;AACA,oBAAoB,8DAAY,qBAAqB,aAAa;AAClE;AACA,qBAAqB;AACrB;AACA,aAAa;AACb;AACA,gBAAgB,8DAAY,uBAAuB,iBAAiB;AACpE,aAAa;AACb;AACA;AACA,aAAa;AACb;AACA,YAAY,8DAAY,0BAA0B,iBAAiB;AACnE;AACA;AACA;AACA;AACA,YAAY,8DAAY,oBAAoB,cAAc;AAC1D;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,8DAAY,sBAAsB,iBAAiB;AAC/D;AACA;AACA;AACA,YAAY,8DAAY,iBAAiB,cAAc,KAAK,iBAAiB;AAC7E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,8DAAY,wBAAwB,SAAS;AACzD;AACA,aAAa;AACb;AACA;AACA;AACA,YAAY,8DAAY,qBAAqB,cAAc;AAC3D;AACA,aAAa;AACb;AACA;AACA;AACA,sBAAsB,0DAAW;AACjC;AACA,gBAAgB,0DAAmB,CAAC,wDAAa,IAAI,cAAc;AACnE,YAAY,0DAAmB,UAAU,SAAS,wDAAwD,0CAA0C,0DAAmB,UAAU;AACjL;AACA,mBAAmB;AACnB,gBAAgB,0DAAmB,CAAC,8EAAkB,IAAI;AAC1D,wEAAwE;AACxE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,6DAAW;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,0DAAW;AACjC,gBAAgB,0DAAmB,CAAC,wDAAa,IAAI,cAAc;AACnE,YAAY,0DAAmB,CAAC,sFAAsB,IAAI,kEAAkE;AAC5H;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA,eAAe,oEAAgB;AAC/B,eAAe,2DAAS,EAAE,iEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,mDAAM;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;AC/NA;AACA;AACA;AACA;AACA;AACwD;AACD;AACvD;AACA;AACA;AACO;AACP;AACA;AACA,eAAe,kEAAgB;AAC/B,eAAe,iEAAe;AAC9B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0GAA0G,MAAM;AAChH;AACA;AACA;AACA;AACA;AACA;AACA,0GAA0G,MAAM;AAChH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B;AAC5B;AACA;AACA;AACA;AACA,4BAA4B;AAC5B;AACA;AACA;AACA;AACA;AACA,sGAAsG,MAAM;AAC5G,4BAA4B;AAC5B;AACA,6GAA6G,MAAM;AACnH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,SAAS;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sCAAsC;AACtC,uCAAuC,iCAAiC,iBAAiB,iBAAiB;AAC1G;AACA;AACA,6BAA6B,iCAAiC,iBAAiB,iBAAiB;AAChG;AACA;AACA;AACA,iCAAiC,WAAW,0BAA0B;AACtE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,SAAS;AACT;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,SAAS;AACT;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,6BAA6B;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAiE,YAAY;AAC7E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,qDAAqD,cAAc;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,WAAW,GAAG,wCAAwC;AAC/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,oBAAoB;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;AC7kBA;AACA;AACA;AACA;AAC0D;AACY;AACd;AACF;AACM;AACR;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA,eAAe,oEAAe,EAAE,iEAAe,EAAE,kEAAgB,EAAE,gEAAe;AAClF;AACA;AACA;AACA;AACA,mCAAmC,4DAAU;AAC7C;AACA,mCAAmC,oEAAgB;AACnD;AACA;AACA,oCAAoC,+DAAa;AACjD;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA,iDAAiD,WAAW;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;AC1EA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAC0C;AACI;AACS;AACP;AACE;AACM;AACiC;AAClF;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,mDAAU;AACtD,gCAAgC,uDAAY;AAC5C,iCAAiC,+DAAa;AAC9C;AACA;AACA,SAAS;AACT;AACA,iCAAiC,yDAAa;AAC9C;AACA,kCAAkC,2DAAc;AAChD;AACA,qCAAqC,iEAAiB;AACtD,wBAAwB,GAAG,wEAAyB;AACpD;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,6CAA6C;AACtE;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,4BAA4B,gBAAgB;AAC5C,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0EAA0E,sFAAsF;AAChK;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,oBAAoB;AACzE;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,iBAAiB,MAAM,iBAAiB;AACnF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,UAAU;AACvD;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B;AAC7B;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,cAAc;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B;AAC7B,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gDAAgD,qDAAqD;AACrG;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,iBAAiB,eAAe,iBAAiB;AAC9F,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,iBAAiB,MAAM,iBAAiB;AACnF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0EAA0E,gBAAgB;AAC1F;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,6BAA6B;AAC7D;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA,0DAA0D,8BAA8B;AACxF;AACA;AACA;AACA,kHAAkH;AAClH,iCAAiC;AACjC,6BAA6B;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,sEAAsE;AACxI,6BAA6B;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,8BAA8B,MAAM,mEAAmE,GAAG;AACnK;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD,8BAA8B;AACpF;AACA,6BAA6B;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA,gCAAgC,iCAAiC;AACjE,6EAA6E,gCAAgC;AAC7G;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA,mDAAmD,OAAO;AAC1D,6BAA6B;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA,cAAc,oEAAoE;AAClF,cAAc,0DAA0D;AACxE,cAAc,2DAA2D;AACzE,cAAc,qDAAqD;AACnE;AACA,cAAc,8FAA8F;AAC5G;AACA,cAAc,mFAAmF;AACjG,cAAc,4CAA4C;AAC1D,cAAc,6CAA6C;AAC3D,cAAc,wCAAwC;AACtD,cAAc,wCAAwC;AACtD,cAAc,6CAA6C;AAC3D;AACA,cAAc,gGAAgG;AAC9G,cAAc,8FAA8F;AAC5G,cAAc,yFAAyF;AACvG,cAAc,qEAAqE;AACnF;AACA,cAAc,qDAAqD;AACnE,cAAc,kEAAkE;AAChF;AACA;AACA,qBAAqB,kBAAkB;AACvC;AACA;AACA,yBAAyB;AACzB;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,yCAAyC;AAC1F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oDAAoD,iBAAiB,IAAI,4CAA4C;AACrH,uDAAuD,mCAAmC;AAC1F;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wDAAwD,QAAQ,KAAK,4BAA4B,IAAI,4BAA4B,IAAI,8BAA8B;AACnK;AACA,oEAAoE,WAAW,EAAE,mBAAmB,aAAa,OAAO;AACxH;AACA,wEAAwE,mBAAmB;AAC3F,iCAAiC;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mDAAmD,mBAAmB;AACtE,6BAA6B;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA,oDAAoD,mBAAmB;AACvE;AACA;AACA;AACA,oIAAoI;AACpI;AACA;AACA;AACA;AACA,mDAAmD,yBAAyB;AAC5E,6BAA6B;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,KAAK;AAC/C,8CAA8C,OAAO;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mDAAmD;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,eAAe;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,gDAAgD;AAChE;AACA;AACA,6DAA6D,4BAA4B,IAAI,0BAA0B;AACvH,6BAA6B,6BAA6B,eAAe,+BAA+B;AACxG;AACA;AACA,yDAAyD,mBAAmB,UAAU,sCAAsC;AAC5H;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,eAAe;AACpD;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,kBAAkB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,kBAAkB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC;AACrC,mCAAmC;AACnC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,oBAAoB;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC;AACjC;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,UAAU;AACvD,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB;AACxB;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,qEAAsB;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mEAAmE,2BAA2B,UAAU,MAAM;AAC9G;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,wBAAwB,SAAS,2BAA2B;AACrF,gCAAgC,2BAA2B;AAC3D;AACA;AACA,iDAAiD;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA,gBAAgB,8BAA8B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,iDAAiD;AAC7E,4BAA4B,qCAAqC;AACjE;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,WAAW;AAC7E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sEAAsE,WAAW;AACjF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,iBAAiB,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7xDjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA,sEAAsE,gBAAgB;AACtF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA,cAAc,gBAAgB,KAAK,cAAc;AACjD;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA,iBAAiB;AACjB;AACA;AACA,iBAAiB,mCAAmC,EAAE;AACtD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uCAAuC,qBAAqB;AAC5D;AACA,oBAAoB,iBAAiB;AACrC;AACA;AACA;AACA,0DAA0D,oBAAoB,GAAG,YAAY;AAC7F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,oBAAoB,GAAG,YAAY;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;AC9WA;AACA;AACA;AACyJ;AACzJ;AAC2D;AACpD;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6DAAU;AACzC;AACA;AACA,2BAA2B,yDAAM;AACjC;AACA,yEAAyE;AACzE;AACA;AACA;AACA;AACA;AACA,yBAAyB,EAAE,gBAAgB;AAC3C,qCAAqC,EAAE,KAAK;AAC5C;AACA,wCAAwC;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa;AACzD;AACA;AACA,aAAa;AACb;AACA;AACA;AACA,iEAAiE;AACjE;AACA;AACA;AACA;AACA;AACA,4CAA4C;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,uBAAuB;AACrD;AACA;AACA,oBAAoB,uBAAuB,oEAAoB;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA,oBAAoB,gEAAgB;AACpC;AACA;AACA;AACA;AACA;AACA,oBAAoB,gEAAgB;AACpC,sEAAsE,YAAY;AAClF;AACA,0CAA0C,oEAAoB;AAC9D;AACA;AACA,qCAAqC,kEAAkB;AACvD;AACA,kDAAkD,kEAAkB;AACpE;AACA,kCAAkC;AAClC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,gEAAgB;AACpC,0CAA0C,oEAAoB;AAC9D;AACA,qCAAqC,kEAAkB;AACvD;AACA,kDAAkD,kEAAkB;AACpE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa,0BAA0B,gCAAgC;AACnH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa,2BAA2B,kCAAkC;AACtH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,uBAAuB;AACrD;AACA,oBAAoB,uBAAuB,oEAAoB;AAC/D;AACA;AACA;AACA,gBAAgB,gEAAgB;AAChC;AACA;AACA;AACA;AACA;AACA,oBAAoB,gEAAgB;AACpC,2EAA2E,YAAY;AACvF,0CAA0C,oEAAoB;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,4DAA4D,MAAM;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,cAAc;AACtC;AACA;AACA,kDAAkD,cAAc;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,qCAAqC;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,uBAAuB;AACrD;AACA;AACA,oBAAoB,uBAAuB,oEAAoB;AAC/D;AACA;AACA;AACA;AACA,gBAAgB,gEAAgB;AAChC;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,gEAAgB;AACpC,sEAAsE,YAAY;AAClF;AACA,0CAA0C,oEAAoB;AAC9D;AACA;AACA;AACA;AACA,kCAAkC;AAClC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,oEAAoB;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4EAA4E,cAAc;AAC1F,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,uDAAuD,MAAM;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,cAAc;AACtC;AACA;AACA,kDAAkD,cAAc;AAChE;AACA;AACA,4CAA4C;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,0BAA0B;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mDAAmD,WAAW;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD;AACzD;AACA,iCAAiC;AACjC,wCAAwC;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC;AACjC;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,uDAAuD,MAAM;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,cAAc;AACtC;AACA;AACA,kDAAkD,cAAc;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mDAAmD,WAAW;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD;AACzD;AACA,iCAAiC;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA,mCAAmC,8BAA8B;AACjE,SAAS;AACT,6DAA6D;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA,iEAAiE;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,cAAc;AACtC;AACA;AACA,kDAAkD,cAAc;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,6DAA6D;AAC7D;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,6DAA6D;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,6DAA6D;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,6DAA6D;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,6DAA6D;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,+DAA+D,yCAAyC;AACxG;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa,8BAA8B,gDAAgD;AACvI;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa,gCAAgC,gDAAgD;AACzI;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa,gCAAgC,iDAAiD;AAC1I;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa,kCAAkC,iCAAiC;AAC5H;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa,iCAAiC,qDAAqD;AAC/I;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa,sCAAsC,iCAAiC;AAChI;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,wDAAwD,MAAM;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,cAAc;AACtC;AACA;AACA,kDAAkD,cAAc;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa,0BAA0B,gDAAgD;AACnI;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,uDAAuD,MAAM;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,sDAAsD,MAAM;AAC5D;AACA;AACA;AACA;;;;;;;;;;;;;;;;;ACniCA;AACA;AACA;AACA;AACA;AACA;AACA;AACiE;AACjE;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAe,wEAAyB;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,sBAAsB,WAAW,GAAG,WAAW;AAC/C;AACA;AACA;AACA,4BAA4B,SAAS;AACrC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+EAA+E,WAAW;AAC1F;AACA;AACA,uEAAuE,WAAW;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B;AAC/B;AACA,4BAA4B,6BAA6B;AACzD,sEAAsE;AACtE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,YAAY;AAC1D;AACA;AACA,gEAAgE,WAAW;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC;AACxC;AACA;AACA;AACA;AACA,6EAA6E,UAAU;AACvF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sFAAsF,cAAc;AACpG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0EAA0E,qBAAqB;AAC/F,4BAA4B,sBAAsB;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0DAA0D,WAAW;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,gBAAgB;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,4BAA4B;AACxE,6CAA6C,sBAAsB;AACnE,wCAAwC,gCAAgC;AACxE,wCAAwC,gCAAgC;AACxE,0CAA0C,yDAAyD;AACnG,2CAA2C,0DAA0D;AACrG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,iBAAiB,EAAC;;;;;;;;;;;;;;;;;AC7UjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAC8D;AAC9D;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA,eAAe,qEAAsB;AACrC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA,0DAA0D,gBAAgB,IAAI,QAAQ;AACtF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,QAAQ;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8DAA8D,eAAe,OAAO,WAAW;AAC/F,2BAA2B,YAAY,IAAI,6BAA6B;AACxE;AACA;AACA;AACA,4DAA4D,eAAe,OAAO,YAAY;AAC9F,iDAAiD,iBAAiB;AAClE;AACA;AACA;AACA;AACA;AACA,wDAAwD,eAAe,OAAO,WAAW;AACzF;AACA;AACA;AACA;AACA;AACA,oBAAoB,yCAAyC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAe,gBAAgB,IAAI,eAAe;AAClD,uBAAuB,aAAa,cAAc,eAAe;AACjE,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oEAAoE,sCAAsC;AAC1G,+BAA+B,mCAAmC;AAClE;AACA;AACA;AACA,oBAAoB,iCAAiC;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,gBAAgB;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uCAAuC,sBAAsB;AAC7D,kDAAkD,gCAAgC;AAClF,8CAA8C,mCAAmC;AACjF;AACA,8CAA8C,4BAA4B;AAC1E,wCAAwC,0BAA0B;AAClE,4CAA4C,8BAA8B;AAC1E,4CAA4C,6BAA6B;AACzE,8CAA8C,+CAA+C;AAC7F;AACA;AACA;AACA,iEAAe,cAAc,EAAC;;;;;;;;;;;;;;;;AC7T9B;AACA;AACA;AACA;AAC6D;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,kDAAkD;AAC9F;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,oBAAoB,4BAA4B;AAChD;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,4EAA4E;AAC/G,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,oEAAqB;AAC/C;AACA;AACA,+BAA+B,oEAAqB;AACpD;AACA;AACA,+BAA+B,oEAAqB;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,SAAS;AACpD;AACA;AACA,uCAAuC,SAAS;AAChD,iBAAiB;AACjB;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD,iCAAiC,cAAc;AAC/C;AACA,+CAA+C,cAAc,mBAAmB,aAAa;AAC7F,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;AC/SA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,EAAE,gBAAgB;AAC3C,qCAAqC,EAAE,KAAK;AAC5C;AACA,wCAAwC;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,gCAAgC,gDAAgD;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa,QAAQ,OAAO;AACpE;AACA;AACA;AACA,gCAAgC,sCAAsC;AACtE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+CAA+C,aAAa,QAAQ,OAAO;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa,QAAQ,OAAO;AACpE;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,gCAAgC,kCAAkC;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;ACxIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACuD;AAC6C;AAC7F;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,WAAW,GAAG,wCAAwC;AAChG;AACA,qCAAqC,gBAAgB;AACrD,qCAAqC,gBAAgB;AACrD;AACA;AACA,wBAAwB,uDAAY;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,gBAAgB;AACrD,qCAAqC,gBAAgB;AACrD,qCAAqC,gBAAgB;AACrD,qCAAqC,gBAAgB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,mEAAwB;AACvD;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA,4BAA4B,mEAAwB;AACpD;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA,+BAA+B,mEAAwB;AACvD;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA,qCAAqC,gBAAgB;AACrD;AACA;AACA;AACA;AACA,4BAA4B,mEAAwB;AACpD;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA,6BAA6B,mEAAwB;AACrD;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA,6BAA6B,mEAAwB;AACrD;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA,kCAAkC,mEAAwB;AAC1D;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA,+BAA+B,mEAAwB;AACvD;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,kCAAkC,mEAAwB;AAC1D;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA,4BAA4B,mEAAwB;AACpD;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA,8BAA8B,mEAAwB;AACtD;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA,iCAAiC,mEAAwB;AACzD;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA,kCAAkC,mEAAwB;AAC1D;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA,gCAAgC,mEAAwB;AACxD;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA,8BAA8B,mEAAwB;AACtD;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,iCAAiC,mEAAwB;AACzD;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA,4BAA4B,mEAAwB;AACpD;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA,gCAAgC,mEAAwB;AACxD;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC;AAClC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,WAAW,KAAK,UAAU;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,WAAW,SAAS,eAAe;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA,qBAAqB;AACrB;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA,eAAe,qEAA0B;AACzC;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,4BAA4B,kBAAkB,yBAAyB;AACvF,iCAAiC,SAAS;AAC1C;AACA,kBAAkB,2FAA2F;AAC7G;AACA,cAAc,+CAA+C,YAAY;AACzE;AACA,cAAc,kDAAkD,YAAY;AAC5E;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B;AAC7B;AACA;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,4CAA4C;AAC5C;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,aAAa;AACb,gBAAgB;;AAEhB;AACA;AACA;AACA;;AAEA;AACA;AACA,cAAc;AACd;AACA,cAAc,mDAAmD,KAAK;AACtE;AACA,cAAc,iDAAiD,KAAK;AACpE;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,aAAa,WAAW,YAAY;AAC/E;AACA;AACA;AACA,6BAA6B;AAC7B;AACA;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,SAAS,GAAG,SAAS;AACvF;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB,8CAA8C,KAAK;AACrE;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8DAA8D,aAAa;AAC3E;AACA;AACA,mCAAmC,MAAM,EAAE,YAAY,EAAE,SAAS;AAClE,oCAAoC;AACpC;AACA;AACA,yBAAyB;AACzB;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB;AAChB,8BAA8B;AAC9B,kBAAkB;;AAElB;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,SAAS;;AAET;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;;AAEjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B;AAC7B;AACA;AACA;AACA,6BAA6B,2BAA2B,QAAQ;AAChE;AACA;AACA;AACA;AACA;AACA,sCAAsC,oBAAoB;AAC1D,oCAAoC;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,QAAQ,iBAAiB,eAAe,gBAAgB,yBAAyB,MAAM,IAAI,WAAW,cAAc,oBAAoB,eAAe;AAC5L;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,gBAAgB;AAChB,kBAAkB;;AAElB;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;;AAET,cAAc;AACd;AACA,cAAc,mDAAmD,KAAK;AACtE;AACA,cAAc,iDAAiD,KAAK;AACpE;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAiE,uBAAuB,EAAE,OAAO,EAAE,qBAAqB,QAAQ,QAAQ;AACxI;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B;AAC7B;AACA;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,WAAW,GAAG,wCAAwC;AAClG;AACA;AACA;AACA;AACA,qDAAqD,eAAe;AACpE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4DAA4D,2DAA2D;AACvH;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,wFAAwF,SAAS;AACjG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oGAAoG,kBAAkB;AACtH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB;AAChB,oBAAoB;AACpB,oBAAoB;;AAEpB;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B;AAC7B;AACA;AACA;AACA;;AAEA;AACA;;AAEA,cAAc;AACd;AACA,cAAc,qDAAqD,EAAE;AACrE;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mEAAmE,OAAO,GAAG,OAAO,IAAI,UAAU;AAClG;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B;AAC7B;AACA;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,iBAAiB;AAChD;AACA;AACA,gCAAgC,QAAQ;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0DAA0D,eAAe,IAAI,cAAc;AAC3F;AACA;AACA;AACA;AACA;AACA,gGAAgG,kBAAkB;AAClH;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,aAAa;AACb,YAAY;;AAEZ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uCAAuC,MAAM,oCAAoC,KAAK;AACtF;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mDAAmD,KAAK;;AAExD;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,QAAQ,GAAG,YAAY,KAAK,+BAA+B;AAC9F;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6DAA6D,YAAY,MAAM,cAAc,EAAE,0CAA0C;AACzI;AACA;AACA,6BAA6B;AAC7B;AACA;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,YAAY;AAC5B;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA,8CAA8C,UAAU,mBAAmB,cAAc;AACzF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,UAAU,gBAAgB,UAAU,EAAE,mBAAmB,WAAW,WAAW;AAClH;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,iCAAiC;AACjD;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA,8CAA8C,UAAU,mBAAmB,cAAc;AACzF;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,WAAW;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,wBAAwB;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC,iBAAiB,IAAI,kBAAkB;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,cAAc;AACd,eAAe;;AAEf;AACA;AACA,0DAA0D,KAAK;;AAE/D;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B;AAC1B;AACA;AACA,SAAS;AACT;AACA,iCAAiC;;AAEjC;AACA;AACA;;AAEA,cAAc;AACd;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,aAAa,OAAO,kBAAkB;AAC3F;AACA;AACA;AACA,6BAA6B;AAC7B;AACA;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,gBAAgB;;AAEhB;AACA;AACA,sBAAsB;AACtB;AACA,yEAAyE,KAAK;AAC9E;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA,cAAc;AACd;AACA,cAAc,+DAA+D,sBAAsB;AACnG;AACA,cAAc,iDAAiD,KAAK;AACpE;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,OAAO,IAAI,YAAY;AACjE;AACA;AACA;AACA,6BAA6B;AAC7B;AACA;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,kBAAkB;;AAElB;AACA,mDAAmD,KAAK;;AAExD;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA,0BAA0B;AAC1B;AACA,sDAAsD,KAAK;AAC3D;AACA;AACA,kBAAkB;;AAElB;AACA,cAAc;AACd;AACA,cAAc,iDAAiD,KAAK;AACpE;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,QAAQ,IAAI,YAAY;AACnE;AACA;AACA;AACA,6BAA6B;AAC7B;AACA;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,qDAAqD;AACrE;AACA;AACA;AACA;AACA,sCAAsC,WAAW,GAAG,wCAAwC;AAC5F;AACA;AACA;AACA;AACA,uCAAuC,WAAW,YAAY,kCAAkC,QAAQ,aAAa,oBAAoB,EAAE;AAC3I;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sCAAsC,WAAW,UAAU,yCAAyC;AACpG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,gBAAgB;AACzE;AACA;AACA,qDAAqD,MAAM;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,gBAAgB;AAC5D;AACA;AACA,6BAA6B;AAC7B;AACA;AACA;AACA;AACA,6BAA6B;AAC7B;AACA,+CAA+C,6BAA6B;AAC5E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+CAA+C,OAAO;AACtD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B;AAC7B;AACA,6CAA6C,OAAO;AACpD;AACA;AACA;AACA;AACA;AACA,yBAAyB,iDAAiD,UAAU;AACpF;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,uBAAuB;AACvB;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uCAAuC,iBAAiB,KAAK,+BAA+B;AAC5F;AACA;AACA;AACA;AACA;AACA,uEAAuE,WAAW;AAClF;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB,aAAa;AACb,gBAAgB;AAChB,gBAAgB;AAChB,iBAAiB;;AAEjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,mBAAmB,YAAY,mBAAmB,aAAa,mBAAmB,aAAa,oBAAoB;AACxJ;AACA;AACA;AACA,mCAAmC,QAAQ,MAAM,cAAc;AAC/D;AACA;AACA;AACA;AACA;AACA,gDAAgD,QAAQ,MAAM,cAAc,IAAI,cAAc;AAC9F;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA,gBAAgB,wDAAwD;AACxE;AACA;AACA,qBAAqB,0BAA0B,WAAW;AAC1D;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,kBAAkB;AAClB,iBAAiB;AACjB,iBAAiB;AACjB,mBAAmB;AACnB,iBAAiB;;AAEjB;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA,6CAA6C,SAAS;AACtD;AACA;;AAEA;AACA,0CAA0C,SAAS;;AAEnD;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,sBAAsB,oBAAoB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,SAAS,gBAAgB,KAAK;AAChF;;AAEA;AACA,cAAc,8CAA8C,KAAK;AACjE;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,eAAe,OAAO,eAAe;AAChF;AACA;AACA,sDAAsD,eAAe;AACrE;AACA;AACA,oDAAoD,eAAe;AACnE;AACA;AACA;AACA,mCAAmC,MAAM,GAAG,gBAAgB,aAAa,YAAY;AACrF;AACA;AACA;AACA,6BAA6B;AAC7B;AACA;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uCAAuC,KAAK;AAC5C;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,KAAK;AAChD;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,KAAK;AAChD;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+CAA+C,KAAK;AACpD;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA,EAAE;AACF,MAAM,EAAE,oBAAoB,EAAE;AAC9B,0BAA0B,EAAE,oBAAoB,EAAE;AAClD,cAAc,EAAE;AAChB;AACA,cAAc,EAAE;;AAEhB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB;AACxB,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,qBAAqB;AAC7C,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,qBAAqB;AAC7C,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB;AACxB,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,WAAW;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,WAAW;AACxD;AACA;AACA;AACA;AACA;AACA,iCAAiC,iEAAe;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,oBAAoB;AAChD;AACA;AACA,qDAAqD,GAAG;AACxD;AACA;AACA;AACA,yDAAyD,GAAG;AAC5D;AACA;AACA,yDAAyD,GAAG;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,wBAAwB;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,kBAAkB,IAAI,mBAAmB;AACzE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,QAAQ;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB;AACvB;AACA;AACA;AACA;AACA;AACA,iEAAe,YAAY,EAAC;;;;;;;;;;;;;;;;;;;;;AC17E5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,gBAAgB;AACjE;AACA;AACA,uDAAuD,iBAAiB,GAAG,qBAAqB;AAChG;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6DAA6D,KAAK;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mEAAmE,SAAS;AAC5E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,KAAK;AAC7C;AACA;AACA;AACA;AACA;AACA,uBAAuB,KAAK,GAAG,WAAW,GAAG,wCAAwC;AACrF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wEAAwE,KAAK;AAC7E;AACA;AACA,qEAAqE,KAAK,YAAY,sBAAsB;AAC5G;AACA;AACA,qDAAqD,yCAAyC;AAC9F;AACA;AACA,yDAAyD,KAAK;AAC9D;AACA;AACA;AACA,0DAA0D,KAAK;AAC/D;AACA;AACA;AACA,mEAAmE,KAAK;AACxE;AACA;AACA,mEAAmE,KAAK;AACxE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,2BAA2B,EAAE,iBAAiB;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,IAAI,IAAI,SAAS;AAC3C,aAAa;AACb;AACA;AACA,0CAA0C,aAAa;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,gBAAgB;AAC7D,8CAA8C,sBAAsB;AACpE;AACA;AACA,iCAAiC,MAAM,GAAG,cAAc,IAAI,eAAe,cAAc,sBAAsB;AAC/G;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA,iEAAe,YAAY,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AClZ5B;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;;;;;;;;;;;;;;;;;ACrDA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA,CAAC,gCAAgC;AAC1B;AACP;AACA;AACA;AACA;AACA;AACA,CAAC,gCAAgC;AACjC;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA,CAAC,gCAAgC;;;;;;;;;;;;;;;;ACxBjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA,oCAAoC,iCAAiC,IAAI,sBAAsB;AAC/F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,kBAAkB;AAC1C;AACA,sEAAsE,MAAM,IAAI,EAAE;AAClF;AACA,wFAAwF,MAAM,IAAI,EAAE;AACpG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oDAAoD;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB;AACxB;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oEAAoE,EAAE;AACtE;AACA;AACA;AACA,6DAA6D,EAAE;AAC/D;AACA;AACA;AACA;AACA,iEAAe,aAAa,EAAC;;;;;;;;;;;;;;;;;;;;;;;AC9Q7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,gBAAgB;AACpC;AACA,4BAA4B;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA,oBAAoB,kBAAkB;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gDAAgD,wCAAwC;AACxF;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,wCAAwC;AAChF;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,yCAAyC;AACzC;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,iBAAiB;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB;AACvB;AACA;AACA,4BAA4B;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,0BAA0B;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4EAA4E,QAAQ;AACpF,wFAAwF,YAAY;AACpG;AACA;AACA;AACA,MAAM;AACN,MAAM;AACN;AACA,IAAI;AACJ;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA,kCAAkC,mBAAmB;AACrD,gCAAgC;AAChC,gCAAgC;AAChC,kCAAkC,kBAAkB;AACpD,iCAAiC;AACjC,mCAAmC,kBAAkB;AACrD,kCAAkC;AAClC,iCAAiC;AACjC;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,mBAAmB;AAC7C;AACA;AACA,kCAAkC,eAAe,IAAI,kBAAkB;AACvE,SAAS;AACT;AACA,KAAK;AACL;AACA,6CAA6C,EAAE;AAC/C,0BAA0B,mBAAmB;AAC7C;AACA;AACA,kCAAkC,cAAc,IAAI,kBAAkB;AACtE,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA,0BAA0B,mBAAmB;AAC7C;AACA;AACA,kCAAkC,cAAc,IAAI,kBAAkB;AACtE,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA,0BAA0B,mBAAmB;AAC7C;AACA;AACA,kCAAkC,cAAc,IAAI,MAAM;AAC1D,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uCAAuC,QAAQ;AAC/C;AACA,8BAA8B,mBAAmB;AACjD;AACA;AACA,sCAAsC,eAAe,IAAI,MAAM;AAC/D,aAAa;AACb;AACA,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uCAAuC,QAAQ;AAC/C;AACA,8BAA8B,mBAAmB;AACjD;AACA;AACA,sCAAsC,eAAe,IAAI,MAAM;AAC/D,aAAa;AACb;AACA,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA,8BAA8B,mBAAmB;AACjD;AACA;AACA,sCAAsC,gBAAgB,IAAI,SAAS;AACnE,aAAa;AACb;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA,oEAAoE,gBAAgB;AACpF;AACA,sCAAsC,sBAAsB,eAAe;AAC3E;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA,gDAAgD,YAAY;AAC5D;AACA;AACA;AACA,8CAA8C,YAAY;AAC1D;AACA;AACA;AACA,6CAA6C,YAAY;AACzD;AACA;AACA;AACA,6CAA6C,YAAY;AACzD;AACA;AACA,6CAA6C,YAAY;AACzD,KAAK;AACL;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,mBAAmB,IAAI,gCAAgC;AAC5G;AACA;AACA,iDAAiD,mBAAmB,IAAI,KAAK;AAC7E;AACA;AACA;AACA;AACA,mEAAmE,mBAAmB,IAAI,gBAAgB;AAC1G;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,mBAAmB,IAAI,uDAAuD;AAChI;AACA;AACA;AACA;AACA;AACA,kDAAkD,mBAAmB,IAAI,kCAAkC;AAC3G;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,mBAAmB,IAAI,6BAA6B;AAClG;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,8CAA8C,OAAO,IAAI,kCAAkC;AAC3F,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA,kDAAkD,OAAO,IAAI,kCAAkC;AAC/F,aAAa;AACb;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uBAAuB;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;ACxzBA;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,iBAAiB,E;;;;;;;;;;;;;;;;ACNlB;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,YAAY,E;;;;;;;;;;;;;;;;ACNb;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,iBAAiB,E;;;;;;;;;;;;;;;;ACNlB;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,WAAW,E;;;;;;;;;;;;;;;;ACNZ;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,WAAW,E;;;;;;;;;;;;;;;;ACNZ;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,gBAAgB,E;;;;;;;;;;;;;;;;ACNjB;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,gBAAgB,E","sources":["webpack://hdsp-agent/./lib/components/AgentPanel.js","webpack://hdsp-agent/./lib/components/FileSelectionDialog.js","webpack://hdsp-agent/./lib/components/PromptGenerationDialog.js","webpack://hdsp-agent/./lib/components/SettingsPanel.js","webpack://hdsp-agent/./lib/components/TaskProgressWidget.js","webpack://hdsp-agent/./lib/index.js","webpack://hdsp-agent/./lib/logoSvg.js","webpack://hdsp-agent/./lib/plugins/cell-buttons-plugin.js","webpack://hdsp-agent/./lib/plugins/idle-monitor-plugin.js","webpack://hdsp-agent/./lib/plugins/lsp-bridge-plugin.js","webpack://hdsp-agent/./lib/plugins/prompt-generation-plugin.js","webpack://hdsp-agent/./lib/plugins/save-interceptor-plugin.js","webpack://hdsp-agent/./lib/plugins/sidebar-plugin.js","webpack://hdsp-agent/./lib/services/AgentOrchestrator.js","webpack://hdsp-agent/./lib/services/ApiKeyManager.js","webpack://hdsp-agent/./lib/services/ApiService.js","webpack://hdsp-agent/./lib/services/CheckpointManager.js","webpack://hdsp-agent/./lib/services/ContextManager.js","webpack://hdsp-agent/./lib/services/StateVerifier.js","webpack://hdsp-agent/./lib/services/TaskService.js","webpack://hdsp-agent/./lib/services/ToolExecutor.js","webpack://hdsp-agent/./lib/services/ToolRegistry.js","webpack://hdsp-agent/./lib/types/auto-agent.js","webpack://hdsp-agent/./lib/types/index.js","webpack://hdsp-agent/./lib/utils/SafetyChecker.js","webpack://hdsp-agent/./lib/utils/markdownRenderer.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/AutoFixHigh.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/Cancel.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/CheckCircle.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/Close.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/Error.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/ExpandLess.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/ExpandMore.js"],"sourcesContent":["/**\n * Agent Panel - Main sidebar panel for Jupyter Agent\n * Cursor AI Style: Unified Chat + Agent Interface\n */\nimport React, { useState, useEffect, useRef, useImperativeHandle, forwardRef, useCallback } from 'react';\nimport { ReactWidget, LabIcon } from '@jupyterlab/ui-components';\nimport { NotebookPanel, NotebookActions } from '@jupyterlab/notebook';\nimport { SettingsPanel } from './SettingsPanel';\nimport { getLLMConfig, saveLLMConfig, hasValidApiKey, getDefaultLLMConfig } from '../services/ApiKeyManager';\nimport { AgentOrchestrator } from '../services/AgentOrchestrator';\nimport { DEFAULT_AUTO_AGENT_CONFIG, } from '../types/auto-agent';\nimport { formatMarkdownToHtml } from '../utils/markdownRenderer';\nimport { FileSelectionDialog } from './FileSelectionDialog';\n// 로고 이미지 (SVG) - TypeScript 모듈에서 인라인 문자열로 import\nimport { headerLogoSvg, tabbarLogoSvg } from '../logoSvg';\n// 탭바 아이콘 생성\nconst hdspTabIcon = new LabIcon({\n name: 'hdsp-agent:tab-icon',\n svgstr: tabbarLogoSvg\n});\n// Agent 명령어 감지 함수\nconst isAgentCommand = (input) => {\n const trimmed = input.trim().toLowerCase();\n return trimmed.startsWith('/run ') ||\n trimmed.startsWith('@agent ') ||\n trimmed.startsWith('/agent ') ||\n trimmed.startsWith('/execute ');\n};\n// Agent 명령어에서 실제 요청 추출\nconst extractAgentRequest = (input) => {\n const trimmed = input.trim();\n if (trimmed.toLowerCase().startsWith('/run ')) {\n return trimmed.slice(5).trim();\n }\n if (trimmed.toLowerCase().startsWith('@agent ')) {\n return trimmed.slice(7).trim();\n }\n if (trimmed.toLowerCase().startsWith('/agent ')) {\n return trimmed.slice(7).trim();\n }\n if (trimmed.toLowerCase().startsWith('/execute ')) {\n return trimmed.slice(9).trim();\n }\n return trimmed;\n};\n// ═══════════════════════════════════════════════════════════════════════════\n// Python 파일 에러 감지 및 처리 유틸리티\n// ═══════════════════════════════════════════════════════════════════════════\n// Python 에러 패턴 감지\nconst detectPythonError = (text) => {\n const errorPatterns = [\n /Traceback \\(most recent call last\\)/i,\n /SyntaxError:/i,\n /NameError:/i,\n /TypeError:/i,\n /ImportError:/i,\n /ModuleNotFoundError:/i,\n /AttributeError:/i,\n /ValueError:/i,\n /KeyError:/i,\n /IndexError:/i,\n /FileNotFoundError:/i,\n /File\\s+\"[^\"]+\\.py\"/i,\n ];\n return errorPatterns.some(pattern => pattern.test(text));\n};\n// 에러 메시지에서 Python 파일 경로 추출\nconst extractFilePathsFromError = (text) => {\n const paths = [];\n // 패턴 1: python xxx.py 명령어\n const cmdMatch = text.match(/python\\s+(\\S+\\.py)/gi);\n if (cmdMatch) {\n cmdMatch.forEach(match => {\n const pathMatch = match.match(/python\\s+(\\S+\\.py)/i);\n if (pathMatch)\n paths.push(pathMatch[1]);\n });\n }\n // 패턴 2: File \"xxx.py\" 형식 (트레이스백)\n const fileMatches = text.matchAll(/File\\s+\"([^\"]+\\.py)\"/gi);\n for (const match of fileMatches) {\n if (!paths.includes(match[1])) {\n paths.push(match[1]);\n }\n }\n return paths;\n};\n// 메인 파일 경로 추출 (명령어에서 실행한 파일)\nconst extractMainFilePath = (text) => {\n // python xxx.py 명령어에서 추출\n const cmdMatch = text.match(/python\\s+(\\S+\\.py)/i);\n if (cmdMatch) {\n return cmdMatch[1];\n }\n return null;\n};\n// 에러가 발생한 실제 파일 추출 (트레이스백의 마지막 파일)\nconst extractErrorFilePath = (text) => {\n // File \"xxx.py\" 패턴들 중 마지막 것 (실제 에러 발생 위치)\n const fileMatches = [...text.matchAll(/File\\s+\"([^\"]+\\.py)\"/gi)];\n if (fileMatches.length > 0) {\n return fileMatches[fileMatches.length - 1][1];\n }\n return null;\n};\n// Python 파일에서 로컬 import 추출\nconst extractLocalImports = (content) => {\n const imports = [];\n // from xxx import yyy (로컬 모듈)\n const fromImports = content.matchAll(/^from\\s+(\\w+)\\s+import/gm);\n for (const match of fromImports) {\n const moduleName = match[1];\n // 표준 라이브러리가 아닌 것만 (간단한 휴리스틱)\n if (!isStdLibModule(moduleName)) {\n imports.push(moduleName);\n }\n }\n // import xxx (로컬 모듈)\n const directImports = content.matchAll(/^import\\s+(\\w+)/gm);\n for (const match of directImports) {\n const moduleName = match[1];\n if (!isStdLibModule(moduleName)) {\n imports.push(moduleName);\n }\n }\n return [...new Set(imports)];\n};\n// 표준 라이브러리 모듈 체크 (간단한 목록)\nconst isStdLibModule = (name) => {\n const stdLibModules = [\n 'os', 'sys', 'json', 're', 'math', 'datetime', 'time', 'random',\n 'collections', 'itertools', 'functools', 'typing', 'pathlib',\n 'subprocess', 'threading', 'multiprocessing', 'asyncio', 'socket',\n 'http', 'urllib', 'email', 'html', 'xml', 'logging', 'unittest',\n 'io', 'pickle', 'copy', 'pprint', 'traceback', 'warnings',\n 'contextlib', 'abc', 'dataclasses', 'enum', 'types',\n // 외부 라이브러리 (일반적으로 설치됨)\n 'numpy', 'pandas', 'matplotlib', 'seaborn', 'sklearn', 'scipy',\n 'torch', 'tensorflow', 'keras', 'requests', 'flask', 'django',\n ];\n return stdLibModules.includes(name.toLowerCase());\n};\nconst ChatPanel = forwardRef(({ apiService, notebookTracker, consoleTracker }, ref) => {\n // 통합 메시지 목록 (Chat + Agent 실행)\n const [messages, setMessages] = useState([]);\n const [input, setInput] = useState('');\n const [isLoading, setIsLoading] = useState(false);\n const [isStreaming, setIsStreaming] = useState(false);\n const [streamingMessageId, setStreamingMessageId] = useState(null);\n const [conversationId, setConversationId] = useState('');\n const [showSettings, setShowSettings] = useState(false);\n const [llmConfig, setLlmConfig] = useState(null);\n // Agent 실행 상태\n const [isAgentRunning, setIsAgentRunning] = useState(false);\n const [currentAgentMessageId, setCurrentAgentMessageId] = useState(null);\n const [executionSpeed, setExecutionSpeed] = useState('normal');\n // 입력 모드 (Cursor AI 스타일) - 로컬 스토리지에서 복원\n const [inputMode, setInputMode] = useState(() => {\n try {\n const saved = localStorage.getItem('hdsp-agent-input-mode');\n return (saved === 'agent' || saved === 'chat') ? saved : 'chat';\n }\n catch {\n return 'chat';\n }\n });\n const [showModeDropdown, setShowModeDropdown] = useState(false);\n // 파일 수정 관련 상태\n const [pendingFileFixes, setPendingFileFixes] = useState([]);\n // Console 에러 자동 감지 상태\n const [lastConsoleError, setLastConsoleError] = useState(null);\n const [showConsoleErrorNotification, setShowConsoleErrorNotification] = useState(false);\n // File selection state\n const [fileSelectionMetadata, setFileSelectionMetadata] = useState(null);\n const [pendingAgentRequest, setPendingAgentRequest] = useState(null);\n // Human-in-the-Loop state\n const [debugStatus, setDebugStatus] = useState(null);\n const [interruptData, setInterruptData] = useState(null);\n // Todo list state (from TodoListMiddleware)\n const [todos, setTodos] = useState([]);\n const [isTodoExpanded, setIsTodoExpanded] = useState(false);\n // Agent thread ID for context persistence across cycles (SummarizationMiddleware support)\n const [agentThreadId, setAgentThreadId] = useState(null);\n // Rejection feedback mode - when user clicks reject, wait for optional feedback\n const [isRejectionMode, setIsRejectionMode] = useState(false);\n const [pendingRejectionInterrupt, setPendingRejectionInterrupt] = useState(null);\n const interruptMessageIdRef = useRef(null);\n const approvalPendingRef = useRef(false);\n const pendingToolCallsRef = useRef([]);\n const handledToolCallKeysRef = useRef(new Set());\n const isNotebookWidget = (widget) => {\n if (!widget)\n return false;\n if (widget instanceof NotebookPanel)\n return true;\n const model = widget?.content?.model;\n return Boolean(model && (model.cells || model.sharedModel?.cells));\n };\n const getActiveNotebookPanel = () => {\n const app = window.jupyterapp;\n const currentWidget = app?.shell?.currentWidget;\n if (isNotebookWidget(currentWidget)) {\n return currentWidget;\n }\n if (notebookTracker?.currentWidget && isNotebookWidget(notebookTracker.currentWidget)) {\n return notebookTracker.currentWidget;\n }\n return null;\n };\n const insertCell = (notebook, cellType, source) => {\n const model = notebook.content?.model;\n const cellCount = model?.cells?.length ?? model?.sharedModel?.cells?.length;\n if (!model?.sharedModel || cellCount === undefined) {\n console.warn('[AgentPanel] Notebook model not ready for insert');\n return null;\n }\n const insertIndex = cellCount;\n model.sharedModel.insertCell(insertIndex, {\n cell_type: cellType,\n source\n });\n notebook.content.activeCellIndex = insertIndex;\n return insertIndex;\n };\n const deleteCell = (notebook, cellIndex) => {\n const model = notebook.content?.model;\n const cellCount = model?.cells?.length ?? model?.sharedModel?.cells?.length;\n if (!model?.sharedModel || cellCount === undefined) {\n console.warn('[AgentPanel] Notebook model not ready for delete');\n return false;\n }\n if (cellIndex < 0 || cellIndex >= cellCount) {\n console.warn('[AgentPanel] Invalid cell index for delete:', cellIndex);\n return false;\n }\n model.sharedModel.deleteCell(cellIndex);\n console.log('[AgentPanel] Deleted rejected cell at index:', cellIndex);\n return true;\n };\n const executeCell = async (notebook, cellIndex) => {\n try {\n await notebook.sessionContext.ready;\n notebook.content.activeCellIndex = cellIndex;\n await NotebookActions.run(notebook.content, notebook.sessionContext);\n }\n catch (error) {\n console.error('[AgentPanel] Cell execution failed:', error);\n }\n };\n const captureExecutionResult = (notebook, cellIndex) => {\n const cell = notebook.content.widgets[cellIndex];\n const model = cell?.model;\n const outputs = model?.outputs;\n const rawState = model?.executionState;\n const executionState = typeof rawState === 'string'\n ? rawState\n : rawState?.get?.() ?? rawState?.value ?? null;\n const result = {\n success: true,\n output: '',\n error: undefined,\n error_type: undefined,\n traceback: undefined,\n execution_count: model?.executionCount ?? null,\n execution_state: executionState,\n kernel_status: notebook.sessionContext?.session?.kernel?.status ?? null,\n cell_type: model?.type ?? null,\n outputs: [],\n };\n if (!outputs || outputs.length === 0) {\n return result;\n }\n const stripImageData = (data) => {\n const filtered = {};\n Object.keys(data || {}).forEach((key) => {\n if (!key.toLowerCase().startsWith('image/')) {\n filtered[key] = data[key];\n }\n });\n return filtered;\n };\n const errorSignatures = [\n 'command not found',\n 'ModuleNotFoundError',\n 'No module named',\n 'Traceback (most recent call last)',\n 'Error:',\n ];\n for (let i = 0; i < outputs.length; i++) {\n const output = outputs.get(i);\n const json = output.toJSON?.() || output;\n let sanitizedOutput = json;\n if (output.type === 'error') {\n result.outputs.push(sanitizedOutput);\n result.success = false;\n result.error_type = json.ename;\n result.error = json.evalue;\n if (Array.isArray(json.traceback)) {\n result.traceback = json.traceback;\n }\n }\n else if (output.type === 'stream' && json.text) {\n result.outputs.push(sanitizedOutput);\n result.output += json.text;\n const lowerText = json.text.toLowerCase();\n if (errorSignatures.some(signature => lowerText.includes(signature.toLowerCase()))) {\n result.success = false;\n result.error_type = 'runtime_error';\n result.error = json.text.trim();\n }\n }\n else if ((output.type === 'execute_result' || output.type === 'display_data') && json.data) {\n const filteredData = stripImageData(json.data);\n if (Object.keys(filteredData).length > 0) {\n sanitizedOutput = { ...json, data: filteredData };\n result.outputs.push(sanitizedOutput);\n result.output += JSON.stringify(filteredData);\n }\n }\n }\n return result;\n };\n const executePendingApproval = async () => {\n const notebook = getActiveNotebookPanel();\n if (!notebook) {\n console.warn('[AgentPanel] No active notebook to execute cell');\n return null;\n }\n const pending = pendingToolCallsRef.current;\n if (pending.length === 0) {\n approvalPendingRef.current = false;\n return null;\n }\n const next = pending.shift();\n if (!next) {\n approvalPendingRef.current = false;\n return null;\n }\n if (next.tool === 'jupyter_cell' && next.code && typeof next.cellIndex === 'number') {\n await executeCell(notebook, next.cellIndex);\n const execResult = captureExecutionResult(notebook, next.cellIndex);\n execResult.code = next.code;\n next.execution_result = execResult;\n console.log('[AgentPanel] Executed approved code cell from tool call');\n approvalPendingRef.current = pendingToolCallsRef.current.length > 0;\n return next;\n }\n approvalPendingRef.current = pendingToolCallsRef.current.length > 0;\n return next;\n };\n const getAutoApproveEnabled = (config) => (Boolean(config?.autoApprove));\n const queueApprovalCell = (code) => {\n const notebook = getActiveNotebookPanel();\n if (!notebook) {\n console.warn('[AgentPanel] No active notebook to add approval cell');\n return;\n }\n const key = `jupyter_cell:${code}`;\n if (handledToolCallKeysRef.current.has(key)) {\n return;\n }\n const index = insertCell(notebook, 'code', code);\n if (index === null) {\n return;\n }\n handledToolCallKeysRef.current.add(key);\n approvalPendingRef.current = true;\n pendingToolCallsRef.current.push({ tool: 'jupyter_cell', code, cellIndex: index });\n console.log('[AgentPanel] Added code cell pending approval via interrupt');\n };\n const handleToolCall = (toolCall) => {\n const key = `${toolCall.tool}:${toolCall.code || toolCall.content || ''}`;\n if (handledToolCallKeysRef.current.has(key)) {\n return;\n }\n if (toolCall.tool === 'jupyter_cell') {\n return;\n }\n if (toolCall.tool === 'markdown' && toolCall.content) {\n const notebook = getActiveNotebookPanel();\n if (!notebook) {\n console.warn('[AgentPanel] No active notebook to add markdown');\n return;\n }\n const index = insertCell(notebook, 'markdown', toolCall.content);\n if (index !== null) {\n handledToolCallKeysRef.current.add(key);\n console.log('[AgentPanel] Added markdown cell from tool call');\n }\n }\n };\n // 모드 변경 시 로컬 스토리지에 저장\n useEffect(() => {\n try {\n localStorage.setItem('hdsp-agent-input-mode', inputMode);\n }\n catch {\n // 로컬 스토리지 접근 불가 시 무시\n }\n }, [inputMode]);\n const messagesEndRef = useRef(null);\n const pendingLlmPromptRef = useRef(null);\n const allCodeBlocksRef = useRef([]);\n const currentCellIdRef = useRef(null);\n const currentCellIndexRef = useRef(null);\n const orchestratorRef = useRef(null);\n const makeMessageId = (suffix) => {\n const base = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n return suffix ? `${base}-${suffix}` : base;\n };\n // Expose handleSendMessage via ref\n useImperativeHandle(ref, () => ({\n handleSendMessage: async () => {\n await handleSendMessage();\n },\n setInput: (value) => {\n setInput(value);\n },\n setLlmPrompt: (prompt) => {\n pendingLlmPromptRef.current = prompt;\n // Find textarea and set data attribute\n const textarea = messagesEndRef.current?.parentElement?.querySelector('.jp-agent-input');\n if (textarea) {\n textarea.setAttribute('data-llm-prompt', prompt);\n }\n },\n setCurrentCellId: (cellId) => {\n console.log('[AgentPanel] setCurrentCellId called with:', cellId);\n currentCellIdRef.current = cellId;\n },\n setCurrentCellIndex: (cellIndex) => {\n console.log('[AgentPanel] setCurrentCellIndex called with:', cellIndex);\n currentCellIndexRef.current = cellIndex;\n },\n setInputMode: (mode) => {\n console.log('[AgentPanel] setInputMode called with:', mode);\n setInputMode(mode);\n }\n }));\n // Load config on mount\n useEffect(() => {\n loadConfig();\n }, []);\n // ═══════════════════════════════════════════════════════════════════════════\n // Console 출력 모니터링 - Python 에러 자동 감지\n // ═══════════════════════════════════════════════════════════════════════════\n useEffect(() => {\n if (!consoleTracker) {\n console.log('[AgentPanel] ConsoleTracker not available');\n return;\n }\n console.log('[AgentPanel] Setting up console output monitoring');\n // Console 출력에서 에러 감지하는 함수\n const checkConsoleForErrors = () => {\n const currentConsole = consoleTracker.currentWidget;\n if (!currentConsole) {\n return;\n }\n try {\n // Console의 출력 영역에서 텍스트 추출\n const consoleNode = currentConsole.node;\n if (!consoleNode)\n return;\n // JupyterLab Console의 출력은 .jp-OutputArea-output 클래스에 있음\n const outputAreas = consoleNode.querySelectorAll('.jp-OutputArea-output');\n if (outputAreas.length === 0)\n return;\n // 최근 출력만 확인 (마지막 몇 개)\n const recentOutputs = Array.from(outputAreas).slice(-5);\n let combinedOutput = '';\n recentOutputs.forEach((output) => {\n const text = output.textContent || '';\n combinedOutput += text + '\\n';\n });\n // Python 에러 감지\n if (detectPythonError(combinedOutput)) {\n console.log('[AgentPanel] Python error detected in console output');\n // 중복 알림 방지\n if (lastConsoleError !== combinedOutput) {\n setLastConsoleError(combinedOutput);\n setShowConsoleErrorNotification(true);\n // 5초 후 자동으로 알림 숨기기\n setTimeout(() => {\n setShowConsoleErrorNotification(false);\n }, 10000);\n }\n }\n }\n catch (e) {\n console.error('[AgentPanel] Error checking console output:', e);\n }\n };\n // MutationObserver로 Console 출력 변경 감지\n let observer = null;\n const setupObserver = () => {\n const currentConsole = consoleTracker.currentWidget;\n if (!currentConsole?.node)\n return;\n // 기존 observer 정리\n if (observer) {\n observer.disconnect();\n }\n observer = new MutationObserver((mutations) => {\n // 출력 영역에 변화가 있으면 에러 체크\n const hasOutputChange = mutations.some(mutation => mutation.type === 'childList' ||\n (mutation.type === 'characterData'));\n if (hasOutputChange) {\n // 약간의 딜레이 후 체크 (출력이 완전히 렌더링될 때까지)\n setTimeout(checkConsoleForErrors, 100);\n }\n });\n // Console 전체를 관찰\n observer.observe(currentConsole.node, {\n childList: true,\n subtree: true,\n characterData: true\n });\n console.log('[AgentPanel] MutationObserver set up for console');\n };\n // 현재 Console에 observer 설정\n setupObserver();\n // Console 변경 시 observer 재설정\n const onConsoleChanged = () => {\n console.log('[AgentPanel] Console changed, re-setting up observer');\n setupObserver();\n };\n consoleTracker.currentChanged?.connect(onConsoleChanged);\n // Cleanup\n return () => {\n if (observer) {\n observer.disconnect();\n }\n consoleTracker.currentChanged?.disconnect(onConsoleChanged);\n };\n }, [consoleTracker, lastConsoleError]);\n // Remove lastActiveNotebook state - just use currentWidget directly\n // Agent 실행 핸들러\n const handleAgentExecution = useCallback(async (request) => {\n // CRITICAL: Prevent concurrent agent executions\n if (isAgentRunning) {\n const errorMessage = {\n id: makeMessageId(),\n role: 'assistant',\n content: '⚠️ 이전 작업이 아직 실행 중입니다. 완료될 때까지 기다려주세요.',\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, errorMessage]);\n return;\n }\n // IMPORTANT: Use app.shell.currentWidget for reliable active tab detection\n const app = window.jupyterapp;\n let notebook = null;\n // Try to get notebook from shell.currentWidget (most reliable)\n if (app?.shell?.currentWidget) {\n const currentWidget = app.shell.currentWidget;\n if ('content' in currentWidget && currentWidget.content?.model) {\n notebook = currentWidget;\n }\n }\n // Fallback to notebookTracker\n if (!notebook) {\n notebook = notebookTracker?.currentWidget;\n }\n const sessionContext = notebook?.sessionContext;\n // 디버깅: 현재 선택된 노트북 경로 로그\n console.log('[AgentPanel] shell.currentWidget:', app?.shell?.currentWidget?.context?.path);\n console.log('[AgentPanel] tracker.currentWidget:', notebookTracker?.currentWidget?.context?.path);\n console.log('[AgentPanel] Using notebook:', notebook?.context?.path);\n console.log('[AgentPanel] Notebook title:', notebook?.title?.label);\n if (!notebook || !sessionContext) {\n // 노트북이 없으면 에러 메시지 표시\n const errorMessage = {\n id: makeMessageId(),\n role: 'assistant',\n content: '노트북을 먼저 열어주세요. Agent 실행은 활성 노트북이 필요합니다.',\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, errorMessage]);\n return;\n }\n // ★ 현재 활성화된 노트북으로 AgentOrchestrator 재생성 (탭 전환 대응)\n const config = { ...DEFAULT_AUTO_AGENT_CONFIG, executionSpeed };\n const orchestrator = new AgentOrchestrator(notebook, sessionContext, apiService, config);\n // Agent 실행 메시지 생성\n const agentMessageId = `agent-${Date.now()}`;\n const agentMessage = {\n id: agentMessageId,\n type: 'agent_execution',\n request,\n status: { phase: 'planning', message: '실행 계획 생성 중...' },\n plan: null,\n result: null,\n completedSteps: [],\n failedSteps: [],\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, agentMessage]);\n setCurrentAgentMessageId(agentMessageId);\n setIsAgentRunning(true);\n try {\n // Get current llmConfig for the agent execution\n const currentLlmConfig = llmConfig || getLLMConfig() || getDefaultLLMConfig();\n const result = await orchestrator.executeTask(request, notebook, (newStatus) => {\n // 실시간 상태 업데이트\n setMessages(prev => prev.map(msg => msg.id === agentMessageId && 'type' in msg && msg.type === 'agent_execution'\n ? {\n ...msg,\n status: newStatus,\n plan: newStatus.plan || msg.plan,\n completedSteps: newStatus.currentStep && newStatus.currentStep > 1\n ? Array.from({ length: newStatus.currentStep - 1 }, (_, i) => i + 1)\n : msg.completedSteps,\n failedSteps: newStatus.phase === 'failed' && newStatus.currentStep\n ? [...msg.failedSteps, newStatus.currentStep]\n : msg.failedSteps,\n }\n : msg));\n }, currentLlmConfig // Pass llmConfig to orchestrator\n );\n // 최종 결과 업데이트\n setMessages(prev => prev.map(msg => msg.id === agentMessageId && 'type' in msg && msg.type === 'agent_execution'\n ? {\n ...msg,\n status: {\n phase: result.success ? 'completed' : 'failed',\n message: result.finalAnswer || (result.success ? '작업 완료' : '작업 실패'),\n },\n result,\n completedSteps: result.plan?.steps.map(s => s.stepNumber) || [],\n }\n : msg));\n }\n catch (error) {\n console.log('[AgentPanel] Caught error:', error);\n console.log('[AgentPanel] Error name:', error.name);\n console.log('[AgentPanel] Error has fileSelectionMetadata:', !!error.fileSelectionMetadata);\n // Handle FILE_SELECTION_REQUIRED error\n if (error.name === 'FileSelectionError' && error.fileSelectionMetadata) {\n console.log('[AgentPanel] File selection required:', error.fileSelectionMetadata);\n // Show file selection dialog\n setFileSelectionMetadata(error.fileSelectionMetadata);\n setPendingAgentRequest(request);\n // Update message to show waiting state\n setMessages(prev => prev.map(msg => msg.id === agentMessageId && 'type' in msg && msg.type === 'agent_execution'\n ? {\n ...msg,\n status: {\n phase: 'executing',\n message: '파일 선택 대기 중...'\n },\n }\n : msg));\n // Keep isAgentRunning true to show we're paused, not failed\n return;\n }\n setMessages(prev => prev.map(msg => msg.id === agentMessageId && 'type' in msg && msg.type === 'agent_execution'\n ? {\n ...msg,\n status: { phase: 'failed', message: error.message || '실행 중 오류 발생' },\n }\n : msg));\n }\n finally {\n setIsAgentRunning(false);\n setCurrentAgentMessageId(null);\n }\n }, [notebookTracker, apiService]);\n const loadConfig = () => {\n // Load from localStorage using ApiKeyManager\n const config = getLLMConfig();\n if (!config) {\n console.log('[AgentPanel] No config in localStorage, using default');\n const defaultConfig = getDefaultLLMConfig();\n setLlmConfig(defaultConfig);\n return;\n }\n setLlmConfig(config);\n // Log loaded model configuration\n console.log('=== HDSP Agent Model Configuration (localStorage) ===');\n console.log('Provider:', config.provider);\n if (config.provider === 'gemini') {\n console.log('Gemini Model:', config.gemini?.model || 'gemini-2.5-flash (default)');\n console.log('Gemini API Key:', config.gemini?.apiKey ? '✓ Configured' : '✗ Not configured');\n }\n else if (config.provider === 'vllm') {\n console.log('vLLM Model:', config.vllm?.model || 'default');\n console.log('vLLM Endpoint:', config.vllm?.endpoint || 'http://localhost:8000');\n console.log('vLLM API Key:', config.vllm?.apiKey ? '✓ Configured' : '✗ Not configured');\n }\n else if (config.provider === 'openai') {\n console.log('OpenAI Model:', config.openai?.model || 'gpt-4');\n console.log('OpenAI API Key:', config.openai?.apiKey ? '✓ Configured' : '✗ Not configured');\n }\n console.log('====================================================');\n };\n // ═══════════════════════════════════════════════════════════════════════════\n // Python 파일 에러 수정 관련 함수들\n // ═══════════════════════════════════════════════════════════════════════════\n // JupyterLab Contents API를 통해 파일 내용 로드\n const loadFileContent = async (filePath) => {\n try {\n // PageConfig에서 base URL 가져오기\n const { PageConfig, URLExt } = await import('@jupyterlab/coreutils');\n const baseUrl = PageConfig.getBaseUrl();\n const apiUrl = URLExt.join(baseUrl, 'api/contents', filePath);\n console.log('[AgentPanel] Loading file:', filePath, 'from:', apiUrl);\n const response = await fetch(apiUrl, {\n method: 'GET',\n credentials: 'include',\n });\n if (!response.ok) {\n console.warn('[AgentPanel] Failed to load file:', filePath, response.status);\n return null;\n }\n const data = await response.json();\n return data.content;\n }\n catch (error) {\n console.error('[AgentPanel] Error loading file:', filePath, error);\n return null;\n }\n };\n // 파일에 수정된 코드 저장\n const saveFileContent = async (filePath, content) => {\n try {\n const { PageConfig, URLExt } = await import('@jupyterlab/coreutils');\n const baseUrl = PageConfig.getBaseUrl();\n const apiUrl = URLExt.join(baseUrl, 'api/contents', filePath);\n console.log('[AgentPanel] Saving file:', filePath);\n const response = await fetch(apiUrl, {\n method: 'PUT',\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n type: 'file',\n format: 'text',\n content: content,\n }),\n });\n if (!response.ok) {\n console.error('[AgentPanel] Failed to save file:', filePath, response.status);\n return false;\n }\n console.log('[AgentPanel] File saved successfully:', filePath);\n return true;\n }\n catch (error) {\n console.error('[AgentPanel] Error saving file:', filePath, error);\n return false;\n }\n };\n // Python 에러 메시지 처리 및 파일 수정 요청\n // Returns true if handled, false if should fall back to regular chat\n const handlePythonErrorFix = async (errorMessage) => {\n console.log('[AgentPanel] Handling Python error fix request');\n // 1. 에러가 발생한 파일 경로 추출\n const errorFilePath = extractErrorFilePath(errorMessage);\n const mainFilePath = extractMainFilePath(errorMessage);\n const allFilePaths = extractFilePathsFromError(errorMessage);\n console.log('[AgentPanel] Error file:', errorFilePath);\n console.log('[AgentPanel] Main file:', mainFilePath);\n console.log('[AgentPanel] All files:', allFilePaths);\n if (!errorFilePath && !mainFilePath) {\n // 파일 경로를 찾을 수 없으면 일반 채팅으로 처리\n console.log('[AgentPanel] No file path found, falling back to regular chat stream');\n return false;\n }\n // 2. 주요 파일 내용 로드\n const targetFile = errorFilePath || mainFilePath;\n const mainContent = await loadFileContent(targetFile);\n if (!mainContent) {\n console.warn('[AgentPanel] Could not load file content for:', targetFile);\n // 파일을 읽을 수 없으면 에러 메시지만으로 처리 시도\n const errorOnlyRequest = {\n action: 'fix',\n mainFile: { path: targetFile, content: '(파일 읽기 실패)' },\n errorOutput: errorMessage,\n };\n try {\n const result = await apiService.fileAction(errorOnlyRequest);\n handleFileFixResponse(result.response, result.fixedFiles);\n }\n catch (error) {\n console.error('[AgentPanel] File fix API error:', error);\n addErrorMessage('파일 수정 요청 실패: ' + error.message);\n }\n return true; // Handled (even if failed)\n }\n // 3. 관련 파일들 (imports) 로드\n const localImports = extractLocalImports(mainContent);\n const relatedFiles = [];\n // 에러 메시지에 언급된 다른 파일들도 로드\n for (const path of allFilePaths) {\n if (path !== targetFile) {\n const content = await loadFileContent(path);\n if (content) {\n relatedFiles.push({ path, content });\n }\n }\n }\n // 로컬 import 파일들도 로드\n const baseDir = targetFile.includes('/') ? targetFile.substring(0, targetFile.lastIndexOf('/')) : '';\n for (const moduleName of localImports) {\n const modulePath = baseDir ? `${baseDir}/${moduleName}.py` : `${moduleName}.py`;\n if (!allFilePaths.includes(modulePath) && !relatedFiles.some(f => f.path === modulePath)) {\n const content = await loadFileContent(modulePath);\n if (content) {\n relatedFiles.push({ path: modulePath, content });\n }\n }\n }\n console.log('[AgentPanel] Related files loaded:', relatedFiles.length);\n // 4. 파일 수정 API 호출\n const request = {\n action: 'fix',\n mainFile: { path: targetFile, content: mainContent },\n errorOutput: errorMessage,\n relatedFiles: relatedFiles.length > 0 ? relatedFiles : undefined,\n };\n try {\n setIsLoading(true);\n const result = await apiService.fileAction(request);\n handleFileFixResponse(result.response, result.fixedFiles);\n return true; // Successfully handled\n }\n catch (error) {\n console.error('[AgentPanel] File fix API error:', error);\n addErrorMessage('파일 수정 요청 실패: ' + error.message);\n return true; // Handled (even if failed)\n }\n finally {\n setIsLoading(false);\n }\n };\n // 파일 수정 응답 처리\n const handleFileFixResponse = (response, fixedFiles) => {\n console.log('[AgentPanel] File fix response received, fixed files:', fixedFiles.length);\n // Assistant 메시지로 응답 표시\n const assistantMessage = {\n id: makeMessageId('file-fix'),\n role: 'assistant',\n content: response,\n timestamp: Date.now(),\n metadata: { type: 'file_fix', fixedFiles },\n };\n setMessages(prev => [...prev, assistantMessage]);\n // 수정된 파일이 있으면 상태에 저장 (적용 버튼용)\n if (fixedFiles.length > 0) {\n setPendingFileFixes(fixedFiles);\n }\n };\n // 수정된 파일 적용\n const applyFileFix = async (fix) => {\n console.log('[AgentPanel] Applying fix to file:', fix.path);\n const success = await saveFileContent(fix.path, fix.content);\n if (success) {\n // 성공 메시지\n const successMessage = {\n id: makeMessageId('apply-success'),\n role: 'assistant',\n content: `✅ **${fix.path}** 파일이 수정되었습니다.\\n\\n파일 에디터에서 변경사항을 확인하세요.`,\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, successMessage]);\n // 적용된 파일은 pending에서 제거\n setPendingFileFixes(prev => prev.filter(f => f.path !== fix.path));\n }\n else {\n addErrorMessage(`파일 저장 실패: ${fix.path}`);\n }\n };\n // 에러 메시지 추가 헬퍼\n const addErrorMessage = (message) => {\n const errorMessage = {\n id: makeMessageId('error'),\n role: 'assistant',\n content: `⚠️ ${message}`,\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, errorMessage]);\n };\n const handleSaveConfig = (config) => {\n console.log('[AgentPanel] Saving config to localStorage');\n console.log('Provider:', config.provider);\n console.log('API Key configured:', hasValidApiKey(config) ? '✓ Yes' : '✗ No');\n // Save to localStorage using ApiKeyManager\n saveLLMConfig(config);\n // Update state\n setLlmConfig(config);\n console.log('[AgentPanel] Config saved successfully');\n };\n // File selection handlers\n const handleFileSelect = async (index) => {\n console.log('[AgentPanel] File selected:', index);\n if (!fileSelectionMetadata || !pendingAgentRequest) {\n console.error('[AgentPanel] Missing file selection metadata or pending request');\n return;\n }\n // Get selected file info (index is 1-based from the UI button click)\n const selectedFile = fileSelectionMetadata.options[index - 1];\n if (!selectedFile) {\n console.error('[AgentPanel] Invalid file selection index:', index);\n return;\n }\n console.log('[AgentPanel] Selected file:', selectedFile.path);\n // Close dialog\n setFileSelectionMetadata(null);\n const originalRequest = pendingAgentRequest;\n setPendingAgentRequest(null);\n // Update the agent message to show file was selected\n setMessages(prev => prev.map(msg => {\n if ('type' in msg && msg.type === 'agent_execution' && msg.status.phase === 'executing') {\n return {\n ...msg,\n status: {\n phase: 'executing',\n message: `파일 선택됨: ${selectedFile.relative}\\n실행 재개 중...`\n },\n };\n }\n return msg;\n }));\n // Re-execute the agent with the selected file path explicitly mentioned\n // Modify the request to include the selected file path\n const modifiedRequest = `${originalRequest} (파일 경로: ${selectedFile.path})`;\n console.log('[AgentPanel] Re-executing agent with modified request:', modifiedRequest);\n // Resume execution by calling handleAgentExecution with the modified request\n // Note: This will create a new agent message, but the existing one should be marked as cancelled\n setMessages(prev => prev.map(msg => {\n if ('type' in msg && msg.type === 'agent_execution' && msg.status.phase === 'executing') {\n return {\n ...msg,\n status: {\n phase: 'completed',\n message: `파일 선택됨: ${selectedFile.relative}\\n새 실행으로 재개됩니다...`\n },\n };\n }\n return msg;\n }));\n // Reset agent running state to allow new execution\n setIsAgentRunning(false);\n setCurrentAgentMessageId(null);\n // Start new agent execution with explicit file path\n await handleAgentExecution(modifiedRequest);\n };\n const handleFileSelectCancel = () => {\n console.log('[AgentPanel] File selection cancelled');\n // Update the agent message to show cancellation\n setMessages(prev => prev.map(msg => {\n if ('type' in msg && msg.type === 'agent_execution' && msg.status.phase === 'executing') {\n return {\n ...msg,\n status: {\n phase: 'failed',\n message: '사용자가 파일 선택을 취소했습니다.'\n },\n };\n }\n return msg;\n }));\n setFileSelectionMetadata(null);\n setPendingAgentRequest(null);\n setIsAgentRunning(false);\n setCurrentAgentMessageId(null);\n };\n // Auto-scroll to bottom when messages change or streaming\n useEffect(() => {\n if (isStreaming) {\n // 스트리밍 중에는 즉시 스크롤\n messagesEndRef.current?.scrollIntoView({ behavior: 'auto' });\n }\n else {\n // 일반 메시지 추가 시 부드러운 스크롤\n messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });\n }\n }, [messages, isStreaming]);\n const handleNextItemSelection = async (nextText) => {\n const trimmed = nextText.trim();\n if (!trimmed)\n return;\n // Check if there's a pending interrupt (HITL approval waiting)\n const hasPendingInterrupt = interruptData !== null;\n // Check if all todos are completed\n const allTodosCompleted = todos.length === 0 || todos.every(t => t.status === 'completed');\n // Block if there's a pending interrupt OR if agent is running with incomplete todos\n // Allow if all todos are completed even if streaming is finishing up\n if (hasPendingInterrupt || (isAgentRunning && !allTodosCompleted)) {\n showNotification('다른 작업이 진행 중입니다. 완료 후 다시 시도해주세요.', 'warning');\n return;\n }\n await sendChatMessage({\n displayContent: trimmed,\n clearInput: true\n });\n };\n const sendChatMessage = async ({ displayContent, llmPrompt, textarea, clearInput = true }) => {\n const displayContentText = displayContent || (llmPrompt ? '셀 분석 요청' : '');\n if (!displayContentText && !llmPrompt)\n return;\n // Check if API key is configured before sending\n let currentConfig = llmConfig;\n if (!currentConfig) {\n // Config not loaded yet, try to load from localStorage\n currentConfig = getLLMConfig() || getDefaultLLMConfig();\n setLlmConfig(currentConfig);\n }\n // Check API key using ApiKeyManager\n const hasApiKey = hasValidApiKey(currentConfig);\n const autoApproveEnabled = getAutoApproveEnabled(currentConfig);\n if (!hasApiKey) {\n // Show error message and open settings\n const providerName = currentConfig?.provider || 'LLM';\n const errorMessage = {\n id: makeMessageId(),\n role: 'assistant',\n content: `API Key가 설정되지 않았습니다.\\n\\n${providerName === 'gemini' ? 'Gemini' : providerName === 'openai' ? 'OpenAI' : 'vLLM'} API Key를 먼저 설정해주세요.\\n\\n설정 버튼을 클릭하여 API Key를 입력하세요.`,\n timestamp: Date.now()\n };\n setMessages(prev => [...prev, errorMessage]);\n setShowSettings(true);\n return;\n }\n const userMessage = {\n id: makeMessageId(),\n role: 'user',\n content: displayContentText,\n timestamp: Date.now()\n };\n setMessages(prev => [...prev, userMessage]);\n if (clearInput && displayContentText) {\n setInput('');\n }\n setIsLoading(true);\n setIsStreaming(true);\n // Clear todos only when starting a new task (all completed or no todos)\n // Keep todos if there are pending/in_progress items (continuation of current task)\n const hasActiveTodos = todos.some(t => t.status === 'pending' || t.status === 'in_progress');\n if (!hasActiveTodos) {\n setTodos([]);\n }\n setDebugStatus(null);\n // Clear the data attribute and ref after using it\n if (textarea && llmPrompt) {\n textarea.removeAttribute('data-llm-prompt');\n pendingLlmPromptRef.current = null;\n }\n // Create assistant message ID for streaming updates\n const assistantMessageId = makeMessageId('assistant');\n let streamedContent = '';\n setStreamingMessageId(assistantMessageId);\n // Add empty assistant message that will be updated during streaming\n const initialAssistantMessage = {\n id: assistantMessageId,\n role: 'assistant',\n content: '',\n timestamp: Date.now()\n };\n setMessages(prev => [...prev, initialAssistantMessage]);\n try {\n // Use LLM prompt if available, otherwise use the display content\n const messageToSend = llmPrompt || displayContentText;\n console.log('[AgentPanel] Sending message with mode:', inputMode, 'agentThreadId:', agentThreadId);\n // Chat 모드: 단순 Q&A (sendChatStream)\n // Agent V2 모드 또는 그 외: LangChain Deep Agent (sendAgentV2Stream)\n if (inputMode === 'chat') {\n // 단순 Chat 모드 - /chat/stream 사용\n await apiService.sendChatStream({\n message: messageToSend,\n conversationId: conversationId || undefined,\n llmConfig: currentConfig\n }, \n // onChunk callback\n (chunk) => {\n streamedContent += chunk;\n setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)\n ? { ...msg, content: streamedContent }\n : msg));\n }, \n // onMetadata callback\n (metadata) => {\n if (metadata.conversationId && !conversationId) {\n setConversationId(metadata.conversationId);\n }\n });\n }\n else {\n // Agent V2 모드 - /agent/langchain/stream 사용 (HITL, Todo, 도구 실행)\n await apiService.sendAgentV2Stream({\n message: messageToSend,\n conversationId: conversationId || undefined,\n llmConfig: currentConfig // Include API keys with request\n }, \n // onChunk callback - update message content incrementally\n (chunk) => {\n streamedContent += chunk;\n setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)\n ? { ...msg, content: streamedContent }\n : msg));\n }, \n // onMetadata callback - update conversationId and metadata\n (metadata) => {\n if (metadata.conversationId && !conversationId) {\n setConversationId(metadata.conversationId);\n }\n if (metadata.provider || metadata.model) {\n setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)\n ? {\n ...msg,\n metadata: {\n ...msg.metadata,\n provider: metadata.provider,\n model: metadata.model\n }\n }\n : msg));\n }\n }, \n // onDebug callback - show debug status in gray\n (status) => {\n setDebugStatus(status);\n }, \n // onInterrupt callback - show approval dialog\n (interrupt) => {\n approvalPendingRef.current = true;\n // Capture threadId from interrupt for context persistence\n if (interrupt.threadId && !agentThreadId) {\n setAgentThreadId(interrupt.threadId);\n console.log('[AgentPanel] Captured agentThreadId from interrupt:', interrupt.threadId);\n }\n // Auto-approve search/file/resource tools - execute immediately without user interaction\n if (interrupt.action === 'search_workspace_tool'\n || interrupt.action === 'search_notebook_cells_tool'\n || interrupt.action === 'check_resource_tool'\n || interrupt.action === 'list_files_tool'\n || interrupt.action === 'read_file_tool') {\n void handleAutoToolInterrupt(interrupt);\n return;\n }\n if (autoApproveEnabled) {\n // 자동 승인 모드일 때도 인터럽트 메시지를 생성하여 코드블럭으로 표시\n upsertInterruptMessage(interrupt, true);\n void resumeFromInterrupt(interrupt, 'approve');\n return;\n }\n if (interrupt.action === 'jupyter_cell_tool' && interrupt.args?.code) {\n const shouldQueue = shouldExecuteInNotebook(interrupt.args.code);\n if (isAutoApprovedCode(interrupt.args.code)) {\n if (shouldQueue) {\n queueApprovalCell(interrupt.args.code);\n }\n void resumeFromInterrupt(interrupt, 'approve');\n return;\n }\n if (shouldQueue) {\n queueApprovalCell(interrupt.args.code);\n }\n }\n setInterruptData(interrupt);\n upsertInterruptMessage(interrupt);\n setIsLoading(false);\n setIsStreaming(false);\n }, \n // onTodos callback - update todo list UI\n (newTodos) => {\n setTodos(newTodos);\n }, \n // onDebugClear callback - clear debug status\n () => {\n setDebugStatus(null);\n }, \n // onToolCall callback - add cells to notebook\n handleToolCall, \n // onComplete callback - capture thread_id for context persistence\n (data) => {\n if (data.threadId) {\n setAgentThreadId(data.threadId);\n console.log('[AgentPanel] Captured agentThreadId for context persistence:', data.threadId);\n }\n }, \n // threadId - pass existing thread_id to continue context\n agentThreadId || undefined);\n }\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'Failed to send message';\n setDebugStatus(`오류: ${message}`);\n // Update the assistant message with error\n setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)\n ? {\n ...msg,\n content: streamedContent + `\\n\\nError: ${message}`\n }\n : msg));\n }\n finally {\n setIsLoading(false);\n setIsStreaming(false);\n setStreamingMessageId(null);\n // Keep completed todos visible after the run\n }\n };\n // Extract and store code blocks from messages, setup button listeners\n useEffect(() => {\n // Use a small delay to ensure DOM is updated after message rendering\n const timeoutId = setTimeout(() => {\n // Find messages container - try multiple selectors\n const messagesContainer = document.querySelector('.jp-agent-messages') ||\n messagesEndRef.current?.parentElement ||\n document.querySelector('[class*=\"jp-agent-messages\"]');\n if (!messagesContainer) {\n console.log('[AgentPanel] Messages container not found');\n return;\n }\n // Extract code blocks from all assistant messages\n const codeBlocks = [];\n const containers = messagesContainer.querySelectorAll('.code-block-container');\n console.log(`[AgentPanel] Found ${containers.length} code block containers`);\n containers.forEach(container => {\n const blockId = container.getAttribute('data-block-id');\n if (!blockId) {\n console.warn('[AgentPanel] Code block container missing data-block-id');\n return;\n }\n const codeElement = container.querySelector(`#${blockId}`);\n if (!codeElement) {\n console.warn(`[AgentPanel] Code element #${blockId} not found`);\n return;\n }\n const codeText = codeElement.textContent || '';\n const langElement = container.querySelector('.code-block-language');\n const language = langElement?.textContent?.toLowerCase() || 'python';\n codeBlocks.push({\n id: blockId,\n code: codeText,\n language: language\n });\n });\n // Update code blocks ref\n allCodeBlocksRef.current = codeBlocks;\n console.log(`[AgentPanel] Stored ${codeBlocks.length} code blocks`, codeBlocks.map(b => b.id));\n // Use event delegation - attach single listener to container\n const handleContainerClick = async (e) => {\n const target = e.target;\n const nextItem = target.closest('.jp-next-items-item');\n if (nextItem) {\n e.stopPropagation();\n e.preventDefault();\n const subject = nextItem.querySelector('.jp-next-items-subject')?.textContent?.trim() || '';\n const description = nextItem.querySelector('.jp-next-items-description')?.textContent?.trim() || '';\n const nextText = subject && description\n ? `${subject}\\n\\n${description}`\n : (subject || description);\n if (nextText) {\n void handleNextItemSelection(nextText);\n }\n return;\n }\n // Handle expand/collapse button\n if (target.classList.contains('code-block-toggle') || target.closest('.code-block-toggle')) {\n const button = target.classList.contains('code-block-toggle')\n ? target\n : target.closest('.code-block-toggle');\n e.stopPropagation();\n e.preventDefault();\n const container = button.closest('.code-block-container');\n if (!container)\n return;\n const isExpanded = container.classList.toggle('is-expanded');\n button.setAttribute('aria-expanded', String(isExpanded));\n button.setAttribute('title', isExpanded ? '접기' : '전체 보기');\n button.setAttribute('aria-label', isExpanded ? '접기' : '전체 보기');\n const icon = button.querySelector('.code-block-toggle-icon');\n if (icon) {\n icon.textContent = isExpanded ? '▴' : '▾';\n }\n return;\n }\n // Handle copy button\n if (target.classList.contains('code-block-copy') || target.closest('.code-block-copy')) {\n const button = target.classList.contains('code-block-copy')\n ? target\n : target.closest('.code-block-copy');\n e.stopPropagation();\n e.preventDefault();\n const blockId = button.getAttribute('data-block-id');\n if (!blockId)\n return;\n const block = allCodeBlocksRef.current.find(b => b.id === blockId);\n if (!block)\n return;\n try {\n await navigator.clipboard.writeText(block.code);\n const originalText = button.textContent;\n button.textContent = '복사됨!';\n setTimeout(() => {\n button.textContent = originalText || '복사';\n }, 2000);\n }\n catch (error) {\n console.error('Failed to copy code:', error);\n showNotification('복사에 실패했습니다.', 'error');\n }\n return;\n }\n // Handle apply button\n if (target.classList.contains('code-block-apply') || target.closest('.code-block-apply')) {\n const button = target.classList.contains('code-block-apply')\n ? target\n : target.closest('.code-block-apply');\n e.stopPropagation();\n e.preventDefault();\n const blockId = button.getAttribute('data-block-id');\n console.log(`[AgentPanel] Apply button clicked via delegation, blockId: ${blockId}`);\n if (!blockId) {\n showNotification('코드 블록 ID를 찾을 수 없습니다.', 'error');\n return;\n }\n const block = allCodeBlocksRef.current.find(b => b.id === blockId);\n if (!block) {\n console.error('[AgentPanel] Block not found!', {\n clickedId: blockId,\n availableIds: allCodeBlocksRef.current.map(b => b.id),\n blocksCount: allCodeBlocksRef.current.length\n });\n showNotification('코드를 찾을 수 없습니다.', 'error');\n return;\n }\n console.log(`[AgentPanel] Applying code to cell via delegation, code length: ${block.code.length}`);\n await applyCodeToCell(block.code, blockId, button);\n return;\n }\n };\n // Attach single event listener to container\n messagesContainer.addEventListener('click', handleContainerClick);\n // Cleanup - store handler reference for cleanup\n messagesContainer._agentPanelClickHandler = handleContainerClick;\n }, 100); // Small delay to ensure DOM is ready\n // Cleanup\n return () => {\n clearTimeout(timeoutId);\n // Remove event listener on cleanup\n const messagesContainer = document.querySelector('.jp-agent-messages') ||\n messagesEndRef.current?.parentElement ||\n document.querySelector('[class*=\"jp-agent-messages\"]');\n if (messagesContainer && messagesContainer._agentPanelClickHandler) {\n messagesContainer.removeEventListener('click', messagesContainer._agentPanelClickHandler);\n delete messagesContainer._agentPanelClickHandler;\n }\n };\n }, [messages]);\n // Helper: Get notification background color\n const getNotificationColor = (type) => {\n switch (type) {\n case 'error': return '#f56565';\n case 'warning': return '#ed8936';\n default: return '#4299e1';\n }\n };\n // Helper: Create and show notification element\n const createNotificationElement = (message, backgroundColor) => {\n const notification = document.createElement('div');\n notification.style.cssText = `\n position: fixed;\n top: 20px;\n right: 20px;\n padding: 12px 16px;\n border-radius: 6px;\n color: white;\n font-size: 14px;\n font-weight: 500;\n z-index: 10001;\n opacity: 0;\n transition: opacity 0.3s ease;\n max-width: 300px;\n word-wrap: break-word;\n background: ${backgroundColor};\n `;\n notification.textContent = message;\n return notification;\n };\n // Helper: Animate notification in and out\n const animateNotification = (notification) => {\n setTimeout(() => notification.style.opacity = '1', 10);\n setTimeout(() => {\n notification.style.opacity = '0';\n setTimeout(() => {\n if (notification.parentNode) {\n notification.parentNode.removeChild(notification);\n }\n }, 300);\n }, 3000);\n };\n // Show notification\n const showNotification = (message, type = 'info') => {\n const notification = createNotificationElement(message, getNotificationColor(type));\n document.body.appendChild(notification);\n animateNotification(notification);\n };\n // Show tooltip on a specific cell\n const showCellTooltip = (cellElement, message) => {\n // Remove any existing tooltip\n const existingTooltip = document.querySelector('.jp-agent-cell-tooltip');\n if (existingTooltip) {\n existingTooltip.remove();\n }\n const tooltip = document.createElement('div');\n tooltip.className = 'jp-agent-cell-tooltip';\n tooltip.textContent = message;\n tooltip.style.cssText = `\n position: absolute;\n top: 0;\n left: 50%;\n transform: translateX(-50%);\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n padding: 8px 16px;\n border-radius: 0 0 8px 8px;\n font-size: 13px;\n font-weight: 500;\n box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);\n z-index: 1000;\n opacity: 0;\n transition: opacity 0.3s ease;\n pointer-events: none;\n white-space: nowrap;\n `;\n // Make cell position relative if not already\n const originalPosition = cellElement.style.position;\n if (!originalPosition || originalPosition === 'static') {\n cellElement.style.position = 'relative';\n }\n cellElement.appendChild(tooltip);\n // Fade in\n requestAnimationFrame(() => {\n tooltip.style.opacity = '1';\n });\n // Fade out and remove after 2 seconds\n setTimeout(() => {\n tooltip.style.opacity = '0';\n setTimeout(() => {\n tooltip.remove();\n // Restore original position\n if (!originalPosition || originalPosition === 'static') {\n cellElement.style.position = originalPosition || '';\n }\n }, 300);\n }, 2000);\n };\n // Apply code to Jupyter cell\n const applyCodeToCell = async (code, blockId, button) => {\n console.log('[AgentPanel] applyCodeToCell called', { codeLength: code.length, blockId });\n const originalText = button.textContent;\n try {\n button.disabled = true;\n button.textContent = '적용 중...';\n const app = window.jupyterapp;\n if (!app) {\n console.error('[AgentPanel] jupyterapp not found in window');\n showNotification('JupyterLab을 찾을 수 없습니다.', 'error');\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n return;\n }\n console.log('[AgentPanel] Found jupyterapp');\n // Try to get notebook tracker from app\n // The notebookTracker is passed to cellButtonsPlugin, so we need to access it differently\n // Use shell to find current notebook widget\n const shell = app.shell;\n const currentWidget = shell.currentWidget;\n // Check if current widget is a notebook\n if (currentWidget && 'content' in currentWidget) {\n const notebook = currentWidget.content;\n // Check if it's a notebook (has model and cells)\n if (notebook && 'model' in notebook && notebook.model && 'cells' in notebook.model) {\n await applyCodeToNotebookCell(code, notebook, blockId, button, originalText);\n return;\n }\n }\n // Fallback: show cell selector dialog\n showCellSelectorDialog(code, button, originalText);\n }\n catch (error) {\n console.error('Failed to apply code to cell:', error);\n showNotification('코드 적용에 실패했습니다.', 'error');\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n }\n };\n // Helper: Try different methods to set cell source\n const setCellSource = (cellModel, code) => {\n if (cellModel.sharedModel && typeof cellModel.sharedModel.setSource === 'function') {\n cellModel.sharedModel.setSource(code);\n }\n else if (cellModel.setSource && typeof cellModel.setSource === 'function') {\n cellModel.setSource(code);\n }\n else if (cellModel.value && typeof cellModel.value.setText === 'function') {\n cellModel.value.setText(code);\n }\n else if (cellModel.value && cellModel.value.text !== undefined) {\n cellModel.value.text = code;\n }\n else {\n throw new Error('Unable to set cell source - no compatible method found');\n }\n };\n // Helper: Reset button state\n const resetButtonState = (button, originalText) => {\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n };\n // Apply code to a specific notebook cell\n const applyCodeToNotebookCell = async (code, notebook, blockId, button, originalText) => {\n try {\n // Get widgets from notebook\n const cells = notebook.widgets || [];\n const modelCellsLength = notebook.model?.cells?.length || 0;\n console.log('[AgentPanel] applyCodeToNotebookCell called', {\n currentCellIndex: currentCellIndexRef.current,\n currentCellId: currentCellIdRef.current,\n blockId,\n widgetsLength: cells.length,\n modelCellsLength: modelCellsLength\n });\n // Try cell index first (most reliable)\n if (currentCellIndexRef.current !== null && currentCellIndexRef.current !== undefined) {\n console.log('[AgentPanel] Using cell index:', currentCellIndexRef.current);\n // Safety check: if widgets array is empty but model has cells, there might be a rendering issue\n if (cells.length === 0 && modelCellsLength > 0) {\n console.warn('[AgentPanel] Widgets not rendered yet, model has', modelCellsLength, 'cells');\n // Fall through to show selector dialog\n }\n else if (currentCellIndexRef.current >= 0 && currentCellIndexRef.current < cells.length) {\n const cell = cells[currentCellIndexRef.current];\n const cellModel = cell.model || cell;\n console.log('[AgentPanel] Found cell at index, applying code...');\n try {\n setCellSource(cellModel, code);\n console.log('[AgentPanel] Code applied successfully!');\n // Show tooltip on the target cell\n if (cell.node) {\n showCellTooltip(cell.node, '✓ 코드가 적용되었습니다');\n }\n else {\n showNotification('코드가 셀에 적용되었습니다!', 'info');\n }\n resetButtonState(button, originalText);\n // Clear the cell index after successful application\n currentCellIndexRef.current = null;\n currentCellIdRef.current = null;\n return;\n }\n catch (updateError) {\n console.error('Failed to update cell content:', updateError);\n showNotification('셀 내용 업데이트 실패: ' + updateError.message, 'error');\n resetButtonState(button, originalText);\n return;\n }\n }\n else {\n console.error('[AgentPanel] Cell index out of bounds:', {\n currentIndex: currentCellIndexRef.current,\n widgetsLength: cells.length,\n modelCellsLength: modelCellsLength\n });\n // Don't show error, fall through to selector dialog\n currentCellIndexRef.current = null;\n currentCellIdRef.current = null;\n }\n }\n // Fallback: show selector dialog\n console.log('[AgentPanel] Showing cell selector dialog');\n showCellSelectorDialog(code, button, originalText);\n }\n catch (error) {\n console.error('Failed to apply code:', error);\n showNotification('코드 적용에 실패했습니다.', 'error');\n resetButtonState(button, originalText);\n }\n };\n // Show cell selector dialog (similar to chrome_agent)\n const showCellSelectorDialog = (code, button, originalText) => {\n try {\n const app = window.jupyterapp;\n if (!app) {\n showNotification('JupyterLab을 찾을 수 없습니다.', 'error');\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n return;\n }\n // Find current notebook from shell\n const shell = app.shell;\n const currentWidget = shell.currentWidget;\n if (!currentWidget || !('content' in currentWidget)) {\n showNotification('활성 노트북을 찾을 수 없습니다.', 'warning');\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n return;\n }\n const notebook = currentWidget.content;\n if (!notebook || !('model' in notebook) || !notebook.model || !('cells' in notebook.model)) {\n showNotification('활성 노트북을 찾을 수 없습니다.', 'warning');\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n return;\n }\n const cells = notebook.widgets || [];\n const codeCells = [];\n // Collect code cells\n cells.forEach((cell, index) => {\n const cellModel = cell.model || cell;\n if (cellModel.type === 'code') {\n const cellId = cellModel.metadata?.get?.('jupyterAgentCellId') ||\n cellModel.id ||\n `cell-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n // Ensure cell has ID\n try {\n if (!cellModel.metadata?.get?.('jupyterAgentCellId')) {\n if (cellModel.metadata?.set) {\n cellModel.metadata.set('jupyterAgentCellId', cellId);\n }\n }\n }\n catch (e) {\n // Metadata might not be accessible, continue anyway\n }\n const content = (cellModel.sharedModel?.getSource?.() ||\n cellModel.value?.text ||\n '').toString();\n const preview = content.substring(0, 100).replace(/\\n/g, ' ');\n codeCells.push({\n cell,\n index,\n id: cellId,\n preview\n });\n }\n });\n if (codeCells.length === 0) {\n showNotification('코드 셀을 찾을 수 없습니다.', 'warning');\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n return;\n }\n // Remove existing dialog\n const existingDialog = document.querySelector('.jp-agent-cell-selector-dialog');\n if (existingDialog) {\n existingDialog.remove();\n }\n // Create dialog overlay\n const dialogOverlay = document.createElement('div');\n dialogOverlay.className = 'jp-agent-cell-selector-dialog';\n dialogOverlay.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.3);\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n `;\n // Create dialog container\n const dialogContainer = document.createElement('div');\n dialogContainer.style.cssText = `\n background: #fafafa;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n padding: 24px;\n max-width: 500px;\n width: 90%;\n max-height: 80vh;\n overflow-y: auto;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n `;\n // Create cell list HTML\n const cellListHTML = codeCells.map(({ index, id, preview }) => {\n return `\n <div class=\"jp-agent-cell-selector-item\" data-cell-id=\"${id}\" style=\"\n padding: 12px;\n margin-bottom: 8px;\n background: white;\n border: 2px solid #e0e0e0;\n border-radius: 4px;\n cursor: pointer;\n transition: all 0.2s ease;\n \">\n <div style=\"display: flex; align-items: center; gap: 8px; margin-bottom: 4px;\">\n <span style=\"\n background: #667eea;\n color: white;\n padding: 2px 8px;\n border-radius: 12px;\n font-size: 11px;\n font-weight: 600;\n \">셀 ${index + 1}</span>\n </div>\n <div style=\"font-size: 12px; color: #757575; font-family: 'Menlo', monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\">\n ${escapeHtml(preview)}${preview.length >= 100 ? '...' : ''}\n </div>\n </div>\n `;\n }).join('');\n // Dialog content\n dialogContainer.innerHTML = `\n <div style=\"margin-bottom: 20px;\">\n <h3 style=\"margin: 0 0 8px 0; color: #424242; font-size: 16px; font-weight: 500;\">\n 코드를 적용할 셀 선택\n </h3>\n <p style=\"margin: 0; color: #757575; font-size: 13px;\">\n AI가 생성한 코드를 적용할 셀을 선택하세요\n </p>\n </div>\n <div class=\"jp-agent-cell-list\" style=\"margin-bottom: 20px;\">\n ${cellListHTML}\n </div>\n <div style=\"display: flex; gap: 12px; justify-content: flex-end;\">\n <button class=\"jp-agent-cell-selector-cancel-btn\" style=\"\n background: transparent;\n color: #616161;\n border: 1px solid #d1d5db;\n border-radius: 3px;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n \">취소</button>\n </div>\n `;\n dialogOverlay.appendChild(dialogContainer);\n document.body.appendChild(dialogOverlay);\n // Cell item click handlers\n const cellItems = dialogContainer.querySelectorAll('.jp-agent-cell-selector-item');\n cellItems.forEach(item => {\n item.addEventListener('click', () => {\n const cellId = item.getAttribute('data-cell-id');\n if (!cellId)\n return;\n // Find the cell\n const cellInfo = codeCells.find(c => c.id === cellId);\n if (!cellInfo)\n return;\n const cellModel = cellInfo.cell.model || cellInfo.cell;\n // Apply code to cell\n try {\n setCellSource(cellModel, code);\n dialogOverlay.remove();\n // Show tooltip on the target cell\n if (cellInfo.cell.node) {\n showCellTooltip(cellInfo.cell.node, '✓ 코드가 적용되었습니다');\n }\n else {\n showNotification('코드가 셀에 적용되었습니다!', 'info');\n }\n resetButtonState(button, originalText);\n }\n catch (error) {\n console.error('Failed to apply code to cell:', error);\n showNotification('코드 적용에 실패했습니다.', 'error');\n resetButtonState(button, originalText);\n }\n });\n // Hover effects\n item.addEventListener('mouseenter', () => {\n item.style.borderColor = '#667eea';\n item.style.background = '#f8f9ff';\n });\n item.addEventListener('mouseleave', () => {\n item.style.borderColor = '#e0e0e0';\n item.style.background = 'white';\n });\n });\n // Cancel button\n const cancelBtn = dialogContainer.querySelector('.jp-agent-cell-selector-cancel-btn');\n if (cancelBtn) {\n cancelBtn.addEventListener('click', () => {\n dialogOverlay.remove();\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n });\n cancelBtn.addEventListener('mouseenter', () => {\n cancelBtn.style.background = '#f5f5f5';\n cancelBtn.style.borderColor = '#9ca3af';\n });\n cancelBtn.addEventListener('mouseleave', () => {\n cancelBtn.style.background = 'transparent';\n cancelBtn.style.borderColor = '#d1d5db';\n });\n }\n // Close on overlay click\n dialogOverlay.addEventListener('click', (e) => {\n if (e.target === dialogOverlay) {\n dialogOverlay.remove();\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n }\n });\n // ESC key to close\n const handleEsc = (e) => {\n if (e.key === 'Escape') {\n dialogOverlay.remove();\n document.removeEventListener('keydown', handleEsc);\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n }\n };\n document.addEventListener('keydown', handleEsc);\n }\n catch (error) {\n console.error('Failed to show cell selector:', error);\n showNotification('셀 선택 다이얼로그 표시에 실패했습니다.', 'error');\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n }\n };\n // Helper function to escape HTML\n const escapeHtml = (text) => {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n };\n const isAutoApprovedCode = (code) => {\n const lines = code\n .split('\\n')\n .map(line => line.trim())\n .filter(line => line.length > 0 && !line.startsWith('#'));\n if (lines.length === 0) {\n return true;\n }\n if (lines.some(line => (line.startsWith('!')\n || line.startsWith('%%bash')\n || line.startsWith('%%sh')\n || line.startsWith('%%shell')))) {\n return false;\n }\n const disallowedPatterns = [\n /(^|[^=!<>])=([^=]|$)/,\n /\\.read_[a-zA-Z0-9_]*\\s*\\(/,\n /\\bread_[a-zA-Z0-9_]*\\s*\\(/,\n /\\.to_[a-zA-Z0-9_]*\\s*\\(/,\n ];\n if (lines.some(line => disallowedPatterns.some(pattern => pattern.test(line)))) {\n return false;\n }\n const allowedPatterns = [\n /^print\\(.+\\)$/,\n /^display\\(.+\\)$/,\n /^df\\.(head|info|describe)\\s*\\(.*\\)$/,\n /^display\\(df\\.(head|info|describe)\\s*\\(.*\\)\\)$/,\n /^df\\.(tail|sample)\\s*\\(.*\\)$/,\n /^display\\(df\\.(tail|sample)\\s*\\(.*\\)\\)$/,\n /^df\\.(shape|columns|dtypes)$/,\n /^import\\s+pandas\\s+as\\s+pd$/,\n ];\n return lines.every(line => allowedPatterns.some(pattern => pattern.test(line)));\n };\n const userRequestedNotebookExecution = () => {\n const lastUserMessage = [...messages]\n .reverse()\n .find((msg) => isChatMessage(msg) && msg.role === 'user');\n const content = lastUserMessage?.content ?? '';\n return /노트북|셀|cell|notebook|jupyter/i.test(content);\n };\n const isShellCell = (code) => {\n const lines = code.split('\\n');\n const firstLine = lines.find(line => line.trim().length > 0)?.trim() || '';\n if (firstLine.startsWith('%%bash')\n || firstLine.startsWith('%%sh')\n || firstLine.startsWith('%%shell')) {\n return true;\n }\n return lines.some(line => line.trim().startsWith('!'));\n };\n const extractShellCommand = (code) => {\n const lines = code.split('\\n');\n const firstNonEmptyIndex = lines.findIndex(line => line.trim().length > 0);\n if (firstNonEmptyIndex === -1) {\n return '';\n }\n const firstLine = lines[firstNonEmptyIndex].trim();\n if (firstLine.startsWith('%%bash')\n || firstLine.startsWith('%%sh')\n || firstLine.startsWith('%%shell')) {\n const script = lines.slice(firstNonEmptyIndex + 1).join('\\n').trim();\n if (!script) {\n return '';\n }\n const escaped = script\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/'/g, \"\\\\'\")\n .replace(/\\r?\\n/g, '\\\\n');\n return `bash -lc $'${escaped}'`;\n }\n const shellLines = lines\n .map(line => line.trim())\n .filter(line => line.startsWith('!'))\n .map(line => line.replace(/^!+/, '').trim())\n .filter(Boolean);\n return shellLines.join('\\n');\n };\n const buildPythonCommand = (code) => (`python3 -c ${JSON.stringify(code)}`);\n const shouldExecuteInNotebook = (code) => {\n const notebook = getActiveNotebookPanel();\n if (!notebook) {\n return false;\n }\n if (isShellCell(code) && !userRequestedNotebookExecution()) {\n return false;\n }\n return true;\n };\n const truncateOutputLines = (output, maxLines = 2) => {\n const lines = output.split(/\\r?\\n/).filter(line => line.length > 0);\n const text = lines.slice(0, maxLines).join('\\n');\n return { text, truncated: lines.length > maxLines };\n };\n const createCommandOutputMessage = (command) => {\n const messageId = makeMessageId('command-output');\n const outputMessage = {\n id: messageId,\n role: 'system',\n content: `🐚 ${command}\\n`,\n timestamp: Date.now(),\n metadata: {\n kind: 'shell-output',\n command\n }\n };\n setMessages(prev => [...prev, outputMessage]);\n return messageId;\n };\n const appendCommandOutputMessage = (messageId, text, stream) => {\n if (!text)\n return;\n const prefix = stream === 'stderr' ? '[stderr] ' : '';\n setMessages(prev => prev.map(msg => {\n if (msg.id !== messageId || !('role' in msg)) {\n return msg;\n }\n const chatMsg = msg;\n return {\n ...chatMsg,\n content: `${chatMsg.content}${prefix}${text}`\n };\n }));\n };\n const executeSubprocessCommand = async (command, timeout, onOutput, stdin) => {\n try {\n const result = await apiService.executeCommandStream(command, { timeout, onOutput, stdin });\n const stdout = typeof result.stdout === 'string' ? result.stdout : '';\n const stderr = typeof result.stderr === 'string' ? result.stderr : '';\n const combined = [stdout, stderr].filter(Boolean).join('\\n');\n const summary = truncateOutputLines(combined, 2);\n const truncated = summary.truncated || Boolean(result.truncated);\n const output = summary.text || '(no output)';\n if (result.success) {\n return {\n success: true,\n output,\n returncode: result.returncode ?? null,\n command,\n truncated\n };\n }\n const errorText = summary.text || result.error || stderr || 'Command failed';\n return {\n success: false,\n output,\n error: errorText,\n returncode: result.returncode ?? null,\n command,\n truncated\n };\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'Command execution failed';\n if (onOutput) {\n onOutput({ stream: 'stderr', text: `${message}\\n` });\n }\n const summary = truncateOutputLines(message, 2);\n return {\n success: false,\n output: '',\n error: summary.text || 'Command execution failed',\n returncode: null,\n command,\n truncated: summary.truncated\n };\n }\n };\n const executeCodeViaSubprocess = async (code, timeout) => {\n const isShell = isShellCell(code);\n const command = isShell ? extractShellCommand(code) : buildPythonCommand(code);\n if (!command) {\n return {\n success: false,\n output: '',\n error: isShell ? 'Shell command is empty' : 'Python command is empty',\n returncode: null,\n command,\n truncated: false,\n execution_method: 'subprocess'\n };\n }\n const result = await executeSubprocessCommand(command, timeout);\n return {\n ...result,\n execution_method: 'subprocess'\n };\n };\n const upsertInterruptMessage = (interrupt, autoApproved) => {\n const interruptMessageId = interruptMessageIdRef.current || makeMessageId('interrupt');\n interruptMessageIdRef.current = interruptMessageId;\n const interruptMessage = {\n id: interruptMessageId,\n role: 'system',\n content: interrupt.description || '코드 실행 승인이 필요합니다.',\n timestamp: Date.now(),\n metadata: {\n interrupt: {\n ...interrupt,\n resolved: autoApproved || false,\n decision: autoApproved ? 'approve' : undefined,\n autoApproved: autoApproved || false\n }\n }\n };\n setMessages(prev => {\n const hasExisting = prev.some(msg => msg.id === interruptMessageId);\n if (hasExisting) {\n return prev.map(msg => msg.id === interruptMessageId ? interruptMessage : msg);\n }\n return [...prev, interruptMessage];\n });\n };\n const clearInterruptMessage = (decision) => {\n if (!interruptMessageIdRef.current)\n return;\n const messageId = interruptMessageIdRef.current;\n interruptMessageIdRef.current = null;\n setMessages(prev => prev.map(msg => {\n // Only IChatMessage has metadata property (check via 'role' property)\n if (msg.id === messageId && 'role' in msg) {\n const chatMsg = msg;\n return {\n ...chatMsg,\n metadata: {\n ...chatMsg.metadata,\n interrupt: {\n ...(chatMsg.metadata?.interrupt || {}),\n resolved: true,\n decision: decision || 'approve' // Track approve/reject decision\n }\n }\n };\n }\n return msg;\n }));\n };\n const validateRelativePath = (path) => {\n const trimmed = path.trim();\n if (trimmed.startsWith('/') || trimmed.startsWith('\\\\') || /^[A-Za-z]:/.test(trimmed)) {\n return { valid: false, error: 'Absolute paths are not allowed' };\n }\n if (trimmed.includes('..')) {\n return { valid: false, error: 'Path traversal (..) is not allowed' };\n }\n return { valid: true };\n };\n const normalizeContentsPath = (path) => {\n const trimmed = path.trim();\n if (!trimmed || trimmed === '.' || trimmed === './') {\n return '';\n }\n return trimmed\n .replace(/^\\.\\/+/, '')\n .replace(/^\\/+/, '')\n .replace(/\\/+$/, '');\n };\n const globToRegex = (pattern) => {\n const escaped = pattern.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&');\n const regex = `^${escaped.replace(/\\*/g, '.*').replace(/\\?/g, '.')}$`;\n return new RegExp(regex);\n };\n const fetchContentsModel = async (path, options) => {\n try {\n const { PageConfig, URLExt } = await import('@jupyterlab/coreutils');\n const baseUrl = PageConfig.getBaseUrl();\n const normalizedPath = normalizeContentsPath(path);\n const apiUrl = URLExt.join(baseUrl, 'api/contents', normalizedPath);\n const query = new URLSearchParams();\n if (options?.content !== undefined) {\n query.set('content', options.content ? '1' : '0');\n }\n if (options?.format) {\n query.set('format', options.format);\n }\n const url = query.toString() ? `${apiUrl}?${query.toString()}` : apiUrl;\n const response = await fetch(url, {\n method: 'GET',\n credentials: 'include',\n });\n if (!response.ok) {\n return { success: false, error: `Failed to load contents: ${response.status}` };\n }\n const data = await response.json();\n return { success: true, data };\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'Failed to load contents';\n return { success: false, error: message };\n }\n };\n const executeListFilesTool = async (params) => {\n const pathCheck = validateRelativePath(params.path);\n if (!pathCheck.valid) {\n return { success: false, error: pathCheck.error };\n }\n const pattern = (params.pattern || '*').trim() || '*';\n const recursive = params.recursive ?? false;\n const matcher = globToRegex(pattern);\n const maxEntries = 500;\n const files = [];\n const pendingDirs = [normalizeContentsPath(params.path)];\n const visited = new Set();\n while (pendingDirs.length > 0 && files.length < maxEntries) {\n const dirPath = pendingDirs.shift() ?? '';\n if (visited.has(dirPath)) {\n continue;\n }\n visited.add(dirPath);\n const contentsResult = await fetchContentsModel(dirPath, { content: true });\n if (!contentsResult.success) {\n return { success: false, error: contentsResult.error };\n }\n const model = contentsResult.data;\n if (!model || model.type !== 'directory' || !Array.isArray(model.content)) {\n const displayPath = dirPath || '.';\n return { success: false, error: `Not a directory: ${displayPath}` };\n }\n for (const entry of model.content) {\n if (!entry) {\n continue;\n }\n const name = entry.name || entry.path?.split('/').pop() || '';\n const entryPath = entry.path || name;\n const isDir = entry.type === 'directory';\n if (matcher.test(name)) {\n files.push({\n path: entryPath,\n isDir,\n size: isDir ? 0 : (entry.size ?? 0),\n });\n }\n if (recursive && isDir && entryPath) {\n pendingDirs.push(entryPath);\n }\n if (files.length >= maxEntries) {\n break;\n }\n }\n }\n const formatted = files.map((file) => {\n const icon = file.isDir ? '📁' : '📄';\n const sizeInfo = file.isDir ? '' : ` (${file.size} bytes)`;\n return `${icon} ${file.path}${file.isDir ? '/' : sizeInfo}`;\n }).join('\\n');\n return {\n success: true,\n output: formatted || '(empty directory)',\n metadata: { count: files.length, files }\n };\n };\n const executeReadFileTool = async (params) => {\n if (!params.path) {\n return { success: false, error: 'Path is required' };\n }\n const pathCheck = validateRelativePath(params.path);\n if (!pathCheck.valid) {\n return { success: false, error: pathCheck.error };\n }\n const maxLines = typeof params.maxLines === 'number' ? params.maxLines : 1000;\n const safeMaxLines = Math.max(0, maxLines);\n const contentsResult = await fetchContentsModel(params.path, { content: true, format: 'text' });\n if (!contentsResult.success) {\n return { success: false, error: contentsResult.error };\n }\n const model = contentsResult.data;\n if (!model) {\n return { success: false, error: 'File not found' };\n }\n if (model.type === 'directory') {\n return { success: false, error: `Path is a directory: ${params.path}` };\n }\n let content = model.content ?? '';\n if (model.format === 'base64') {\n return { success: false, error: 'Binary file content is not supported' };\n }\n if (typeof content !== 'string') {\n content = JSON.stringify(content, null, 2);\n }\n const lines = content.split('\\n');\n const sliced = lines.slice(0, safeMaxLines);\n return {\n success: true,\n output: sliced.join('\\n'),\n metadata: {\n lineCount: sliced.length,\n truncated: lines.length > safeMaxLines\n }\n };\n };\n // Helper function for auto-approving search/file tools with execution results\n const handleAutoToolInterrupt = async (interrupt) => {\n const { threadId, action, args } = interrupt;\n console.log('[AgentPanel] Auto-approving tool:', action, args);\n try {\n let executionResult;\n if (action === 'search_workspace_tool') {\n setDebugStatus(`🔍 검색 실행 중: ${args?.pattern || ''}`);\n executionResult = await apiService.searchWorkspace({\n pattern: args?.pattern || '',\n file_types: args?.file_types || ['*.py', '*.ipynb'],\n path: args?.path || '.',\n max_results: args?.max_results || 50,\n case_sensitive: args?.case_sensitive || false\n });\n console.log('[AgentPanel] search_workspace result:', executionResult);\n }\n else if (action === 'search_notebook_cells_tool') {\n setDebugStatus(`🔍 노트북 검색 실행 중: ${args?.pattern || ''}`);\n executionResult = await apiService.searchNotebookCells({\n pattern: args?.pattern || '',\n notebook_path: args?.notebook_path,\n cell_type: args?.cell_type,\n max_results: args?.max_results || 30,\n case_sensitive: args?.case_sensitive || false\n });\n console.log('[AgentPanel] search_notebook_cells result:', executionResult);\n }\n else if (action === 'check_resource_tool') {\n const filesList = args?.files || [];\n setDebugStatus(`📊 리소스 체크 중: ${filesList.join(', ') || 'system'}`);\n executionResult = await apiService.checkResource({\n files: filesList,\n dataframes: args?.dataframes || [],\n file_size_command: args?.file_size_command || '',\n dataframe_check_code: args?.dataframe_check_code || ''\n });\n console.log('[AgentPanel] check_resource result:', executionResult);\n }\n else if (action === 'list_files_tool') {\n setDebugStatus('📂 파일 목록 조회 중...');\n const listParams = {\n path: typeof args?.path === 'string' ? args.path : '.',\n recursive: args?.recursive ?? false,\n pattern: args?.pattern ?? undefined\n };\n executionResult = await executeListFilesTool(listParams);\n console.log('[AgentPanel] list_files result:', executionResult);\n }\n else if (action === 'read_file_tool') {\n setDebugStatus('📄 파일 읽는 중...');\n const readParams = {\n path: typeof args?.path === 'string' ? args.path : '',\n encoding: typeof args?.encoding === 'string' ? args.encoding : undefined,\n maxLines: args?.max_lines ?? args?.maxLines\n };\n executionResult = await executeReadFileTool(readParams);\n console.log('[AgentPanel] read_file result:', executionResult);\n }\n else {\n console.warn('[AgentPanel] Unknown auto tool:', action);\n return;\n }\n // Resume with execution result\n const resumeArgs = {\n ...args,\n execution_result: executionResult\n };\n // Clear interrupt state (don't show approval UI for search)\n setInterruptData(null);\n // Create new assistant message for continued response\n const assistantMessageId = makeMessageId('assistant');\n setStreamingMessageId(assistantMessageId);\n setMessages(prev => [\n ...prev,\n {\n id: assistantMessageId,\n role: 'assistant',\n content: '',\n timestamp: Date.now()\n }\n ]);\n let streamedContent = '';\n let interrupted = false;\n setDebugStatus('🤔 LLM 응답 대기 중');\n await apiService.resumeAgent(threadId, 'edit', // Use 'edit' to pass execution_result in args\n resumeArgs, undefined, llmConfig || undefined, (chunk) => {\n streamedContent += chunk;\n setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)\n ? { ...msg, content: streamedContent }\n : msg));\n }, (status) => {\n setDebugStatus(status);\n }, (nextInterrupt) => {\n interrupted = true;\n approvalPendingRef.current = true;\n const autoApproveEnabled = getAutoApproveEnabled(llmConfig || getLLMConfig() || getDefaultLLMConfig());\n // Handle next interrupt (could be another search or code execution)\n if (nextInterrupt.action === 'search_workspace_tool'\n || nextInterrupt.action === 'search_notebook_cells_tool'\n || nextInterrupt.action === 'check_resource_tool'\n || nextInterrupt.action === 'list_files_tool'\n || nextInterrupt.action === 'read_file_tool') {\n void handleAutoToolInterrupt(nextInterrupt);\n return;\n }\n if (autoApproveEnabled) {\n // 자동 승인 모드일 때도 인터럽트 메시지를 생성하여 코드블럭으로 표시\n upsertInterruptMessage(nextInterrupt, true);\n void resumeFromInterrupt(nextInterrupt, 'approve');\n return;\n }\n if (nextInterrupt.action === 'jupyter_cell_tool' && nextInterrupt.args?.code) {\n const shouldQueue = shouldExecuteInNotebook(nextInterrupt.args.code);\n if (isAutoApprovedCode(nextInterrupt.args.code)) {\n if (shouldQueue) {\n queueApprovalCell(nextInterrupt.args.code);\n }\n void resumeFromInterrupt(nextInterrupt, 'approve');\n return;\n }\n if (shouldQueue) {\n queueApprovalCell(nextInterrupt.args.code);\n }\n }\n setInterruptData(nextInterrupt);\n upsertInterruptMessage(nextInterrupt);\n setIsLoading(false);\n setIsStreaming(false);\n }, (newTodos) => {\n setTodos(newTodos);\n }, () => {\n setDebugStatus(null);\n }, handleToolCall);\n if (!interrupted) {\n setIsLoading(false);\n setIsStreaming(false);\n setStreamingMessageId(null);\n approvalPendingRef.current = false;\n }\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'Tool execution failed';\n console.error('[AgentPanel] Auto tool error:', error);\n setDebugStatus(`오류: ${message}`);\n setIsLoading(false);\n setIsStreaming(false);\n approvalPendingRef.current = false;\n }\n };\n const resumeFromInterrupt = async (interrupt, decision, feedback // Optional feedback message for rejection\n ) => {\n const { threadId } = interrupt;\n setInterruptData(null);\n setDebugStatus(null);\n clearInterruptMessage(decision); // Pass decision to track approve/reject\n let resumeDecision = decision;\n let resumeArgs = undefined;\n if (decision === 'approve') {\n // Handle write_file_tool separately - execute file write on Jupyter server\n if (interrupt.action === 'write_file_tool') {\n try {\n setDebugStatus('📝 파일 쓰기 중...');\n const writeResult = await apiService.writeFile(interrupt.args?.path || '', interrupt.args?.content || '', {\n encoding: interrupt.args?.encoding || 'utf-8',\n overwrite: interrupt.args?.overwrite || false\n });\n console.log('[AgentPanel] write_file result:', writeResult);\n resumeDecision = 'edit';\n resumeArgs = {\n ...interrupt.args,\n execution_result: writeResult\n };\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'File write failed';\n console.error('[AgentPanel] write_file error:', error);\n resumeDecision = 'edit';\n resumeArgs = {\n ...interrupt.args,\n execution_result: { success: false, error: message }\n };\n }\n }\n else if (interrupt.action === 'edit_file_tool') {\n // Handle edit_file_tool - read file, perform replacement, write back\n try {\n setDebugStatus('✏️ 파일 수정 중...');\n const filePath = interrupt.args?.path || '';\n const oldString = interrupt.args?.old_string || '';\n const newString = interrupt.args?.new_string || '';\n const replaceAll = interrupt.args?.replace_all || false;\n // Read current file content\n const readResult = await apiService.readFile(filePath);\n if (!readResult.success || readResult.content === undefined) {\n throw new Error(readResult.error || 'Failed to read file');\n }\n const originalContent = readResult.content;\n const occurrences = (originalContent.match(new RegExp(oldString.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g')) || []).length;\n if (occurrences === 0) {\n throw new Error(`String not found in file: '${oldString.slice(0, 100)}...'`);\n }\n if (occurrences > 1 && !replaceAll) {\n throw new Error(`String appears ${occurrences} times. Use replace_all=True or provide more context.`);\n }\n // Perform replacement\n const newContent = replaceAll\n ? originalContent.split(oldString).join(newString)\n : originalContent.replace(oldString, newString);\n // Generate diff for logging\n const diffLines = [];\n const originalLines = originalContent.split('\\n');\n const newLines = newContent.split('\\n');\n diffLines.push(`--- ${filePath} (before)`);\n diffLines.push(`+++ ${filePath} (after)`);\n // Simple diff generation\n let i = 0, j = 0;\n while (i < originalLines.length || j < newLines.length) {\n if (i < originalLines.length && j < newLines.length && originalLines[i] === newLines[j]) {\n i++;\n j++;\n }\n else if (i < originalLines.length && (j >= newLines.length || originalLines[i] !== newLines[j])) {\n diffLines.push(`-${originalLines[i]}`);\n i++;\n }\n else {\n diffLines.push(`+${newLines[j]}`);\n j++;\n }\n }\n const diff = diffLines.join('\\n');\n // Write the modified content\n const writeResult = await apiService.writeFile(filePath, newContent, {\n encoding: 'utf-8',\n overwrite: true\n });\n console.log('[AgentPanel] edit_file result:', writeResult);\n resumeDecision = 'edit';\n resumeArgs = {\n ...interrupt.args,\n execution_result: {\n ...writeResult,\n diff,\n occurrences,\n lines_added: newLines.length - originalLines.length > 0 ? newLines.length - originalLines.length : 0,\n lines_removed: originalLines.length - newLines.length > 0 ? originalLines.length - newLines.length : 0,\n }\n };\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'File edit failed';\n console.error('[AgentPanel] edit_file error:', error);\n resumeDecision = 'edit';\n resumeArgs = {\n ...interrupt.args,\n execution_result: { success: false, error: message }\n };\n }\n }\n else if (interrupt.action === 'multiedit_file_tool') {\n // Handle multiedit_file_tool - apply multiple edits atomically (Crush 패턴)\n try {\n setDebugStatus('✏️ 다중 파일 수정 중...');\n const filePath = interrupt.args?.path || '';\n const edits = interrupt.args?.edits || [];\n if (!filePath || edits.length === 0) {\n throw new Error('File path and edits are required');\n }\n // Read current file content\n const readResult = await apiService.readFile(filePath);\n if (!readResult.success || readResult.content === undefined) {\n throw new Error(readResult.error || 'Failed to read file');\n }\n const originalContent = readResult.content;\n let currentContent = originalContent;\n let editsApplied = 0;\n let editsFailed = 0;\n const failedEdits = [];\n // Apply edits sequentially\n for (let i = 0; i < edits.length; i++) {\n const edit = edits[i];\n const oldString = edit.old_string || '';\n const newString = edit.new_string || '';\n const replaceAll = edit.replace_all || false;\n const occurrences = (currentContent.match(new RegExp(oldString.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g')) || []).length;\n if (occurrences === 0) {\n failedEdits.push(`Edit ${i + 1}: String not found`);\n editsFailed++;\n continue;\n }\n if (occurrences > 1 && !replaceAll) {\n failedEdits.push(`Edit ${i + 1}: String appears ${occurrences} times (use replace_all)`);\n editsFailed++;\n continue;\n }\n // Apply this edit\n currentContent = replaceAll\n ? currentContent.split(oldString).join(newString)\n : currentContent.replace(oldString, newString);\n editsApplied++;\n }\n // Generate diff\n const diffLines = [];\n const originalLines = originalContent.split('\\n');\n const newLines = currentContent.split('\\n');\n diffLines.push(`--- ${filePath} (before)`);\n diffLines.push(`+++ ${filePath} (after)`);\n let i = 0, j = 0;\n while (i < originalLines.length || j < newLines.length) {\n if (i < originalLines.length && j < newLines.length && originalLines[i] === newLines[j]) {\n i++;\n j++;\n }\n else if (i < originalLines.length && (j >= newLines.length || originalLines[i] !== newLines[j])) {\n diffLines.push(`-${originalLines[i]}`);\n i++;\n }\n else {\n diffLines.push(`+${newLines[j]}`);\n j++;\n }\n }\n const diff = diffLines.join('\\n');\n // Write the modified content if any edits succeeded\n if (editsApplied > 0) {\n const writeResult = await apiService.writeFile(filePath, currentContent, {\n encoding: 'utf-8',\n overwrite: true\n });\n console.log('[AgentPanel] multiedit_file result:', writeResult, 'applied:', editsApplied, 'failed:', editsFailed);\n resumeDecision = 'edit';\n resumeArgs = {\n ...interrupt.args,\n execution_result: {\n ...writeResult,\n diff,\n edits_applied: editsApplied,\n edits_failed: editsFailed,\n failed_details: failedEdits,\n lines_added: newLines.length - originalLines.length > 0 ? newLines.length - originalLines.length : 0,\n lines_removed: originalLines.length - newLines.length > 0 ? originalLines.length - newLines.length : 0,\n }\n };\n }\n else {\n throw new Error(`All ${edits.length} edits failed: ${failedEdits.join('; ')}`);\n }\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'Multi-edit failed';\n console.error('[AgentPanel] multiedit_file error:', error);\n resumeDecision = 'edit';\n resumeArgs = {\n ...interrupt.args,\n execution_result: { success: false, error: message, edits_applied: 0, edits_failed: 0 }\n };\n }\n }\n else if (interrupt.action === 'diagnostics_tool') {\n // Handle diagnostics_tool - get LSP diagnostics via LSP Bridge\n try {\n setDebugStatus('🔍 LSP 진단 조회 중...');\n const filePath = interrupt.args?.path || null;\n const severityFilter = interrupt.args?.severity_filter || null;\n // Get LSP Bridge instance\n const lspBridge = window._hdspLSPBridge;\n if (lspBridge && lspBridge.isLSPAvailable()) {\n const result = await lspBridge.getDiagnostics(filePath);\n // Apply severity filter if specified\n let diagnostics = result.diagnostics;\n if (severityFilter) {\n diagnostics = diagnostics.filter((d) => d.severity === severityFilter);\n }\n resumeDecision = 'approve';\n resumeArgs = {\n ...interrupt.args,\n execution_result: {\n success: true,\n lsp_available: true,\n diagnostics\n }\n };\n }\n else {\n // LSP not available - return empty result\n console.log('[AgentPanel] LSP not available, returning empty diagnostics');\n resumeDecision = 'approve';\n resumeArgs = {\n ...interrupt.args,\n execution_result: {\n success: true,\n lsp_available: false,\n diagnostics: []\n }\n };\n }\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'Diagnostics failed';\n console.error('[AgentPanel] diagnostics_tool error:', error);\n resumeDecision = 'approve';\n resumeArgs = {\n ...interrupt.args,\n execution_result: { success: false, lsp_available: false, error: message, diagnostics: [] }\n };\n }\n }\n else if (interrupt.action === 'references_tool') {\n // Handle references_tool - find symbol references\n try {\n setDebugStatus('🔗 참조 검색 중...');\n const symbol = interrupt.args?.symbol || '';\n const filePath = interrupt.args?.path || null;\n // Get LSP Bridge instance\n const lspBridge = window._hdspLSPBridge;\n if (lspBridge && lspBridge.isLSPAvailable()) {\n const result = await lspBridge.getReferences(symbol, filePath, interrupt.args?.line, interrupt.args?.character);\n resumeDecision = 'approve';\n resumeArgs = {\n ...interrupt.args,\n execution_result: {\n success: result.success,\n lsp_available: true,\n locations: result.locations,\n used_grep: false\n }\n };\n }\n else {\n // LSP not available - suggest using search_workspace_tool\n console.log('[AgentPanel] LSP not available for references, suggesting grep fallback');\n resumeDecision = 'approve';\n resumeArgs = {\n ...interrupt.args,\n execution_result: {\n success: false,\n lsp_available: false,\n locations: [],\n used_grep: false\n }\n };\n }\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'References search failed';\n console.error('[AgentPanel] references_tool error:', error);\n resumeDecision = 'approve';\n resumeArgs = {\n ...interrupt.args,\n execution_result: { success: false, lsp_available: false, error: message, locations: [] }\n };\n }\n }\n else if (interrupt.action === 'execute_command_tool') {\n const command = (interrupt.args?.command || '').trim();\n // Default stdin to \"y\\n\" for interactive prompts (yes/no)\n const stdinInput = interrupt.args?.stdin ?? 'y\\n';\n setDebugStatus('🐚 셸 명령 실행 중...');\n const outputMessageId = command ? createCommandOutputMessage(command) : null;\n const execResult = command\n ? await executeSubprocessCommand(command, interrupt.args?.timeout, outputMessageId\n ? (chunk) => appendCommandOutputMessage(outputMessageId, chunk.text, chunk.stream)\n : undefined, stdinInput)\n : {\n success: false,\n output: '',\n error: 'Command is required',\n returncode: null,\n command,\n truncated: false\n };\n resumeDecision = 'edit';\n resumeArgs = {\n ...interrupt.args,\n execution_result: execResult\n };\n }\n else if (interrupt.action === 'jupyter_cell_tool' && interrupt.args?.code) {\n const code = interrupt.args.code;\n if (!shouldExecuteInNotebook(code)) {\n setDebugStatus('🐚 서브프로세스로 코드 실행 중...');\n const execResult = await executeCodeViaSubprocess(code, interrupt.args?.timeout);\n resumeDecision = 'edit';\n resumeArgs = {\n code,\n execution_result: execResult\n };\n }\n else {\n const executed = await executePendingApproval();\n if (executed && executed.tool === 'jupyter_cell') {\n resumeDecision = 'edit';\n resumeArgs = {\n code: executed.code,\n execution_result: executed.execution_result\n };\n }\n else {\n const notebook = getActiveNotebookPanel();\n if (!notebook) {\n setDebugStatus('🐚 노트북이 없어 서브프로세스로 실행 중...');\n const execResult = await executeCodeViaSubprocess(code, interrupt.args?.timeout);\n resumeDecision = 'edit';\n resumeArgs = {\n code,\n execution_result: execResult\n };\n }\n else {\n const cellIndex = insertCell(notebook, 'code', code);\n if (cellIndex === null) {\n setDebugStatus('🐚 노트북 모델이 없어 서브프로세스로 실행 중...');\n const execResult = await executeCodeViaSubprocess(code, interrupt.args?.timeout);\n resumeDecision = 'edit';\n resumeArgs = {\n code,\n execution_result: execResult\n };\n }\n else {\n await executeCell(notebook, cellIndex);\n const execResult = captureExecutionResult(notebook, cellIndex);\n resumeDecision = 'edit';\n resumeArgs = {\n code,\n execution_result: execResult\n };\n }\n }\n }\n }\n }\n else {\n const executed = await executePendingApproval();\n if (executed && executed.tool === 'jupyter_cell') {\n resumeDecision = 'edit';\n resumeArgs = {\n code: executed.code,\n execution_result: executed.execution_result\n };\n }\n else if (executed && executed.tool === 'markdown') {\n resumeDecision = 'edit';\n resumeArgs = {\n content: executed.content\n };\n }\n }\n }\n else {\n // Reject: delete the pending cell from notebook\n const pendingCall = pendingToolCallsRef.current[0];\n if (pendingCall && pendingCall.cellIndex !== undefined) {\n const notebook = getActiveNotebookPanel();\n if (notebook) {\n deleteCell(notebook, pendingCall.cellIndex);\n }\n }\n pendingToolCallsRef.current.shift();\n approvalPendingRef.current = pendingToolCallsRef.current.length > 0;\n }\n setIsLoading(true);\n setIsStreaming(true);\n let interrupted = false;\n // 항상 새 메시지 생성 - 승인 UI 아래에 append되도록\n const assistantMessageId = makeMessageId('assistant');\n setStreamingMessageId(assistantMessageId);\n // 새 메시지 추가 (맨 아래에 append)\n setMessages(prev => [\n ...prev,\n {\n id: assistantMessageId,\n role: 'assistant',\n content: '',\n timestamp: Date.now()\n }\n ]);\n let streamedContent = '';\n // Build feedback message for rejection\n const rejectionFeedback = resumeDecision === 'reject'\n ? (feedback ? `사용자 피드백: ${feedback}` : 'User rejected this action')\n : undefined;\n try {\n await apiService.resumeAgent(threadId, resumeDecision, resumeArgs, rejectionFeedback, llmConfig || undefined, (chunk) => {\n streamedContent += chunk;\n setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)\n ? { ...msg, content: streamedContent }\n : msg));\n }, (status) => {\n setDebugStatus(status);\n }, (nextInterrupt) => {\n interrupted = true;\n approvalPendingRef.current = true;\n const autoApproveEnabled = getAutoApproveEnabled(llmConfig || getLLMConfig() || getDefaultLLMConfig());\n // Auto-approve search/file/resource tools\n if (nextInterrupt.action === 'search_workspace_tool'\n || nextInterrupt.action === 'search_notebook_cells_tool'\n || nextInterrupt.action === 'check_resource_tool'\n || nextInterrupt.action === 'list_files_tool'\n || nextInterrupt.action === 'read_file_tool') {\n void handleAutoToolInterrupt(nextInterrupt);\n return;\n }\n if (autoApproveEnabled) {\n // 자동 승인 모드일 때도 인터럽트 메시지를 생성하여 코드블럭으로 표시\n upsertInterruptMessage(nextInterrupt, true);\n void resumeFromInterrupt(nextInterrupt, 'approve');\n return;\n }\n if (nextInterrupt.action === 'jupyter_cell_tool' && nextInterrupt.args?.code) {\n const shouldQueue = shouldExecuteInNotebook(nextInterrupt.args.code);\n if (isAutoApprovedCode(nextInterrupt.args.code)) {\n if (shouldQueue) {\n queueApprovalCell(nextInterrupt.args.code);\n }\n void resumeFromInterrupt(nextInterrupt, 'approve');\n return;\n }\n if (shouldQueue) {\n queueApprovalCell(nextInterrupt.args.code);\n }\n }\n setInterruptData(nextInterrupt);\n upsertInterruptMessage(nextInterrupt);\n setIsLoading(false);\n setIsStreaming(false);\n }, (newTodos) => {\n setTodos(newTodos);\n }, () => {\n setDebugStatus(null);\n }, handleToolCall);\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'Failed to resume';\n setDebugStatus(`오류: ${message}`);\n console.error('Resume failed:', error);\n setMessages(prev => [\n ...prev,\n {\n id: makeMessageId(),\n role: 'assistant',\n content: `Error: ${message}`,\n timestamp: Date.now()\n }\n ]);\n }\n finally {\n setIsLoading(false);\n setIsStreaming(false);\n if (!interrupted) {\n approvalPendingRef.current = false;\n }\n }\n };\n const handleSendMessage = async () => {\n // Handle rejection mode - resume with optional feedback\n if (isRejectionMode && pendingRejectionInterrupt) {\n const feedback = input.trim() || undefined; // Empty input means no feedback\n // Add user message bubble if there's feedback\n if (feedback) {\n const userMessage = {\n id: makeMessageId(),\n role: 'user',\n content: feedback,\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, userMessage]);\n }\n setInput('');\n setIsRejectionMode(false);\n const interruptToResume = pendingRejectionInterrupt;\n setPendingRejectionInterrupt(null);\n await resumeFromInterrupt(interruptToResume, 'reject', feedback);\n return;\n }\n // Check if there's an LLM prompt stored (from cell action)\n const textarea = messagesEndRef.current?.parentElement?.querySelector('.jp-agent-input');\n const llmPrompt = pendingLlmPromptRef.current || textarea?.getAttribute('data-llm-prompt');\n // Allow execution if we have an LLM prompt even if input is empty (for auto-execution)\n if ((!input.trim() && !llmPrompt) || isLoading || isStreaming || isAgentRunning)\n return;\n const currentInput = input.trim();\n // Agent 모드이면 Agent 실행\n if (inputMode === 'agent') {\n // Use app.shell.currentWidget for reliable active tab detection\n const app = window.jupyterapp;\n let notebook = null;\n if (app?.shell?.currentWidget) {\n const currentWidget = app.shell.currentWidget;\n if ('content' in currentWidget && currentWidget.content?.model) {\n notebook = currentWidget;\n }\n }\n if (!notebook) {\n notebook = notebookTracker?.currentWidget;\n }\n // 노트북이 없는 경우\n if (!notebook) {\n // [DISABLED] Python 에러가 감지되면 파일 수정 모드로 전환\n // 이 레거시 기능은 /file/action API가 구현되지 않아 비활성화됨\n // if (detectPythonError(currentInput)) {\n // console.log('[AgentPanel] Agent mode: No notebook, but Python error detected - attempting file fix');\n // const handled = await handlePythonErrorFix(currentInput);\n // if (handled) {\n // const userMessage: IChatMessage = {\n // id: makeMessageId(),\n // role: 'user',\n // content: currentInput,\n // timestamp: Date.now(),\n // };\n // setMessages(prev => [...prev, userMessage]);\n // setInput('');\n // return;\n // }\n // console.log('[AgentPanel] Agent mode: No file path found, continuing...');\n // }\n // 파일 수정 관련 자연어 요청 감지 (에러, 고쳐, 수정, fix 등)\n const fileFixRequestPatterns = [\n /에러.*해결/i,\n /에러.*고쳐/i,\n /에러.*수정/i,\n /오류.*해결/i,\n /오류.*고쳐/i,\n /오류.*수정/i,\n /fix.*error/i,\n /\\.py.*에러/i,\n /\\.py.*오류/i,\n /콘솔.*에러/i,\n /console.*error/i,\n /파일.*에러/i,\n /파일.*오류/i,\n ];\n const isFileFixRequest = fileFixRequestPatterns.some(pattern => pattern.test(currentInput));\n if (isFileFixRequest) {\n console.log('[AgentPanel] Agent mode: No notebook, file fix request detected - prompting for error details');\n // User 메시지 추가\n const userMessage = {\n id: makeMessageId(),\n role: 'user',\n content: currentInput,\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, userMessage]);\n setInput('');\n // 에러 메시지 요청 안내\n const guideMessage = {\n id: makeMessageId('guide'),\n role: 'assistant',\n content: `파일 에러 수정을 도와드리겠습니다! 🔧\n\n**에러 메시지를 복사해서 붙여넣어 주세요.**\n\nConsole에서 발생한 에러 전체를 복사해주시면:\n1. 에러가 발생한 파일을 자동으로 찾아서 읽고\n2. 관련된 import 파일들도 함께 분석하여\n3. 수정된 코드를 제안해 드립니다.\n\n예시:\n\\`\\`\\`\n$ python b.py\nTraceback (most recent call last):\n File \"a.py\", line 3\n def foo(\n ^\nSyntaxError: '(' was never closed\n\\`\\`\\``,\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, guideMessage]);\n return;\n }\n // 일반적인 요청은 Chat 모드로 fallback\n console.log('[AgentPanel] Agent mode: No notebook - falling back to chat mode');\n }\n else {\n // 노트북이 있으면 Agent 실행\n // User 메시지 추가\n const userMessage = {\n id: makeMessageId(),\n role: 'user',\n content: `@agent ${currentInput}`,\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, userMessage]);\n setInput('');\n // Agent 실행\n await handleAgentExecution(currentInput);\n return;\n }\n }\n // Chat 모드에서도 명령어로 Agent 실행 가능\n if (isAgentCommand(currentInput)) {\n const agentRequest = extractAgentRequest(currentInput);\n if (agentRequest) {\n // User 메시지 추가\n const userMessage = {\n id: makeMessageId(),\n role: 'user',\n content: currentInput,\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, userMessage]);\n setInput('');\n // Agent 실행\n await handleAgentExecution(agentRequest);\n return;\n }\n }\n // [DISABLED] Python 에러 감지 및 파일 수정 모드 (Chat 모드에서만)\n // 이 레거시 기능은 /file/action API가 구현되지 않아 비활성화됨\n // LangChain agent의 edit_file_tool을 사용하도록 변경됨\n // if (inputMode === 'chat' && detectPythonError(currentInput)) {\n // console.log('[AgentPanel] Python error detected in message, attempting file fix...');\n // const handled = await handlePythonErrorFix(currentInput);\n // if (handled) {\n // const userMessage: IChatMessage = {\n // id: makeMessageId(),\n // role: 'user',\n // content: currentInput,\n // timestamp: Date.now(),\n // };\n // setMessages(prev => [...prev, userMessage]);\n // setInput('');\n // return;\n // }\n // console.log('[AgentPanel] No file path found in error, using regular LLM stream');\n // }\n // Use the display prompt (input) for the user message, or use a fallback if input is empty\n const displayContent = currentInput || (llmPrompt ? '셀 분석 요청' : '');\n await sendChatMessage({\n displayContent,\n llmPrompt,\n textarea,\n clearInput: Boolean(currentInput)\n });\n };\n // Handle resume after user approval/rejection\n const handleResumeAgent = async (decision) => {\n if (!interruptData)\n return;\n if (decision === 'reject') {\n // Enter rejection mode - wait for user feedback before resuming\n setIsRejectionMode(true);\n setPendingRejectionInterrupt(interruptData);\n // Clear the interrupt UI but keep the data for later, mark as rejected\n setInterruptData(null);\n clearInterruptMessage('reject'); // Pass 'reject' to show \"거부됨\"\n // Focus on input for user to provide feedback\n const textarea = document.querySelector('.jp-agent-input');\n if (textarea) {\n textarea.focus();\n }\n return;\n }\n await resumeFromInterrupt(interruptData, decision);\n };\n const handleKeyDown = (e) => {\n // Enter: 전송\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n handleSendMessage();\n }\n // Shift+Tab: 모드 전환 (chat → agent → agent_v2 → chat 순환)\n if (e.key === 'Tab' && e.shiftKey) {\n e.preventDefault();\n setInputMode(prev => prev === 'chat' ? 'agent' : prev === 'agent' ? 'agent_v2' : 'chat');\n return;\n }\n // Cmd/Ctrl + . : 모드 전환 (대체 단축키)\n if (e.key === '.' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n setInputMode(prev => prev === 'chat' ? 'agent' : prev === 'agent' ? 'agent_v2' : 'chat');\n }\n // Tab (without Shift): Agent 모드일 때 드롭다운 토글\n if (e.key === 'Tab' && !e.shiftKey && inputMode !== 'chat') {\n e.preventDefault();\n setShowModeDropdown(prev => !prev);\n }\n };\n // 모드 토글 함수 (chat → agent → agent_v2 → chat 순환)\n const toggleMode = () => {\n setInputMode(prev => prev === 'chat' ? 'agent' : prev === 'agent' ? 'agent_v2' : 'chat');\n setShowModeDropdown(false);\n };\n const clearChat = () => {\n setMessages([]);\n setConversationId('');\n };\n // Agent 실행 메시지 렌더링\n const renderAgentExecutionMessage = (msg) => {\n const { status, plan, result, completedSteps, failedSteps, request } = msg;\n const isActive = ['planning', 'executing', 'tool_calling', 'self_healing', 'replanning', 'validating', 'reflecting'].includes(status.phase);\n const getStepStatus = (stepNumber) => {\n if (failedSteps.includes(stepNumber))\n return 'failed';\n if (completedSteps.includes(stepNumber))\n return 'completed';\n if (status.currentStep === stepNumber)\n return 'current';\n return 'pending';\n };\n const progressPercent = plan ? (completedSteps.length / plan.totalSteps) * 100 : 0;\n return (React.createElement(\"div\", { className: \"jp-agent-execution-message\" },\n React.createElement(\"div\", { className: \"jp-agent-execution-header\" },\n React.createElement(\"div\", { className: \"jp-agent-execution-badge\" },\n React.createElement(\"svg\", { viewBox: \"0 0 16 16\", fill: \"currentColor\", width: \"12\", height: \"12\" },\n React.createElement(\"path\", { d: \"M8 0a8 8 0 100 16A8 8 0 008 0zm0 14.5a6.5 6.5 0 110-13 6.5 6.5 0 010 13z\" }),\n React.createElement(\"path\", { d: \"M8 3a.75.75 0 01.75.75v3.5h2.5a.75.75 0 010 1.5h-3.25a.75.75 0 01-.75-.75v-4.25A.75.75 0 018 3z\" })),\n \"Agent\"),\n React.createElement(\"span\", { className: \"jp-agent-execution-request\" }, request)),\n React.createElement(\"div\", { className: `jp-agent-execution-status jp-agent-execution-status--${status.phase}` },\n isActive && React.createElement(\"div\", { className: \"jp-agent-execution-spinner\" }),\n status.phase === 'completed' && (React.createElement(\"svg\", { viewBox: \"0 0 16 16\", fill: \"currentColor\", className: \"jp-agent-execution-icon--success\" },\n React.createElement(\"path\", { d: \"M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z\" }))),\n status.phase === 'failed' && (React.createElement(\"svg\", { viewBox: \"0 0 16 16\", fill: \"currentColor\", className: \"jp-agent-execution-icon--error\" },\n React.createElement(\"path\", { d: \"M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z\" }))),\n React.createElement(\"span\", { className: \"jp-agent-execution-status-text\" }, status.phase === 'completed'\n ? '완료'\n : status.phase === 'failed'\n ? '실패'\n : (status.message || status.phase))),\n plan && (React.createElement(\"div\", { className: \"jp-agent-execution-plan\" },\n React.createElement(\"div\", { className: \"jp-agent-execution-plan-header\" },\n React.createElement(\"span\", null, \"\\uC2E4\\uD589 \\uACC4\\uD68D\"),\n React.createElement(\"span\", { className: \"jp-agent-execution-plan-progress\" },\n completedSteps.length,\n \" / \",\n plan.totalSteps)),\n React.createElement(\"div\", { className: \"jp-agent-execution-progress-bar\" },\n React.createElement(\"div\", { className: \"jp-agent-execution-progress-fill\", style: { width: `${progressPercent}%` } })),\n React.createElement(\"div\", { className: \"jp-agent-execution-steps\" }, plan.steps.map((step) => {\n const stepStatus = getStepStatus(step.stepNumber);\n return (React.createElement(\"div\", { key: step.stepNumber, className: `jp-agent-execution-step jp-agent-execution-step--${stepStatus}`, ref: (el) => {\n // 현재 진행 중인 단계로 자동 스크롤\n if (stepStatus === 'current' && el) {\n el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });\n }\n } },\n React.createElement(\"div\", { className: \"jp-agent-execution-step-indicator\" },\n stepStatus === 'completed' && (React.createElement(\"svg\", { viewBox: \"0 0 16 16\", fill: \"currentColor\" },\n React.createElement(\"path\", { d: \"M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z\" }))),\n stepStatus === 'failed' && (React.createElement(\"svg\", { viewBox: \"0 0 16 16\", fill: \"currentColor\" },\n React.createElement(\"path\", { d: \"M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z\" }))),\n stepStatus === 'current' && React.createElement(\"div\", { className: \"jp-agent-execution-step-spinner\" }),\n stepStatus === 'pending' && React.createElement(\"span\", null, step.stepNumber)),\n React.createElement(\"div\", { className: \"jp-agent-execution-step-content\" },\n React.createElement(\"span\", { className: `jp-agent-execution-step-desc ${stepStatus === 'completed' ? 'jp-agent-execution-step-desc--done' : ''}` }, step.description),\n React.createElement(\"div\", { className: \"jp-agent-execution-step-tools\" }, step.toolCalls\n .filter(tc => !['jupyter_cell', 'final_answer', 'markdown'].includes(tc.tool))\n .map((tc, i) => (React.createElement(\"span\", { key: i, className: \"jp-agent-execution-tool-tag\" }, tc.tool)))))));\n })))),\n result && (React.createElement(\"div\", { className: `jp-agent-execution-result jp-agent-execution-result--${result.success ? 'success' : 'error'}` },\n result.finalAnswer && (React.createElement(\"div\", { className: \"jp-agent-execution-result-message jp-RenderedHTMLCommon\", dangerouslySetInnerHTML: { __html: formatMarkdownToHtml(result.finalAnswer) } })),\n result.error && (React.createElement(\"p\", { className: \"jp-agent-execution-result-error\" }, result.error)),\n React.createElement(\"div\", { className: \"jp-agent-execution-result-stats\" },\n React.createElement(\"span\", null,\n result.createdCells.length,\n \"\\uAC1C \\uC140 \\uC0DD\\uC131\"),\n React.createElement(\"span\", null,\n result.modifiedCells.length,\n \"\\uAC1C \\uC140 \\uC218\\uC815\"),\n result.executionTime && (React.createElement(\"span\", null,\n (result.executionTime / 1000).toFixed(1),\n \"\\uCD08\")))))));\n };\n // 메시지가 Chat 메시지인지 확인\n const isChatMessage = (msg) => {\n return !('type' in msg) || msg.type !== 'agent_execution';\n };\n const mapStatusText = (raw) => {\n const normalized = raw.toLowerCase();\n if (normalized.includes('calling tool')) {\n return raw.replace(/Calling tool:/gi, '도구 호출 중:').trim();\n }\n if (normalized.includes('waiting for user approval')) {\n return '승인 대기 중...';\n }\n if (normalized.includes('resuming execution')) {\n return '승인 반영 후 실행 재개 중...';\n }\n return raw;\n };\n const getStatusText = () => {\n if (interruptData) {\n return `승인 대기: ${interruptData.action}`;\n }\n if (debugStatus) {\n return mapStatusText(debugStatus);\n }\n if (isLoading || isStreaming) {\n // 기본 상태 메시지 - todo 내용은 compact todo UI에서 표시\n return '요청 처리 중';\n }\n return null;\n };\n const statusText = getStatusText();\n const hasActiveTodos = todos.some(todo => todo.status === 'pending' || todo.status === 'in_progress');\n return (React.createElement(\"div\", { className: \"jp-agent-panel\" },\n showSettings && (React.createElement(SettingsPanel, { onClose: () => setShowSettings(false), onSave: handleSaveConfig, currentConfig: llmConfig || undefined })),\n React.createElement(\"div\", { className: \"jp-agent-header\" },\n React.createElement(\"div\", { className: \"jp-agent-header-logo\", dangerouslySetInnerHTML: { __html: headerLogoSvg } }),\n React.createElement(\"div\", { className: \"jp-agent-header-buttons\" },\n React.createElement(\"button\", { className: \"jp-agent-clear-button\", onClick: clearChat, title: \"\\uB300\\uD654 \\uCD08\\uAE30\\uD654\" },\n React.createElement(\"svg\", { width: \"16\", height: \"16\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\", strokeWidth: \"2\", strokeLinecap: \"round\", strokeLinejoin: \"round\" },\n React.createElement(\"polyline\", { points: \"3 6 5 6 21 6\" }),\n React.createElement(\"path\", { d: \"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\" }),\n React.createElement(\"line\", { x1: \"10\", y1: \"11\", x2: \"10\", y2: \"17\" }),\n React.createElement(\"line\", { x1: \"14\", y1: \"11\", x2: \"14\", y2: \"17\" }))),\n React.createElement(\"button\", { className: \"jp-agent-settings-button-icon\", onClick: () => setShowSettings(true), title: \"\\uC124\\uC815\" },\n React.createElement(\"svg\", { width: \"16\", height: \"16\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\", strokeWidth: \"2\", strokeLinecap: \"round\", strokeLinejoin: \"round\" },\n React.createElement(\"line\", { x1: \"4\", y1: \"21\", x2: \"4\", y2: \"14\" }),\n React.createElement(\"line\", { x1: \"4\", y1: \"10\", x2: \"4\", y2: \"3\" }),\n React.createElement(\"line\", { x1: \"12\", y1: \"21\", x2: \"12\", y2: \"12\" }),\n React.createElement(\"line\", { x1: \"12\", y1: \"8\", x2: \"12\", y2: \"3\" }),\n React.createElement(\"line\", { x1: \"20\", y1: \"21\", x2: \"20\", y2: \"16\" }),\n React.createElement(\"line\", { x1: \"20\", y1: \"12\", x2: \"20\", y2: \"3\" }),\n React.createElement(\"line\", { x1: \"1\", y1: \"14\", x2: \"7\", y2: \"14\" }),\n React.createElement(\"line\", { x1: \"9\", y1: \"8\", x2: \"15\", y2: \"8\" }),\n React.createElement(\"line\", { x1: \"17\", y1: \"16\", x2: \"23\", y2: \"16\" }))))),\n React.createElement(\"div\", { className: \"jp-agent-messages\" },\n messages.length === 0 ? (React.createElement(\"div\", { className: \"jp-agent-empty-state\" },\n React.createElement(\"p\", null, \"\\uC548\\uB155\\uD558\\uC138\\uC694! HDSP Agent\\uC785\\uB2C8\\uB2E4.\"),\n React.createElement(\"p\", { className: \"jp-agent-empty-hint\" }, inputMode === 'agent'\n ? '노트북 작업을 자연어로 요청하세요. 예: \"데이터 시각화 해줘\"'\n : inputMode === 'agent_v2'\n ? 'Deep Agent 모드입니다. HITL 승인을 통해 도구 실행을 제어할 수 있습니다.'\n : '메시지를 입력하거나 아래 버튼으로 Agent 모드를 선택하세요.'))) : (messages.map(msg => {\n if (isChatMessage(msg)) {\n // 일반 Chat 메시지\n const isAssistant = msg.role === 'assistant';\n const isShellOutput = msg.metadata?.kind === 'shell-output';\n const interruptAction = msg.metadata?.interrupt?.action;\n const isWriteFile = interruptAction === 'write_file_tool';\n const isEditFile = interruptAction === 'edit_file_tool';\n const writePath = (isWriteFile\n && typeof msg.metadata?.interrupt?.args?.path === 'string') ? msg.metadata?.interrupt?.args?.path : '';\n const editPath = (isEditFile\n && typeof msg.metadata?.interrupt?.args?.path === 'string') ? msg.metadata?.interrupt?.args?.path : '';\n const autoApproved = msg.metadata?.interrupt?.autoApproved;\n const headerRole = msg.role === 'user'\n ? '사용자'\n : msg.role === 'system'\n ? (isShellOutput ? 'shell 실행' : (autoApproved ? '자동 승인됨' : '승인 요청'))\n : 'Agent';\n return (React.createElement(\"div\", { key: msg.id, className: isAssistant\n ? 'jp-agent-message jp-agent-message-assistant-inline'\n : `jp-agent-message jp-agent-message-${msg.role}${isShellOutput ? ' jp-agent-message-shell-output' : ''}` },\n !isAssistant && (React.createElement(\"div\", { className: \"jp-agent-message-header\" },\n React.createElement(\"span\", { className: \"jp-agent-message-role\" }, headerRole),\n React.createElement(\"span\", { className: \"jp-agent-message-time\" }, new Date(msg.timestamp).toLocaleTimeString()))),\n React.createElement(\"div\", { className: `jp-agent-message-content${streamingMessageId === msg.id ? ' streaming' : ''}${isShellOutput ? ' jp-agent-message-content-shell' : ''}` }, msg.role === 'system' && msg.metadata?.interrupt ? (React.createElement(\"div\", { className: `jp-agent-interrupt-inline${msg.metadata?.interrupt?.autoApproved ? ' jp-agent-interrupt-auto-approved' : ''}` },\n !msg.metadata?.interrupt?.autoApproved && (React.createElement(\"div\", { className: \"jp-agent-interrupt-description\" }, msg.content)),\n React.createElement(\"div\", { className: \"jp-agent-interrupt-action\" },\n React.createElement(\"div\", { className: \"jp-agent-interrupt-action-args\" }, (() => {\n const command = msg.metadata?.interrupt?.args?.command;\n const code = msg.metadata?.interrupt?.args?.code || msg.metadata?.interrupt?.args?.content || '';\n // Handle edit_file_tool with diff preview\n let snippet;\n let language;\n if (isEditFile) {\n const oldStr = msg.metadata?.interrupt?.args?.old_string || '';\n const newStr = msg.metadata?.interrupt?.args?.new_string || '';\n const replaceAll = msg.metadata?.interrupt?.args?.replace_all;\n // Generate simple diff preview\n const oldPreview = oldStr.length > 500 ? oldStr.slice(0, 500) + '...' : oldStr;\n const newPreview = newStr.length > 500 ? newStr.slice(0, 500) + '...' : newStr;\n snippet = `--- ${editPath} (before)\\n+++ ${editPath} (after)\\n` +\n oldPreview.split('\\n').map((line) => `-${line}`).join('\\n') + '\\n' +\n newPreview.split('\\n').map((line) => `+${line}`).join('\\n') +\n (replaceAll ? '\\n\\n(replace_all: true)' : '');\n language = 'diff';\n }\n else {\n snippet = (command || code || '(no details)');\n language = command ? 'bash' : 'python';\n }\n const resolved = msg.metadata?.interrupt?.resolved;\n const decision = msg.metadata?.interrupt?.decision;\n const autoApproved = msg.metadata?.interrupt?.autoApproved;\n // 자동 승인일 때는 코드블럭 헤더에 배지가 표시되므로 actionHtml에는 표시하지 않음\n const resolvedText = autoApproved ? '' : (decision === 'reject' ? '거부됨' : '승인됨');\n const resolvedClass = autoApproved ? '' : (decision === 'reject' ? 'jp-agent-interrupt-actions--rejected' : 'jp-agent-interrupt-actions--resolved');\n const actionHtml = resolved && !autoApproved\n ? `<div class=\"jp-agent-interrupt-actions ${resolvedClass}\">${resolvedText}</div>`\n : resolved && autoApproved\n ? '' // 자동 승인일 때는 actionHtml 비움 (코드블럭 헤더에 배지 표시)\n : `\n<div class=\"code-block-actions jp-agent-interrupt-actions\">\n <button class=\"jp-agent-interrupt-approve-btn\" data-action=\"approve\">승인</button>\n <button class=\"jp-agent-interrupt-reject-btn\" data-action=\"reject\">거부</button>\n</div>\n`;\n const renderedHtml = (() => {\n let html = formatMarkdownToHtml(`\\n\\`\\`\\`${language}\\n${snippet}\\n\\`\\`\\``);\n if (isWriteFile && writePath) {\n const safePath = escapeHtml(writePath);\n html = html.replace(/<span class=\"code-block-language\">[^<]*<\\/span>/, `<span class=\"code-block-language jp-agent-interrupt-path\">${safePath}</span>`);\n }\n if (isEditFile && editPath) {\n const safePath = escapeHtml(editPath);\n html = html.replace(/<span class=\"code-block-language\">[^<]*<\\/span>/, `<span class=\"code-block-language jp-agent-interrupt-path\">✏️ ${safePath}</span>`);\n }\n // actionHtml이 비어있지 않을 때만 추가\n return actionHtml ? html.replace('</div>', `${actionHtml}</div>`) : html;\n })();\n return (React.createElement(\"div\", { className: \"jp-RenderedHTMLCommon\", style: { padding: '0 4px' }, dangerouslySetInnerHTML: { __html: renderedHtml }, onClick: (event) => {\n const target = event.target;\n const action = target?.getAttribute?.('data-action');\n if (msg.metadata?.interrupt?.resolved) {\n return;\n }\n if (action === 'approve') {\n handleResumeAgent('approve');\n }\n else if (action === 'reject') {\n handleResumeAgent('reject');\n }\n } }));\n })())))) : msg.role === 'assistant' ? (\n // Assistant(AI) 메시지: 마크다운 HTML 렌더링 + Jupyter 스타일 적용\n React.createElement(\"div\", { className: \"jp-RenderedHTMLCommon\", style: { padding: '0 5px' }, dangerouslySetInnerHTML: { __html: formatMarkdownToHtml(msg.content) } })) : (\n // User(사용자) 메시지: 텍스트 그대로 줄바꿈만 처리\n React.createElement(\"div\", { style: { whiteSpace: 'pre-wrap' } }, msg.content)))));\n }\n else {\n // Agent 실행 메시지\n return (React.createElement(\"div\", { key: msg.id, className: \"jp-agent-message jp-agent-message-agent-execution\" }, renderAgentExecutionMessage(msg)));\n }\n })),\n showConsoleErrorNotification && lastConsoleError && (React.createElement(\"div\", { className: \"jp-agent-console-error-notification\" },\n React.createElement(\"div\", { className: \"jp-agent-console-error-header\" },\n React.createElement(\"svg\", { viewBox: \"0 0 16 16\", fill: \"currentColor\", width: \"16\", height: \"16\" },\n React.createElement(\"path\", { d: \"M8.982 1.566a1.13 1.13 0 00-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 01-1.1 0L7.1 5.995A.905.905 0 018 5zm.002 6a1 1 0 110 2 1 1 0 010-2z\" })),\n React.createElement(\"span\", null, \"Console\\uC5D0\\uC11C Python \\uC5D0\\uB7EC\\uAC00 \\uAC10\\uC9C0\\uB418\\uC5C8\\uC2B5\\uB2C8\\uB2E4\")),\n React.createElement(\"div\", { className: \"jp-agent-console-error-preview\" },\n lastConsoleError.slice(0, 200),\n lastConsoleError.length > 200 ? '...' : ''),\n React.createElement(\"div\", { className: \"jp-agent-console-error-actions\" },\n React.createElement(\"button\", { className: \"jp-agent-console-error-fix-btn\", onClick: () => {\n // 에러를 자동으로 입력창에 넣고 파일 수정 요청\n setInput(`다음 에러를 분석하고 수정해주세요:\\n\\n${lastConsoleError}`);\n setShowConsoleErrorNotification(false);\n // 입력창에 포커스\n const textarea = document.querySelector('.jp-agent-input');\n if (textarea)\n textarea.focus();\n } }, \"\\uC5D0\\uB7EC \\uBD84\\uC11D \\uC694\\uCCAD\"),\n React.createElement(\"button\", { className: \"jp-agent-console-error-dismiss-btn\", onClick: () => setShowConsoleErrorNotification(false) }, \"\\uB2EB\\uAE30\")))),\n pendingFileFixes.length > 0 && (React.createElement(\"div\", { className: \"jp-agent-file-fixes\" },\n React.createElement(\"div\", { className: \"jp-agent-file-fixes-header\" },\n React.createElement(\"svg\", { viewBox: \"0 0 16 16\", fill: \"currentColor\", width: \"14\", height: \"14\" },\n React.createElement(\"path\", { d: \"M14 1H2a1 1 0 00-1 1v12a1 1 0 001 1h12a1 1 0 001-1V2a1 1 0 00-1-1zM2 0a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V2a2 2 0 00-2-2H2z\" }),\n React.createElement(\"path\", { d: \"M9.5 5a.5.5 0 00-1 0v3.793L6.854 7.146a.5.5 0 10-.708.708l2.5 2.5a.5.5 0 00.708 0l2.5-2.5a.5.5 0 00-.708-.708L9.5 8.793V5z\" })),\n React.createElement(\"span\", null,\n \"\\uC218\\uC815\\uB41C \\uD30C\\uC77C (\",\n pendingFileFixes.length,\n \"\\uAC1C)\")),\n React.createElement(\"div\", { className: \"jp-agent-file-fixes-list\" }, pendingFileFixes.map((fix, index) => (React.createElement(\"div\", { key: index, className: \"jp-agent-file-fix-item\" },\n React.createElement(\"div\", { className: \"jp-agent-file-fix-info\" },\n React.createElement(\"svg\", { viewBox: \"0 0 16 16\", fill: \"currentColor\", width: \"12\", height: \"12\" },\n React.createElement(\"path\", { d: \"M4 0a2 2 0 00-2 2v12a2 2 0 002 2h8a2 2 0 002-2V4.5L9.5 0H4zm5.5 1.5v2a1 1 0 001 1h2l-3-3zM4.5 8a.5.5 0 010 1h7a.5.5 0 010-1h-7zm0 2a.5.5 0 010 1h7a.5.5 0 010-1h-7zm0 2a.5.5 0 010 1h4a.5.5 0 010-1h-4z\" })),\n React.createElement(\"span\", { className: \"jp-agent-file-fix-path\" }, fix.path)),\n React.createElement(\"button\", { className: \"jp-agent-file-fix-apply\", onClick: () => applyFileFix(fix), title: `${fix.path} 파일에 수정 적용` }, \"\\uC801\\uC6A9\\uD558\\uAE30\"))))),\n React.createElement(\"button\", { className: \"jp-agent-file-fixes-dismiss\", onClick: () => setPendingFileFixes([]), title: \"\\uC218\\uC815 \\uC81C\\uC548 \\uB2EB\\uAE30\" }, \"\\uB2EB\\uAE30\"))),\n React.createElement(\"div\", { ref: messagesEndRef })),\n inputMode !== 'chat' && todos.length > 0 && (React.createElement(\"div\", { className: \"jp-agent-todo-compact\" },\n React.createElement(\"div\", { className: \"jp-agent-todo-compact-header\", onClick: () => setIsTodoExpanded(!isTodoExpanded) },\n React.createElement(\"div\", { className: \"jp-agent-todo-compact-left\" },\n React.createElement(\"svg\", { className: `jp-agent-todo-expand-icon ${isTodoExpanded ? 'jp-agent-todo-expand-icon--expanded' : ''}`, viewBox: \"0 0 16 16\", fill: \"currentColor\", width: \"12\", height: \"12\" },\n React.createElement(\"path\", { d: \"M6 12l4-4-4-4\" })),\n (() => {\n const currentTodo = todos.find(t => t.status === 'in_progress') || todos.find(t => t.status === 'pending');\n const isStillWorking = isStreaming || isLoading || isAgentRunning;\n if (currentTodo) {\n return (React.createElement(React.Fragment, null,\n React.createElement(\"div\", { className: \"jp-agent-todo-compact-spinner\" }),\n React.createElement(\"span\", { className: \"jp-agent-todo-compact-current\" }, currentTodo.content)));\n }\n else if (isStillWorking) {\n return (React.createElement(React.Fragment, null,\n React.createElement(\"div\", { className: \"jp-agent-todo-compact-spinner\" }),\n React.createElement(\"span\", { className: \"jp-agent-todo-compact-current\" }, \"\\uC791\\uC5C5 \\uB9C8\\uBB34\\uB9AC \\uC911...\")));\n }\n else {\n return (React.createElement(\"span\", { className: \"jp-agent-todo-compact-current\" }, \"\\u2713 \\uBAA8\\uB4E0 \\uC791\\uC5C5 \\uC644\\uB8CC\"));\n }\n })()),\n React.createElement(\"span\", { className: \"jp-agent-todo-compact-progress\" },\n todos.filter(t => t.status === 'completed').length,\n \"/\",\n todos.length)),\n statusText && hasActiveTodos && (React.createElement(\"div\", { className: `jp-agent-message jp-agent-message-debug jp-agent-message-debug--inline${statusText.startsWith('오류:') ? ' jp-agent-message-debug-error' : ''}` },\n React.createElement(\"div\", { className: \"jp-agent-debug-content\" },\n React.createElement(\"div\", { className: \"jp-agent-debug-branch\", \"aria-hidden\": \"true\" }),\n React.createElement(\"span\", { className: \"jp-agent-debug-text\" }, statusText),\n !statusText.startsWith('오류:') && (React.createElement(\"span\", { className: \"jp-agent-debug-ellipsis\", \"aria-hidden\": \"true\" }))))),\n isTodoExpanded && (React.createElement(\"div\", { className: \"jp-agent-todo-expanded\" }, todos.map((todo, index) => (React.createElement(\"div\", { key: index, className: `jp-agent-todo-item jp-agent-todo-item--${todo.status}` },\n React.createElement(\"div\", { className: \"jp-agent-todo-item-indicator\" },\n todo.status === 'completed' && (React.createElement(\"svg\", { viewBox: \"0 0 16 16\", fill: \"currentColor\", width: \"12\", height: \"12\" },\n React.createElement(\"path\", { d: \"M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z\" }))),\n todo.status === 'in_progress' && React.createElement(\"div\", { className: \"jp-agent-todo-item-spinner\" }),\n todo.status === 'pending' && React.createElement(\"span\", { className: \"jp-agent-todo-item-number\" }, index + 1)),\n React.createElement(\"span\", { className: `jp-agent-todo-item-text ${todo.status === 'completed' ? 'jp-agent-todo-item-text--done' : ''}` }, todo.content)))))))),\n statusText && !hasActiveTodos && (React.createElement(\"div\", { className: `jp-agent-message jp-agent-message-debug jp-agent-message-debug--above-input${statusText.startsWith('오류:') ? ' jp-agent-message-debug-error' : ''}` },\n React.createElement(\"div\", { className: \"jp-agent-debug-content\" },\n React.createElement(\"span\", { className: \"jp-agent-debug-text\" }, statusText),\n !statusText.startsWith('오류:') && (React.createElement(\"span\", { className: \"jp-agent-debug-ellipsis\", \"aria-hidden\": \"true\" }))))),\n React.createElement(\"div\", { className: \"jp-agent-input-container\" },\n React.createElement(\"div\", { className: \"jp-agent-input-wrapper\" },\n React.createElement(\"textarea\", { className: `jp-agent-input ${inputMode !== 'chat' ? 'jp-agent-input--agent-mode' : ''} ${isRejectionMode ? 'jp-agent-input--rejection-mode' : ''}`, value: input, onChange: (e) => setInput(e.target.value), onKeyDown: handleKeyDown, placeholder: isRejectionMode\n ? '다른 방향 제시'\n : (inputMode === 'agent'\n ? '노트북 작업을 입력하세요... (예: 데이터 시각화 해줘)'\n : inputMode === 'agent_v2'\n ? 'Deep Agent 요청을 입력하세요... (HITL 승인 모드)'\n : '메시지를 입력하세요...'), rows: 3, disabled: isLoading || isAgentRunning }),\n React.createElement(\"button\", { className: \"jp-agent-send-button\", onClick: handleSendMessage, disabled: (!input.trim() && !isRejectionMode) || isLoading || isStreaming || isAgentRunning, title: isRejectionMode ? \"거부 전송 (Enter)\" : \"전송 (Enter)\" }, isAgentRunning ? '실행 중...' : (isRejectionMode ? '거부' : '전송'))),\n React.createElement(\"div\", { className: \"jp-agent-mode-bar\" },\n React.createElement(\"div\", { className: \"jp-agent-mode-toggle-container\" },\n React.createElement(\"button\", { className: `jp-agent-mode-toggle ${inputMode !== 'chat' ? 'jp-agent-mode-toggle--active' : ''}`, onClick: toggleMode, title: `${inputMode === 'chat' ? 'Chat' : inputMode === 'agent' ? 'Agent' : 'Agent V2'} 모드 (⇧Tab)` },\n React.createElement(\"svg\", { className: \"jp-agent-mode-icon\", viewBox: \"0 0 16 16\", fill: \"currentColor\", width: \"14\", height: \"14\" }, inputMode === 'chat' ? (\n // 채팅 아이콘 (Chat 모드)\n React.createElement(\"path\", { d: \"M8 1C3.58 1 0 4.13 0 8c0 1.5.5 2.88 1.34 4.04L.5 15l3.37-.92A8.56 8.56 0 008 15c4.42 0 8-3.13 8-7s-3.58-7-8-7zM4.5 9a1 1 0 110-2 1 1 0 010 2zm3.5 0a1 1 0 110-2 1 1 0 010 2zm3.5 0a1 1 0 110-2 1 1 0 010 2z\" })) : inputMode === 'agent' ? (\n // 무한대 아이콘 (Agent 모드)\n React.createElement(\"path\", { d: \"M4.5 8c0-1.38 1.12-2.5 2.5-2.5.9 0 1.68.48 2.12 1.2L8 8l1.12 1.3c-.44.72-1.22 1.2-2.12 1.2-1.38 0-2.5-1.12-2.5-2.5zm6.88 1.3c.44-.72 1.22-1.2 2.12-1.2 1.38 0 2.5 1.12 2.5 2.5s-1.12 2.5-2.5 2.5c-.9 0-1.68-.48-2.12-1.2L12.5 10.6c.3.24.68.4 1.1.4.83 0 1.5-.67 1.5-1.5S14.43 8 13.6 8c-.42 0-.8.16-1.1.4l-1.12 1.3zM7 9.5c-.42 0-.8-.16-1.1-.4L4.78 7.8c-.44.72-1.22 1.2-2.12 1.2C1.28 9 .17 7.88.17 6.5S1.29 4 2.67 4c.9 0 1.68.48 2.12 1.2L5.9 6.5c-.3-.24-.68-.4-1.1-.4C3.97 6.1 3.3 6.77 3.3 7.6s.67 1.5 1.5 1.5c.42 0 .8-.16 1.1-.4l1.12-1.3L8 8l-1 1.5z\" })) : (\n // 뇌 아이콘 (Agent V2 모드)\n React.createElement(\"path\", { d: \"M8 0a8 8 0 100 16A8 8 0 008 0zm.5 11.5h-1v-1h1v1zm1.7-4.3c-.4.5-.7.8-.7 1.3v.5h-1v-.5c0-.8.4-1.3.8-1.8.4-.4.7-.7.7-1.2 0-.6-.4-1-1-1-.5 0-.9.3-1 .8l-1-.2c.2-.9 1-1.6 2-1.6 1.1 0 2 .8 2 1.9 0 .8-.4 1.3-.8 1.8z\" }))),\n React.createElement(\"span\", { className: \"jp-agent-mode-label\" }, inputMode === 'chat' ? 'Chat' : inputMode === 'agent' ? 'Agent' : 'Agent V2'),\n React.createElement(\"svg\", { className: \"jp-agent-mode-chevron\", viewBox: \"0 0 16 16\", fill: \"currentColor\", width: \"12\", height: \"12\" },\n React.createElement(\"path\", { d: \"M4.47 5.47a.75.75 0 011.06 0L8 7.94l2.47-2.47a.75.75 0 111.06 1.06l-3 3a.75.75 0 01-1.06 0l-3-3a.75.75 0 010-1.06z\" }))),\n showModeDropdown && (React.createElement(\"div\", { className: \"jp-agent-mode-dropdown\" },\n React.createElement(\"button\", { className: `jp-agent-mode-option ${inputMode === 'chat' ? 'jp-agent-mode-option--selected' : ''}`, onClick: () => { setInputMode('chat'); setShowModeDropdown(false); } },\n React.createElement(\"svg\", { viewBox: \"0 0 16 16\", fill: \"currentColor\", width: \"14\", height: \"14\" },\n React.createElement(\"path\", { d: \"M8 1C3.58 1 0 4.13 0 8c0 1.5.5 2.88 1.34 4.04L.5 15l3.37-.92A8.56 8.56 0 008 15c4.42 0 8-3.13 8-7s-3.58-7-8-7zM4.5 9a1 1 0 110-2 1 1 0 010 2zm3.5 0a1 1 0 110-2 1 1 0 010 2zm3.5 0a1 1 0 110-2 1 1 0 010 2z\" })),\n React.createElement(\"span\", null, \"Chat\"),\n React.createElement(\"span\", { className: \"jp-agent-mode-shortcut\" }, \"\\uC77C\\uBC18 \\uB300\\uD654\")),\n React.createElement(\"button\", { className: `jp-agent-mode-option ${inputMode === 'agent' ? 'jp-agent-mode-option--selected' : ''}`, onClick: () => { setInputMode('agent'); setShowModeDropdown(false); } },\n React.createElement(\"svg\", { viewBox: \"0 0 16 16\", fill: \"currentColor\", width: \"14\", height: \"14\" },\n React.createElement(\"path\", { d: \"M4.5 8c0-1.38 1.12-2.5 2.5-2.5.9 0 1.68.48 2.12 1.2L8 8l1.12 1.3c-.44.72-1.22 1.2-2.12 1.2-1.38 0-2.5-1.12-2.5-2.5z\" })),\n React.createElement(\"span\", null, \"Agent\"),\n React.createElement(\"span\", { className: \"jp-agent-mode-shortcut\" }, \"\\uB178\\uD2B8\\uBD81 \\uC790\\uB3D9 \\uC2E4\\uD589\")),\n React.createElement(\"button\", { className: `jp-agent-mode-option ${inputMode === 'agent_v2' ? 'jp-agent-mode-option--selected' : ''}`, onClick: () => { setInputMode('agent_v2'); setShowModeDropdown(false); } },\n React.createElement(\"svg\", { viewBox: \"0 0 16 16\", fill: \"currentColor\", width: \"14\", height: \"14\" },\n React.createElement(\"path\", { d: \"M8 0a8 8 0 100 16A8 8 0 008 0zm.5 11.5h-1v-1h1v1zm1.7-4.3c-.4.5-.7.8-.7 1.3v.5h-1v-.5c0-.8.4-1.3.8-1.8.4-.4.7-.7.7-1.2 0-.6-.4-1-1-1-.5 0-.9.3-1 .8l-1-.2c.2-.9 1-1.6 2-1.6 1.1 0 2 .8 2 1.9 0 .8-.4 1.3-.8 1.8z\" })),\n React.createElement(\"span\", null, \"Agent V2\"),\n React.createElement(\"span\", { className: \"jp-agent-mode-shortcut\" }, \"Deep Agent (HITL)\"))))),\n React.createElement(\"div\", { className: \"jp-agent-mode-hints\" },\n React.createElement(\"span\", { className: \"jp-agent-mode-hint\" }, \"\\u21E7Tab \\uBAA8\\uB4DC \\uC804\\uD658\")))),\n fileSelectionMetadata && (React.createElement(FileSelectionDialog, { filename: fileSelectionMetadata.pattern, options: fileSelectionMetadata.options, message: fileSelectionMetadata.message, onSelect: handleFileSelect, onCancel: handleFileSelectCancel }))));\n});\nChatPanel.displayName = 'ChatPanel';\n/**\n * Agent Panel Widget\n */\nexport class AgentPanelWidget extends ReactWidget {\n constructor(apiService, notebookTracker, consoleTracker) {\n super();\n this.chatPanelRef = React.createRef();\n this.apiService = apiService;\n this.notebookTracker = notebookTracker;\n this.consoleTracker = consoleTracker;\n this.id = 'hdsp-agent-panel';\n this.title.caption = 'HDSP Agent Assistant';\n this.title.icon = hdspTabIcon;\n this.addClass('jp-agent-widget');\n }\n render() {\n return (React.createElement(ChatPanel, { ref: this.chatPanelRef, apiService: this.apiService, notebookTracker: this.notebookTracker, consoleTracker: this.consoleTracker }));\n }\n /**\n * Add a message from cell action\n * @param action - The action type (explain, fix, custom_prompt)\n * @param cellContent - The cell content\n * @param displayPrompt - The user-facing prompt to show in the UI\n * @param llmPrompt - The actual prompt to send to the LLM\n * @param cellId - The cell ID for applying code (optional)\n * @param cellIndex - The cell index (0-based) for applying code (optional)\n */\n addCellActionMessage(action, cellContent, displayPrompt, llmPrompt, cellId, cellIndex) {\n console.log('[AgentPanel] Cell action:', action);\n console.log('[AgentPanel] Display prompt:', displayPrompt);\n console.log('[AgentPanel] LLM prompt:', llmPrompt);\n console.log('[AgentPanel] Cell ID:', cellId);\n console.log('[AgentPanel] Cell Index:', cellIndex);\n if (!this.chatPanelRef.current) {\n console.error('[AgentPanel] ChatPanel ref not available');\n return;\n }\n // E, F, ? 버튼 클릭 시 항상 일반 대화(chat) 모드로 전환\n if (this.chatPanelRef.current.setInputMode) {\n this.chatPanelRef.current.setInputMode('chat');\n }\n // Store cell index for code application (preferred method)\n if (cellIndex !== undefined && this.chatPanelRef.current.setCurrentCellIndex) {\n this.chatPanelRef.current.setCurrentCellIndex(cellIndex);\n }\n // Store cell ID for code application (fallback)\n if (cellId && this.chatPanelRef.current.setCurrentCellId) {\n this.chatPanelRef.current.setCurrentCellId(cellId);\n }\n // Set the display prompt in the input field\n this.chatPanelRef.current.setInput(displayPrompt);\n // Store the LLM prompt\n this.chatPanelRef.current.setLlmPrompt(llmPrompt);\n // Automatically execute after a short delay to ensure state is updated\n setTimeout(() => {\n if (this.chatPanelRef.current) {\n this.chatPanelRef.current.handleSendMessage().catch(error => {\n console.error('[AgentPanel] Failed to send message automatically:', error);\n });\n }\n }, 100);\n }\n /**\n * Analyze entire notebook before saving\n * @param cells - Array of collected cells with content, output, and imports\n * @param onComplete - Callback to execute after analysis (perform save)\n */\n analyzeNotebook(cells, onComplete) {\n console.log('[AgentPanel] analyzeNotebook called with', cells.length, 'cells');\n if (!this.chatPanelRef.current) {\n console.error('[AgentPanel] ChatPanel ref not available');\n onComplete();\n return;\n }\n // 저장 시 검수도 항상 일반 대화(chat) 모드로 전환\n if (this.chatPanelRef.current.setInputMode) {\n this.chatPanelRef.current.setInputMode('chat');\n }\n // Create summary of notebook\n const totalCells = cells.length;\n const cellsWithErrors = cells.filter(cell => cell.output && (cell.output.includes('Error') ||\n cell.output.includes('Traceback') ||\n cell.output.includes('Exception'))).length;\n const cellsWithImports = cells.filter(cell => cell.imports && cell.imports.length > 0).length;\n const localImports = cells.reduce((acc, cell) => {\n if (cell.imports) {\n return acc + cell.imports.filter((imp) => imp.isLocal).length;\n }\n return acc;\n }, 0);\n // Create display prompt\n const displayPrompt = `전체 노트북 검수 요청 (${totalCells}개 셀)`;\n // Create detailed LLM prompt with all cells\n let llmPrompt = `다음은 저장하기 전의 Jupyter 노트북 전체 내용입니다. 모든 셀을 검토하고 개선 사항을 제안해주세요.\n\n## 노트북 요약\n- 전체 셀 수: ${totalCells}개\n- 에러가 있는 셀: ${cellsWithErrors}개\n- Import가 있는 셀: ${cellsWithImports}개\n- 로컬 모듈 import: ${localImports}개\n\n## 전체 셀 내용\n\n`;\n cells.forEach((cell, index) => {\n llmPrompt += `### 셀 ${index + 1} (ID: ${cell.id})\n\\`\\`\\`python\n${cell.content}\n\\`\\`\\`\n`;\n if (cell.output) {\n llmPrompt += `\n**실행 결과:**\n\\`\\`\\`\n${cell.output}\n\\`\\`\\`\n`;\n }\n if (cell.imports && cell.imports.length > 0) {\n llmPrompt += `\n**Imports:**\n`;\n cell.imports.forEach((imp) => {\n llmPrompt += `- \\`${imp.module}\\` (${imp.isLocal ? '로컬' : '표준 라이브러리'})\\n`;\n });\n }\n llmPrompt += '\\n---\\n\\n';\n });\n llmPrompt += `\n## 검수 요청 사항\n\n다음 형식으로 응답해주세요:\n\n### 1. 전반적인 코드 품질 평가\n(노트북 전체의 코드 품질, 구조, 일관성 등을 평가)\n\n### 2. 발견된 주요 이슈\n(에러, 경고, 잠재적 문제점 등을 나열)\n\n### 3. 셀별 개선 제안\n각 셀에 대해 구체적인 개선 사항이 있다면:\n- **셀 X**: (개선 사항)\n \\`\\`\\`python\n (개선된 코드)\n \\`\\`\\`\n\n### 4. 전반적인 개선 권장사항\n(노트북 전체를 개선하기 위한 일반적인 제안)\n\n### 5. 저장 권장 여부\n- ✅ **저장 권장**: (이유)\n- ⚠️ **수정 후 저장 권장**: (이유)\n- ❌ **저장 비권장**: (이유)\n`;\n // Set the display prompt in the input field\n this.chatPanelRef.current.setInput(displayPrompt);\n // Store the LLM prompt\n this.chatPanelRef.current.setLlmPrompt(llmPrompt);\n // Automatically execute after a short delay to ensure state is updated\n setTimeout(() => {\n if (this.chatPanelRef.current) {\n this.chatPanelRef.current.handleSendMessage().catch(error => {\n console.error('[AgentPanel] Failed to send message automatically:', error);\n }).finally(() => {\n // Store the onComplete callback to be executed after user reviews the analysis\n // For now, we'll execute it immediately\n // TODO: Add UI button to allow user to review and then save\n console.log('[AgentPanel] Analysis complete, executing onComplete callback');\n onComplete();\n });\n }\n }, 100);\n }\n}\n","/**\n * File Selection Dialog Component\n *\n * Shows multiple file options to user when a filename is ambiguous\n */\nimport React from 'react';\nexport const FileSelectionDialog = ({ filename, options, message, onSelect, onCancel }) => {\n return (React.createElement(\"div\", { className: \"file-selection-dialog\" },\n React.createElement(\"div\", { className: \"file-selection-overlay\", onClick: onCancel }),\n React.createElement(\"div\", { className: \"file-selection-content\" },\n React.createElement(\"h3\", null, \"\\uD30C\\uC77C \\uC120\\uD0DD\"),\n React.createElement(\"p\", { className: \"file-selection-message\" }, message),\n React.createElement(\"div\", { className: \"file-selection-options\" }, options.map((option, idx) => (React.createElement(\"button\", { key: idx, className: \"file-option-button\", onClick: () => onSelect(idx + 1) },\n React.createElement(\"span\", { className: \"file-option-number\" }, idx + 1),\n React.createElement(\"span\", { className: \"file-option-path\" }, option.relative))))),\n React.createElement(\"div\", { className: \"file-selection-actions\" },\n React.createElement(\"button\", { className: \"file-selection-cancel\", onClick: onCancel }, \"\\uCDE8\\uC18C\"))),\n React.createElement(\"style\", null, `\n .file-selection-dialog {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .file-selection-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n }\n\n .file-selection-content {\n position: relative;\n background: var(--jp-layout-color1);\n border: 1px solid var(--jp-border-color1);\n border-radius: 8px;\n padding: 24px;\n max-width: 600px;\n width: 90%;\n max-height: 80vh;\n overflow-y: auto;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n }\n\n .file-selection-content h3 {\n margin: 0 0 12px 0;\n color: var(--jp-ui-font-color1);\n font-size: 18px;\n font-weight: 600;\n }\n\n .file-selection-message {\n margin: 0 0 20px 0;\n color: var(--jp-ui-font-color2);\n font-size: 14px;\n white-space: pre-wrap;\n line-height: 1.6;\n }\n\n .file-selection-options {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 20px;\n }\n\n .file-option-button {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 16px;\n background: var(--jp-layout-color2);\n border: 1px solid var(--jp-border-color2);\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s;\n text-align: left;\n }\n\n .file-option-button:hover {\n background: var(--jp-layout-color3);\n border-color: var(--jp-brand-color1);\n transform: translateY(-1px);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n }\n\n .file-option-number {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n background: var(--jp-brand-color1);\n color: white;\n border-radius: 50%;\n font-weight: 600;\n font-size: 14px;\n flex-shrink: 0;\n }\n\n .file-option-path {\n flex: 1;\n color: var(--jp-ui-font-color1);\n font-family: var(--jp-code-font-family);\n font-size: 13px;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .file-selection-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n }\n\n .file-selection-cancel {\n padding: 8px 16px;\n background: transparent;\n border: 1px solid var(--jp-border-color2);\n border-radius: 4px;\n color: var(--jp-ui-font-color1);\n cursor: pointer;\n font-size: 14px;\n transition: all 0.2s;\n }\n\n .file-selection-cancel:hover {\n background: var(--jp-layout-color2);\n border-color: var(--jp-border-color1);\n }\n `)));\n};\n","/**\n * Prompt Generation Dialog Component\n * Dialog for entering prompts to generate notebooks\n */\nimport React, { useState } from 'react';\nimport { Dialog, DialogTitle, DialogContent, DialogActions, TextField, Button, Typography, Box, Chip } from '@mui/material';\nimport AutoFixHighIcon from '@mui/icons-material/AutoFixHigh';\nconst EXAMPLE_PROMPTS = [\n '타이타닉 생존자 예측을 dask와 lgbm으로 생성해줘',\n '주식 데이터 분석 및 시각화 노트북 만들어줘',\n 'Iris 데이터셋으로 분류 모델 학습하기',\n '시계열 데이터 분석 및 예측 모델 만들기'\n];\nexport const PromptGenerationDialog = ({ open, onClose, onGenerate }) => {\n const [prompt, setPrompt] = useState('');\n const [isGenerating, setIsGenerating] = useState(false);\n const [progress, setProgress] = useState(0);\n const [statusMessage, setStatusMessage] = useState('');\n const handleGenerate = () => {\n if (prompt.trim()) {\n setIsGenerating(true);\n onGenerate(prompt.trim());\n setPrompt('');\n setIsGenerating(false);\n onClose();\n }\n };\n const handleKeyPress = (event) => {\n if (event.key === 'Enter' && event.ctrlKey) {\n handleGenerate();\n }\n };\n const handleExampleClick = (example) => {\n setPrompt(example);\n };\n return (React.createElement(Dialog, { open: open, onClose: onClose, maxWidth: \"md\", fullWidth: true, PaperProps: {\n sx: {\n borderRadius: 2,\n minHeight: '400px'\n }\n } },\n React.createElement(DialogTitle, null,\n React.createElement(Box, { display: \"flex\", alignItems: \"center\", gap: 1 },\n React.createElement(AutoFixHighIcon, { color: \"primary\" }),\n React.createElement(Typography, { variant: \"h6\" }, \"HALO \\uD504\\uB86C\\uD504\\uD2B8\\uB85C \\uB178\\uD2B8\\uBD81 \\uC0DD\\uC131\"))),\n React.createElement(DialogContent, null,\n React.createElement(Box, { display: \"flex\", flexDirection: \"column\", gap: 2, mt: 1 },\n React.createElement(Typography, { variant: \"body2\", color: \"text.secondary\" }, \"\\uC6D0\\uD558\\uB294 \\uB178\\uD2B8\\uBD81\\uC758 \\uB0B4\\uC6A9\\uC744 \\uC790\\uC5F0\\uC5B4\\uB85C \\uC124\\uBA85\\uD574\\uC8FC\\uC138\\uC694. AI\\uAC00 \\uC790\\uB3D9\\uC73C\\uB85C \\uB178\\uD2B8\\uBD81\\uC744 \\uC0DD\\uC131\\uD569\\uB2C8\\uB2E4.\"),\n React.createElement(TextField, { autoFocus: true, multiline: true, rows: 6, fullWidth: true, variant: \"outlined\", placeholder: \"\\uC608: \\uD0C0\\uC774\\uD0C0\\uB2C9 \\uC0DD\\uC874\\uC790 \\uC608\\uCE21\\uC744 dask\\uC640 lgbm\\uC73C\\uB85C \\uC0DD\\uC131\\uD574\\uC918\", value: prompt, onChange: (e) => setPrompt(e.target.value), onKeyPress: handleKeyPress, disabled: isGenerating, sx: {\n '& .MuiOutlinedInput-root': {\n fontSize: '14px'\n }\n } }),\n React.createElement(Box, null,\n React.createElement(Typography, { variant: \"caption\", color: \"text.secondary\", display: \"block\", mb: 1 }, \"\\uC608\\uC2DC \\uD504\\uB86C\\uD504\\uD2B8 (\\uD074\\uB9AD\\uD558\\uC5EC \\uC0AC\\uC6A9):\"),\n React.createElement(Box, { display: \"flex\", flexWrap: \"wrap\", gap: 1 }, EXAMPLE_PROMPTS.map((example, index) => (React.createElement(Chip, { key: index, label: example, variant: \"outlined\", size: \"small\", onClick: () => handleExampleClick(example), sx: {\n cursor: 'pointer',\n '&:hover': {\n backgroundColor: 'action.hover'\n }\n } }))))),\n React.createElement(Box, { sx: {\n backgroundColor: 'action.hover',\n borderRadius: 1,\n padding: 2\n } },\n React.createElement(Typography, { variant: \"caption\", color: \"text.secondary\" },\n \"\\uD83D\\uDCA1 \",\n React.createElement(\"strong\", null, \"\\uD301:\"),\n React.createElement(\"br\", null),\n \"\\u2022 \\uC0AC\\uC6A9\\uD560 \\uB77C\\uC774\\uBE0C\\uB7EC\\uB9AC\\uB97C \\uBA85\\uC2DC\\uD558\\uBA74 \\uB354 \\uC815\\uD655\\uD569\\uB2C8\\uB2E4\",\n React.createElement(\"br\", null),\n \"\\u2022 \\uBD84\\uC11D \\uBAA9\\uC801\\uACFC \\uB370\\uC774\\uD130\\uB97C \\uAD6C\\uCCB4\\uC801\\uC73C\\uB85C \\uC124\\uBA85\\uD558\\uC138\\uC694\",\n React.createElement(\"br\", null),\n \"\\u2022 Ctrl + Enter\\uB85C \\uBE60\\uB974\\uAC8C \\uC0DD\\uC131\\uD560 \\uC218 \\uC788\\uC2B5\\uB2C8\\uB2E4\",\n React.createElement(\"br\", null),\n \"\\u2022 \\uC0DD\\uC131\\uC740 \\uBC31\\uADF8\\uB77C\\uC6B4\\uB4DC\\uC5D0\\uC11C \\uC9C4\\uD589\\uB418\\uBA70, \\uC644\\uB8CC\\uB418\\uBA74 \\uC54C\\uB9BC\\uC744 \\uBC1B\\uC2B5\\uB2C8\\uB2E4\")))),\n React.createElement(DialogActions, { sx: { padding: 2, paddingTop: 0 } },\n React.createElement(Button, { onClick: onClose, disabled: isGenerating }, \"\\uCDE8\\uC18C\"),\n React.createElement(Button, { onClick: handleGenerate, variant: \"contained\", disabled: !prompt.trim() || isGenerating, startIcon: React.createElement(AutoFixHighIcon, null) }, \"\\uC0DD\\uC131 \\uC2DC\\uC791\"))));\n};\n","/**\n * Settings Panel Component\n * Allows users to configure LLM provider settings\n *\n * API keys are stored in browser localStorage and sent with each request.\n * The Agent Server does not store API keys - it receives them with each request.\n */\nimport React, { useState, useEffect } from 'react';\nimport { getLLMConfig, saveLLMConfig, testApiKey, getDefaultLLMConfig, DEFAULT_LANGCHAIN_SYSTEM_PROMPT } from '../services/ApiKeyManager';\nexport const SettingsPanel = ({ onClose, onSave, currentConfig }) => {\n // Initialize from localStorage or props\n const initConfig = currentConfig || getLLMConfig() || getDefaultLLMConfig();\n const [provider, setProvider] = useState(initConfig.provider || 'gemini');\n const [isTesting, setIsTesting] = useState(false);\n const [testResults, setTestResults] = useState({});\n // Track active key index (first valid key by default)\n const [activeKeyIndex, setActiveKeyIndex] = useState(0);\n // Gemini settings - support multiple keys (max 10)\n const [geminiApiKeys, setGeminiApiKeys] = useState(initConfig.gemini?.apiKeys?.length\n ? initConfig.gemini.apiKeys\n : initConfig.gemini?.apiKey\n ? [initConfig.gemini.apiKey]\n : ['']);\n // Legacy single key for backward compatibility\n const geminiApiKey = geminiApiKeys[0] || '';\n // Validate gemini model - only allow valid options\n const validateGeminiModel = (model) => {\n const validModels = ['gemini-2.5-pro', 'gemini-2.5-flash'];\n if (model && validModels.includes(model)) {\n return model;\n }\n return 'gemini-2.5-flash';\n };\n const [geminiModel, setGeminiModel] = useState(validateGeminiModel(initConfig.gemini?.model));\n // vLLM settings\n const [vllmEndpoint, setVllmEndpoint] = useState(initConfig.vllm?.endpoint || 'http://localhost:8000');\n const [vllmApiKey, setVllmApiKey] = useState(initConfig.vllm?.apiKey || '');\n const [vllmModel, setVllmModel] = useState(initConfig.vllm?.model || 'meta-llama/Llama-2-7b-chat-hf');\n // OpenAI settings\n const [openaiApiKey, setOpenaiApiKey] = useState(initConfig.openai?.apiKey || '');\n const [openaiModel, setOpenaiModel] = useState(initConfig.openai?.model || 'gpt-4');\n const [systemPrompt, setSystemPrompt] = useState(initConfig.systemPrompt || '');\n const [workspaceRoot, setWorkspaceRoot] = useState(initConfig.workspaceRoot || '');\n const [autoApprove, setAutoApprove] = useState(Boolean(initConfig.autoApprove));\n const [idleTimeoutMinutes, setIdleTimeoutMinutes] = useState(initConfig.idleTimeoutMinutes ?? 60);\n // Update state when currentConfig changes\n useEffect(() => {\n if (currentConfig) {\n setProvider(currentConfig.provider || 'gemini');\n // Handle multiple keys\n if (currentConfig.gemini?.apiKeys?.length) {\n setGeminiApiKeys(currentConfig.gemini.apiKeys);\n }\n else if (currentConfig.gemini?.apiKey) {\n setGeminiApiKeys([currentConfig.gemini.apiKey]);\n }\n else {\n setGeminiApiKeys(['']);\n }\n setGeminiModel(validateGeminiModel(currentConfig.gemini?.model));\n setVllmEndpoint(currentConfig.vllm?.endpoint || 'http://localhost:8000');\n setVllmApiKey(currentConfig.vllm?.apiKey || '');\n setVllmModel(currentConfig.vllm?.model || 'meta-llama/Llama-2-7b-chat-hf');\n setOpenaiApiKey(currentConfig.openai?.apiKey || '');\n setOpenaiModel(currentConfig.openai?.model || 'gpt-4');\n setSystemPrompt(currentConfig.systemPrompt || getDefaultLLMConfig().systemPrompt || '');\n setWorkspaceRoot(currentConfig.workspaceRoot || '');\n setAutoApprove(Boolean(currentConfig.autoApprove));\n setIdleTimeoutMinutes(currentConfig.idleTimeoutMinutes ?? 60);\n }\n }, [currentConfig]);\n // Helper: Build LLM config from state\n const buildLLMConfig = () => ({\n provider,\n gemini: {\n apiKey: geminiApiKeys[0] || '',\n apiKeys: geminiApiKeys.filter(k => k && k.trim()),\n model: geminiModel\n },\n vllm: {\n endpoint: vllmEndpoint,\n apiKey: vllmApiKey,\n model: vllmModel\n },\n openai: {\n apiKey: openaiApiKey,\n model: openaiModel\n },\n workspaceRoot: workspaceRoot.trim() ? workspaceRoot.trim() : undefined,\n systemPrompt: systemPrompt && systemPrompt.trim() ? systemPrompt : undefined,\n autoApprove,\n idleTimeoutMinutes\n });\n // Handlers for multiple API keys\n const handleAddKey = () => {\n if (geminiApiKeys.length < 10) {\n setGeminiApiKeys([...geminiApiKeys, '']);\n }\n };\n const handleRemoveKey = (index) => {\n if (geminiApiKeys.length > 1) {\n const newKeys = geminiApiKeys.filter((_, i) => i !== index);\n setGeminiApiKeys(newKeys);\n }\n };\n const handleKeyChange = (index, value) => {\n const newKeys = [...geminiApiKeys];\n newKeys[index] = value;\n setGeminiApiKeys(newKeys);\n };\n const validKeyCount = geminiApiKeys.filter(k => k && k.trim()).length;\n const handleTest = async () => {\n setIsTesting(true);\n setTestResults({});\n if (provider === 'gemini') {\n // Test each Gemini key individually\n const validKeys = geminiApiKeys.map((k, i) => ({ key: k, index: i }))\n .filter(({ key }) => key && key.trim());\n for (const { key, index } of validKeys) {\n setTestResults(prev => ({ ...prev, [index]: 'testing' }));\n const testConfig = {\n provider: 'gemini',\n gemini: { apiKey: key, apiKeys: [key], model: geminiModel }\n };\n try {\n const result = await testApiKey(testConfig);\n setTestResults(prev => ({ ...prev, [index]: result.success ? 'success' : 'error' }));\n }\n catch {\n setTestResults(prev => ({ ...prev, [index]: 'error' }));\n }\n }\n }\n else {\n // For other providers, just test the single config\n try {\n const config = buildLLMConfig();\n const result = await testApiKey(config);\n setTestResults({ 0: result.success ? 'success' : 'error' });\n }\n catch {\n setTestResults({ 0: 'error' });\n }\n }\n setIsTesting(false);\n };\n // Handle clicking a key row to set it as active\n const handleSetActiveKey = (index) => {\n if (geminiApiKeys[index] && geminiApiKeys[index].trim()) {\n setActiveKeyIndex(index);\n }\n };\n const handleSave = () => {\n const config = buildLLMConfig();\n // Save to localStorage\n saveLLMConfig(config);\n // Notify parent component\n onSave(config);\n onClose();\n };\n // Get test status icon for a key\n const getTestStatusIcon = (index) => {\n const status = testResults[index];\n if (status === 'testing')\n return '⏳';\n if (status === 'success')\n return '✅';\n if (status === 'error')\n return '❌';\n return '';\n };\n return (React.createElement(\"div\", { className: \"jp-agent-settings-overlay\" },\n React.createElement(\"div\", { className: \"jp-agent-settings-dialog\" },\n React.createElement(\"div\", { className: \"jp-agent-settings-header\" },\n React.createElement(\"h2\", null, \"LLM \\uC124\\uC815\"),\n React.createElement(\"button\", { className: \"jp-agent-settings-close\", onClick: onClose, title: \"\\uB2EB\\uAE30\" }, \"\\u00D7\")),\n React.createElement(\"div\", { className: \"jp-agent-settings-content\" },\n React.createElement(\"div\", { className: \"jp-agent-settings-notice\" },\n React.createElement(\"span\", null, \"API \\uD0A4\\uB294 \\uBE0C\\uB77C\\uC6B0\\uC800\\uC5D0 \\uC548\\uC804\\uD558\\uAC8C \\uC800\\uC7A5\\uB418\\uBA70, \\uC694\\uCCAD \\uC2DC\\uC5D0\\uB9CC \\uC11C\\uBC84\\uB85C \\uC804\\uC1A1\\uB429\\uB2C8\\uB2E4.\")),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" }, \"\\uD504\\uB85C\\uBC14\\uC774\\uB354\"),\n React.createElement(\"select\", { className: \"jp-agent-settings-select\", value: provider, onChange: (e) => setProvider(e.target.value) },\n React.createElement(\"option\", { value: \"gemini\" }, \"Google Gemini\"),\n React.createElement(\"option\", { value: \"vllm\" }, \"vLLM\"),\n React.createElement(\"option\", { value: \"openai\" }, \"OpenAI\"))),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\", htmlFor: \"jp-agent-workspace-root\" },\n \"\\uC6CC\\uD06C\\uC2A4\\uD398\\uC774\\uC2A4 \\uB8E8\\uD2B8\",\n React.createElement(\"small\", { style: { fontWeight: 'normal', marginLeft: '8px', color: '#666' } }, \"\\uBE44\\uC6B0\\uBA74 \\uD604\\uC7AC \\uB178\\uD2B8\\uBD81 \\uD3F4\\uB354 \\uAE30\\uC900, \\uC808\\uB300/\\uC0C1\\uB300 \\uACBD\\uB85C \\uAC00\\uB2A5\")),\n React.createElement(\"input\", { id: \"jp-agent-workspace-root\", type: \"text\", className: \"jp-agent-settings-input\", value: workspaceRoot, onChange: (e) => setWorkspaceRoot(e.target.value), placeholder: \"\\uC608: /Users/you/project\", \"data-testid\": \"workspace-root-input\" })),\n provider === 'gemini' && (React.createElement(\"div\", { className: \"jp-agent-settings-provider\" },\n React.createElement(\"h3\", null, \"Gemini \\uC124\\uC815\"),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" },\n \"API \\uD0A4 (\",\n validKeyCount,\n \"/10)\",\n React.createElement(\"small\", { style: { fontWeight: 'normal', marginLeft: '8px', color: '#666' } }, \"Rate limit \\uC2DC \\uC790\\uB3D9 \\uB85C\\uD14C\\uC774\\uC158\")),\n geminiApiKeys.map((key, index) => (React.createElement(\"div\", { key: index, className: \"jp-agent-settings-key-row\", style: {\n display: 'flex',\n gap: '8px',\n marginBottom: '8px',\n alignItems: 'center',\n padding: '4px',\n borderRadius: '4px',\n background: activeKeyIndex === index && key && key.trim() ? 'rgba(66, 133, 244, 0.1)' : 'transparent',\n border: activeKeyIndex === index && key && key.trim() ? '1px solid rgba(66, 133, 244, 0.3)' : '1px solid transparent'\n } },\n React.createElement(\"input\", { type: \"radio\", name: \"activeKey\", checked: activeKeyIndex === index && key && key.trim() !== '', onChange: () => handleSetActiveKey(index), disabled: !key || !key.trim(), title: \"\\uD65C\\uC131 \\uD0A4\\uB85C \\uC124\\uC815\", style: { cursor: key && key.trim() ? 'pointer' : 'default' } }),\n React.createElement(\"span\", { style: {\n minWidth: '20px',\n color: '#666',\n fontSize: '12px'\n } },\n index + 1,\n \".\"),\n React.createElement(\"input\", { type: \"password\", className: \"jp-agent-settings-input\", style: { flex: 1 }, value: key, onChange: (e) => handleKeyChange(index, e.target.value), placeholder: \"AIza...\" }),\n getTestStatusIcon(index) && (React.createElement(\"span\", { style: { fontSize: '14px', minWidth: '20px' } }, getTestStatusIcon(index))),\n geminiApiKeys.length > 1 && (React.createElement(\"button\", { type: \"button\", className: \"jp-agent-settings-button-icon\", onClick: () => handleRemoveKey(index), title: \"\\uD0A4 \\uC0AD\\uC81C\", style: {\n padding: '4px 8px',\n background: '#ff4444',\n color: 'white',\n border: 'none',\n borderRadius: '4px',\n cursor: 'pointer'\n } }, \"\\u00D7\"))))),\n geminiApiKeys.length < 10 && (React.createElement(\"button\", { type: \"button\", className: \"jp-agent-settings-button jp-agent-settings-button-secondary\", onClick: handleAddKey, style: { marginTop: '8px' } }, \"+ \\uD0A4 \\uCD94\\uAC00\"))),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" }, \"\\uBAA8\\uB378\"),\n React.createElement(\"select\", { className: \"jp-agent-settings-select\", value: geminiModel, onChange: (e) => setGeminiModel(e.target.value) },\n React.createElement(\"option\", { value: \"gemini-2.5-flash\" }, \"Gemini 2.5 Flash\"),\n React.createElement(\"option\", { value: \"gemini-2.5-pro\" }, \"Gemini 2.5 Pro\"))))),\n provider === 'vllm' && (React.createElement(\"div\", { className: \"jp-agent-settings-provider\" },\n React.createElement(\"h3\", null, \"vLLM \\uC124\\uC815\"),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" }, \"\\uC11C\\uBC84 \\uC8FC\\uC18C\"),\n React.createElement(\"input\", { type: \"text\", className: \"jp-agent-settings-input\", value: vllmEndpoint, onChange: (e) => setVllmEndpoint(e.target.value), placeholder: \"http://localhost:8000\" })),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" }, \"API \\uD0A4 (\\uC120\\uD0DD\\uC0AC\\uD56D)\"),\n React.createElement(\"input\", { type: \"password\", className: \"jp-agent-settings-input\", value: vllmApiKey, onChange: (e) => setVllmApiKey(e.target.value), placeholder: \"API \\uD0A4\\uAC00 \\uD544\\uC694\\uD55C \\uACBD\\uC6B0 \\uC785\\uB825\" })),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" }, \"\\uBAA8\\uB378 \\uC774\\uB984\"),\n React.createElement(\"input\", { type: \"text\", className: \"jp-agent-settings-input\", value: vllmModel, onChange: (e) => setVllmModel(e.target.value), placeholder: \"meta-llama/Llama-2-7b-chat-hf\" })))),\n provider === 'openai' && (React.createElement(\"div\", { className: \"jp-agent-settings-provider\" },\n React.createElement(\"h3\", null, \"OpenAI \\uC124\\uC815\"),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" }, \"API \\uD0A4\"),\n React.createElement(\"input\", { type: \"password\", className: \"jp-agent-settings-input\", value: openaiApiKey, onChange: (e) => setOpenaiApiKey(e.target.value), placeholder: \"sk-...\" })),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" }, \"\\uBAA8\\uB378\"),\n React.createElement(\"select\", { className: \"jp-agent-settings-select\", value: openaiModel, onChange: (e) => setOpenaiModel(e.target.value) },\n React.createElement(\"option\", { value: \"gpt-4\" }, \"GPT-4\"),\n React.createElement(\"option\", { value: \"gpt-4-turbo\" }, \"GPT-4 Turbo\"),\n React.createElement(\"option\", { value: \"gpt-3.5-turbo\" }, \"GPT-3.5 Turbo\"))))),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" }, \"\\uC790\\uB3D9 \\uC2E4\\uD589 \\uC2B9\\uC778\"),\n React.createElement(\"label\", { className: \"jp-agent-settings-checkbox\" },\n React.createElement(\"input\", { type: \"checkbox\", checked: autoApprove, onChange: (e) => setAutoApprove(e.target.checked), \"data-testid\": \"auto-approve-checkbox\" }),\n React.createElement(\"span\", null, \"\\uC2B9\\uC778 \\uC5C6\\uC774 \\uBC14\\uB85C \\uC2E4\\uD589 (\\uCF54\\uB4DC/\\uD30C\\uC77C/\\uC178 \\uD3EC\\uD568)\"))),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\", htmlFor: \"jp-agent-idle-timeout\" },\n \"Idle Timeout (\\uBD84)\",\n React.createElement(\"small\", { style: { fontWeight: 'normal', marginLeft: '8px', color: '#666' } }, \"\\uBE44\\uD65C\\uB3D9 \\uC2DC \\uC790\\uB3D9 \\uC885\\uB8CC (0 = \\uBE44\\uD65C\\uC131\\uD654)\")),\n React.createElement(\"input\", { id: \"jp-agent-idle-timeout\", type: \"number\", className: \"jp-agent-settings-input\", value: idleTimeoutMinutes, onChange: (e) => setIdleTimeoutMinutes(Math.max(0, parseInt(e.target.value) || 0)), min: 0, max: 1440, placeholder: \"60\", style: { width: '120px' }, \"data-testid\": \"idle-timeout-input\" })),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" },\n \"System Prompt (LangChain)\",\n React.createElement(\"small\", { style: { fontWeight: 'normal', marginLeft: '8px', color: '#666' } }, \"LangChain \\uAE30\\uBC18 \\uC5D0\\uC774\\uC804\\uD2B8\\uC5D0\\uB9CC \\uC801\\uC6A9\\uB429\\uB2C8\\uB2E4.\")),\n React.createElement(\"textarea\", { className: \"jp-agent-settings-input jp-agent-settings-textarea\", value: systemPrompt, onChange: (e) => setSystemPrompt(e.target.value), rows: 8 }),\n React.createElement(\"div\", { className: \"jp-agent-settings-inline-actions\" },\n React.createElement(\"button\", { type: \"button\", className: \"jp-agent-settings-button jp-agent-settings-button-secondary jp-agent-settings-button-compact\", onClick: () => setSystemPrompt(DEFAULT_LANGCHAIN_SYSTEM_PROMPT) }, \"\\uAE30\\uBCF8\\uAC12\\uC73C\\uB85C \\uB418\\uB3CC\\uB9AC\\uAE30\")))),\n React.createElement(\"div\", { className: \"jp-agent-settings-footer\" },\n React.createElement(\"button\", { className: \"jp-agent-settings-button jp-agent-settings-button-secondary\", onClick: onClose }, \"\\uCDE8\\uC18C\"),\n React.createElement(\"button\", { className: \"jp-agent-settings-button jp-agent-settings-button-test\", onClick: handleTest, disabled: isTesting }, isTesting ? '테스트 중...' : 'API 테스트'),\n React.createElement(\"button\", { className: \"jp-agent-settings-button jp-agent-settings-button-primary\", onClick: handleSave }, \"\\uC800\\uC7A5\")))));\n};\n","/**\n * Task Progress Widget Component\n * Floating widget showing notebook generation progress\n */\nimport React, { useState } from 'react';\nimport { Card, CardContent, Typography, LinearProgress, Box, IconButton, Collapse, Chip, Button } from '@mui/material';\nimport CloseIcon from '@mui/icons-material/Close';\nimport ExpandMoreIcon from '@mui/icons-material/ExpandMore';\nimport ExpandLessIcon from '@mui/icons-material/ExpandLess';\nimport CheckCircleIcon from '@mui/icons-material/CheckCircle';\nimport ErrorIcon from '@mui/icons-material/Error';\nimport CancelIcon from '@mui/icons-material/Cancel';\nexport const TaskProgressWidget = ({ taskStatus, onClose, onCancel, onOpenNotebook }) => {\n const [expanded, setExpanded] = useState(true);\n const [showDetails, setShowDetails] = useState(false);\n const { status, progress, message, prompt, error, notebookPath } = taskStatus;\n const isComplete = status === 'completed';\n const isFailed = status === 'failed';\n const isCancelled = status === 'cancelled';\n const isRunning = status === 'running';\n const isDone = isComplete || isFailed || isCancelled;\n // Helper: Map status to color\n const getStatusColor = (currentStatus) => {\n switch (currentStatus) {\n case 'completed': return 'success';\n case 'failed': return 'error';\n case 'cancelled': return 'default';\n default: return 'primary';\n }\n };\n // Helper: Map status to icon\n const getStatusIcon = (currentStatus) => {\n switch (currentStatus) {\n case 'completed': return React.createElement(CheckCircleIcon, { fontSize: \"small\" });\n case 'failed': return React.createElement(ErrorIcon, { fontSize: \"small\" });\n case 'cancelled': return React.createElement(CancelIcon, { fontSize: \"small\" });\n default: return null;\n }\n };\n // Helper: Map status to Korean text\n const getStatusText = (currentStatus) => {\n const statusTextMap = {\n 'pending': '대기 중',\n 'running': '생성 중',\n 'completed': '완료',\n 'failed': '실패',\n 'cancelled': '취소됨'\n };\n return statusTextMap[currentStatus] || currentStatus;\n };\n return (React.createElement(Card, { sx: {\n position: 'fixed',\n bottom: 20,\n right: 20,\n width: expanded ? 380 : 320,\n maxWidth: '90vw',\n boxShadow: 3,\n zIndex: 1300,\n transition: 'all 0.3s ease'\n } },\n React.createElement(CardContent, { sx: { padding: 2, '&:last-child': { paddingBottom: 2 } } },\n React.createElement(Box, { display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", mb: 1 },\n React.createElement(Box, { display: \"flex\", alignItems: \"center\", gap: 1 },\n React.createElement(Typography, { variant: \"subtitle2\", fontWeight: \"bold\" }, \"\\uB178\\uD2B8\\uBD81 \\uC0DD\\uC131\"),\n React.createElement(Chip, { label: getStatusText(status), size: \"small\", color: getStatusColor(status), icon: getStatusIcon(status) || undefined })),\n React.createElement(Box, { display: \"flex\" },\n React.createElement(IconButton, { size: \"small\", onClick: () => setShowDetails(!showDetails) }, showDetails ? (React.createElement(ExpandLessIcon, { fontSize: \"small\" })) : (React.createElement(ExpandMoreIcon, { fontSize: \"small\" }))),\n React.createElement(IconButton, { size: \"small\", onClick: onClose },\n React.createElement(CloseIcon, { fontSize: \"small\" })))),\n !isDone && (React.createElement(Box, { mb: 2 },\n React.createElement(LinearProgress, { variant: \"determinate\", value: progress, sx: { height: 6, borderRadius: 1 } }),\n React.createElement(Box, { display: \"flex\", justifyContent: \"space-between\", mt: 0.5 },\n React.createElement(Typography, { variant: \"caption\", color: \"text.secondary\" }, message),\n React.createElement(Typography, { variant: \"caption\", color: \"text.secondary\" },\n progress,\n \"%\")))),\n isComplete && (React.createElement(Box, { mb: 2 },\n React.createElement(Typography, { variant: \"body2\", color: \"success.main\" },\n \"\\u2713 \",\n message))),\n isFailed && error && (React.createElement(Box, { mb: 2 },\n React.createElement(Typography, { variant: \"body2\", color: \"error.main\" },\n \"\\u2717 \",\n error))),\n React.createElement(Collapse, { in: showDetails },\n React.createElement(Box, { sx: {\n backgroundColor: 'action.hover',\n borderRadius: 1,\n padding: 1.5,\n mb: 2\n } },\n React.createElement(Typography, { variant: \"caption\", color: \"text.secondary\", display: \"block\", mb: 0.5 }, \"\\uD504\\uB86C\\uD504\\uD2B8:\"),\n React.createElement(Typography, { variant: \"body2\", sx: { wordBreak: 'break-word' } }, prompt),\n notebookPath && (React.createElement(React.Fragment, null,\n React.createElement(Typography, { variant: \"caption\", color: \"text.secondary\", display: \"block\", mt: 1, mb: 0.5 }, \"\\uC800\\uC7A5 \\uC704\\uCE58:\"),\n React.createElement(Typography, { variant: \"caption\", sx: { wordBreak: 'break-all', fontFamily: 'monospace' } }, notebookPath))))),\n React.createElement(Box, { display: \"flex\", gap: 1, justifyContent: \"flex-end\" },\n isRunning && (React.createElement(Button, { size: \"small\", variant: \"outlined\", onClick: onCancel }, \"\\uCDE8\\uC18C\")),\n isComplete && notebookPath && (React.createElement(Button, { size: \"small\", variant: \"contained\", onClick: onOpenNotebook }, \"\\uB178\\uD2B8\\uBD81 \\uC5F4\\uAE30\")),\n isDone && (React.createElement(Button, { size: \"small\", variant: \"text\", onClick: onClose }, \"\\uB2EB\\uAE30\"))))));\n};\n","/**\n * Jupyter Agent Extension Entry Point\n */\n// Import plugins\nimport { sidebarPlugin } from './plugins/sidebar-plugin';\nimport { cellButtonsPlugin } from './plugins/cell-buttons-plugin';\nimport { promptGenerationPlugin } from './plugins/prompt-generation-plugin';\nimport { saveInterceptorPlugin } from './plugins/save-interceptor-plugin';\nimport { idleMonitorPlugin } from './plugins/idle-monitor-plugin';\nimport { lspBridgePlugin } from './plugins/lsp-bridge-plugin';\n// Import styles\n// import '../style/index.css';\n/**\n * The main plugin export\n * Note: sidebarPlugin must load before cellButtonsPlugin\n * saveInterceptorPlugin loads last to intercept save operations\n */\nconst plugins = [\n sidebarPlugin,\n cellButtonsPlugin,\n promptGenerationPlugin,\n saveInterceptorPlugin,\n idleMonitorPlugin,\n lspBridgePlugin\n];\nexport default plugins;\n","/**\n * Logo SVG strings for HDSP Agent\n * These are inlined to avoid webpack module resolution issues with SVG imports\n */\nexport const headerLogoSvg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1280\" height=\"552\" viewBox=\"0 0 1280 552\">\n<g>\n<path d=\"M 509.50 487.44 C489.07,492.66 475.03,491.17 456.90,481.84 C443.64,475.02 431.71,461.09 425.78,445.49 L 423.50 439.50 L 423.50 352.50 C423.50,270.60 423.61,265.16 425.35,259.64 C429.92,245.21 434.83,237.05 444.80,227.32 C451.11,221.16 454.21,219.17 490.50,198.02 C499.30,192.89 511.23,185.89 517.00,182.47 C524.42,178.07 529.21,175.00 534.39,172.87 C546.27,167.97 560.17,167.97 612.52,168.00 C616.39,168.00 620.47,168.00 624.77,168.00 C696.61,168.00 696.69,168.00 698.60,170.10 C700.45,172.15 700.51,176.37 700.76,326.52 L 701.02 480.84 L 698.37 482.92 C695.82,484.93 694.64,485.00 662.87,485.00 C660.58,485.00 658.44,485.00 656.43,485.01 C635.73,485.03 629.05,485.04 626.93,481.94 C625.92,480.46 625.95,478.27 625.98,475.05 C625.99,474.40 626.00,473.70 626.00,472.95 C626.00,464.61 624.48,459.78 621.50,458.64 C619.31,457.80 613.27,459.19 584.00,467.25 C548.33,477.08 520.07,484.73 509.50,487.44 ZM 101.50 483.17 L 95.89 488.00 L 61.94 488.00 C29.33,488.00 27.92,487.92 26.00,486.00 C24.01,484.01 24.00,482.67 24.00,280.45 L 24.00 76.91 L 28.98 71.92 L 62.97 72.21 C96.81,72.50 96.96,72.51 99.22,74.78 L 101.50 77.05 L 102.00 159.37 C102.39,224.31 102.77,242.02 103.78,243.24 C106.88,246.97 108.55,247.04 192.33,246.77 L 274.16 246.50 L 280.00 239.97 L 280.00 159.53 C280.00,102.71 280.33,78.38 281.11,76.66 C283.11,72.27 285.27,72.00 318.40,72.00 C350.66,72.00 355.11,72.47 357.02,76.04 C357.65,77.22 358.00,150.36 358.00,280.86 L 358.00 483.85 L 355.37 485.93 C352.82,487.93 351.61,488.00 318.98,488.00 C287.23,488.00 285.08,487.89 282.86,486.09 L 280.50 484.18 L 280.00 398.62 C279.50,313.37 279.49,313.05 277.40,310.96 C276.25,309.80 273.55,308.45 271.40,307.95 C265.89,306.67 113.48,306.74 108.86,308.02 C106.86,308.58 104.61,309.77 103.86,310.67 C102.75,312.01 102.41,327.79 102.00,397.74 ZM 803.53 486.27 L 804.22 489.00 L 787.86 488.99 C776.68,488.98 770.71,488.57 769.00,487.71 L 766.50 486.44 L 766.25 262.22 C766.00,39.09 766.01,37.99 768.00,36.00 C769.92,34.08 771.32,34.00 802.44,34.00 L 834.89 34.00 L 837.69 36.41 L 840.50 38.83 L 839.87 387.35 L 834.03 395.42 C820.62,413.96 809.00,435.96 804.66,451.00 C800.58,465.15 800.32,473.46 803.53,486.27 ZM 503.02 426.72 C506.36,428.40 615.07,428.57 619.88,426.89 C620.83,426.56 621.63,426.38 622.31,425.99 C626.01,423.85 626.01,415.30 626.00,340.32 C626.00,336.25 626.00,331.99 626.00,327.52 C626.00,248.50 625.79,234.42 624.54,231.43 C624.09,230.36 623.73,229.47 623.18,228.73 C620.38,225.00 612.60,225.00 563.70,225.01 L 562.28 225.01 C528.10,225.01 507.13,225.39 504.75,226.05 C503.57,226.38 502.60,226.49 501.79,226.92 C497.98,228.96 497.98,238.13 498.01,311.07 C498.01,315.99 498.01,321.19 498.01,326.70 C498.02,329.49 498.02,332.20 498.02,334.83 C498.03,416.15 498.03,423.53 501.47,425.89 C501.92,426.20 502.44,426.42 503.02,426.72 ZM 807.50 386.63 C809.71,390.01 815.16,389.94 817.00,386.51 C818.51,383.68 817.20,379.40 814.51,378.36 C809.40,376.40 804.55,382.13 807.50,386.63 Z\" fill=\"rgb(33,33,33)\"/>\n<path d=\"M 920.88 447.94 C918.34,448.49 915.13,449.38 913.74,449.91 C910.57,451.11 908.44,449.01 904.19,440.50 C899.91,431.92 898.22,426.31 897.06,416.83 C896.19,409.61 894.33,331.10 894.94,327.00 C895.06,326.17 895.51,308.62 895.93,288.00 C896.77,246.77 896.82,246.38 903.51,234.42 C909.99,222.84 915.87,217.34 946.50,194.31 C966.26,179.45 975.52,173.41 982.84,170.64 C988.30,168.58 990.25,168.49 1037.00,168.20 C1070.48,167.98 1087.98,168.25 1093.50,169.07 C1097.90,169.71 1103.07,170.46 1105.00,170.72 C1111.37,171.59 1127.50,180.68 1133.24,186.65 C1134.74,188.22 1137.78,191.34 1139.99,193.59 C1142.19,195.85 1144.00,198.04 1144.00,198.46 C1144.00,198.89 1145.53,201.77 1147.39,204.87 C1155.34,218.08 1158.67,233.11 1159.17,258.00 L 1159.50 274.50 L 1154.09 281.00 C1143.99,293.15 1136.53,301.15 1121.14,316.34 L 1104.14 333.12 C1099.09,338.11 1090.51,345.00 1089.34,345.00 C1088.24,345.00 1088.00,335.76 1088.00,292.25 C1087.99,241.70 1087.91,239.37 1086.04,236.30 C1082.06,229.77 1083.02,229.88 1028.67,229.68 L 1025.26 229.67 C983.83,229.51 976.61,229.49 973.18,233.08 C972.36,233.93 971.76,234.99 970.96,236.30 C969.06,239.41 969.01,241.96 969.00,324.18 C969.00,375.59 969.38,409.86 969.96,411.38 C971.30,414.91 975.10,417.10 981.24,417.89 L 986.50 418.57 L 982.22 421.31 C969.10,429.74 933.62,445.14 920.88,447.94 ZM 1065.75 487.50 C1060.71,488.69 1051.19,488.95 1016.50,488.86 C976.07,488.76 961.65,488.05 959.65,486.05 C959.18,485.58 964.81,482.19 972.15,478.51 C979.49,474.83 985.95,471.47 986.50,471.06 C987.05,470.65 992.00,467.93 997.50,465.04 C1006.02,460.55 1010.72,457.80 1025.42,448.69 C1051.66,432.43 1059.19,427.34 1080.42,411.50 C1083.37,409.30 1087.05,406.60 1088.60,405.50 C1091.66,403.33 1101.28,395.70 1104.50,392.90 C1105.60,391.94 1109.65,388.56 1113.50,385.38 C1117.35,382.21 1123.19,377.34 1126.49,374.56 C1132.44,369.53 1134.33,367.82 1151.50,352.03 L 1160.50 343.74 L 1161.29 354.12 C1162.30,367.25 1161.38,391.47 1159.55,400.14 C1157.35,410.57 1152.30,421.50 1146.04,429.36 C1140.26,436.61 1123.66,450.88 1104.00,465.48 C1098.22,469.77 1091.90,474.51 1089.95,476.03 C1084.46,480.28 1072.67,485.87 1065.75,487.50 ZM 1169.50 127.53 C1167.30,128.25 1163.93,128.83 1162.00,128.80 C1158.73,128.77 1158.83,128.68 1163.50,127.48 C1170.40,125.70 1174.92,125.74 1169.50,127.53 Z\" fill=\"rgb(106,105,93)\"/>\n<path d=\"M 803.53 486.27 C800.32,473.46 800.58,465.15 804.66,451.00 C809.00,435.96 820.62,413.96 834.03,395.42 L 839.87 387.35 L 840.12 246.32 C840.05,327.94 840.28,384.00 840.74,384.00 C841.22,384.00 844.07,381.00 847.06,377.33 C856.97,365.17 869.10,351.67 882.38,338.00 L 895.04 324.98 C895.00,326.15 894.96,326.85 894.94,327.00 C894.43,330.45 895.66,386.60 896.59,408.61 C896.39,405.17 896.24,401.34 896.12,397.00 C895.78,385.17 895.15,375.50 894.73,375.50 C893.01,375.50 878.71,396.18 873.64,406.00 C868.53,415.89 864.99,426.58 865.01,432.06 C865.03,443.07 873.07,451.12 885.79,452.87 C888.93,453.30 892.85,453.52 894.50,453.35 C904.27,452.39 908.17,452.12 908.83,450.52 C909.20,449.61 908.51,448.25 907.25,446.08 C909.67,449.91 911.43,450.79 913.74,449.91 C915.13,449.38 918.34,448.49 920.88,447.94 C933.62,445.14 969.10,429.74 982.22,421.31 L 986.50 418.57 L 981.24,417.89 C979.60,417.68 978.13,417.37 976.82,416.96 C978.94,417.62 981.71,418.00 984.58,418.00 C990.13,418.00 991.42,417.55 999.08,412.92 C1015.33,403.11 1046.98,381.37 1060.00,371.06 C1070.76,362.54 1086.56,349.47 1087.23,348.53 C1087.64,347.96 1087.97,323.20 1087.98,293.50 C1087.99,248.96 1087.93,241.00 1086.73,237.69 C1087.93,240.98 1087.99,248.80 1088.00,292.25 C1088.00,335.76 1088.24,345.00 1089.34,345.00 C1090.51,345.00 1099.09,338.11 1104.14,333.12 L 1121.14 316.34 C1136.53,301.15 1143.99,293.15 1154.09,281.00 L 1159.50 274.50 L 1159.29 264.13 C1159.39,265.95 1159.51,267.46 1159.64,268.39 C1160.27,272.96 1160.32,273.02 1162.27,271.28 C1165.00,268.83 1174.79,254.08 1179.70,245.00 C1185.69,233.94 1188.00,226.29 1188.00,217.57 C1188.00,210.72 1187.75,209.82 1184.70,205.83 C1180.11,199.80 1174.31,196.83 1165.98,196.23 C1157.47,195.62 1144.00,196.83 1144.00,198.20 C1144.00,198.77 1145.53,201.77 1147.39,204.87 C1145.53,201.77 1144.00,198.89 1144.00,198.46 C1144.00,198.04 1142.19,195.85 1139.99,193.59 C1137.78,191.34 1134.74,188.22 1133.24,186.65 C1127.62,180.81 1112.05,171.97 1105.41,170.78 C1107.93,170.84 1112.45,169.46 1121.24,166.00 C1138.23,159.32 1157.80,152.80 1170.50,149.59 C1218.80,137.37 1251.17,146.42 1258.57,174.24 C1267.44,207.55 1234.44,267.03 1171.32,331.50 L 1161.53 341.50 L 1161.48 357.12 C1161.43,356.07 1161.37,355.07 1161.29,354.12 L 1160.50 343.74 L 1151.50 352.03 C1134.33,367.82 1132.44,369.53 1126.49,374.56 C1123.19,377.34 1117.35,382.21 1113.50,385.38 C1109.65,388.56 1105.60,391.94 1104.50,392.90 C1101.28,395.70 1091.66,403.33 1088.60,405.50 C1087.05,406.60 1083.37,409.30 1080.42,411.50 C1059.19,427.34 1051.66,432.43 1025.42,448.69 C1010.72,457.80 1006.02,460.55 997.50,465.04 C992.00,467.93 987.05,470.65 986.50,471.06 C985.95,471.47 979.49,474.83 972.15,478.51 C964.81,482.19 959.18,485.58 959.65,486.05 C960.30,486.69 962.24,487.21 965.94,487.61 C963.89,487.44 962.44,487.26 961.50,487.06 L 955.50 485.76 L 943.50 490.80 C921.92,499.86 902.62,505.99 883.00,510.03 C871.45,512.40 842.75,513.62 835.50,512.04 C822.79,509.28 813.50,503.48 807.62,494.62 L 803.98 489.13 L 796.50 488.99 L 804.22 489.00 ZM 905.64 533.57 C896.06,535.25 896.00,535.25 896.00,534.14 C896.00,533.66 899.26,532.48 903.25,531.52 C909.70,529.96 927.81,524.08 935.25,521.14 C937.53,520.23 938.00,519.44 938.00,516.48 C938.00,509.26 943.29,504.00 950.53,504.00 C955.94,504.00 961.54,508.58 963.07,514.25 C966.26,526.10 953.28,534.63 942.95,527.47 C939.79,525.28 938.91,525.08 936.40,526.04 C931.87,527.76 914.62,531.98 905.64,533.57 ZM 1119.13 157.43 C1117.46,158.30 1114.22,159.00 1111.93,159.00 C1108.42,159.00 1107.15,158.39 1103.88,155.12 C1100.32,151.55 1100.00,150.79 1100.00,145.75 C1100.00,139.49 1102.04,136.05 1106.98,134.01 C1110.91,132.38 1117.21,133.23 1120.54,135.85 C1122.67,137.53 1123.62,137.71 1125.82,136.87 C1133.46,133.92 1155.51,128.64 1166.41,126.80 C1165.51,126.99 1164.53,127.21 1163.50,127.48 C1158.83,128.68 1158.73,128.77 1162.00,128.80 C1163.35,128.82 1165.43,128.54 1167.30,128.12 C1166.23,128.41 1165.05,128.72 1163.79,129.02 C1154.63,131.26 1133.67,138.57 1127.94,141.53 C1125.68,142.70 1125.00,143.74 1125.00,146.06 C1125.00,150.77 1122.45,155.72 1119.13,157.43 ZM 807.50 386.63 C804.55,382.13 809.40,376.40 814.51,378.36 C817.20,379.40 818.51,383.68 817.00,386.51 C815.16,389.94 809.71,390.01 807.50,386.63 ZM 766.48 471.20 C766.05,448.49 766.00,393.72 766.00,261.88 C766.00,103.18 766.01,56.97 766.73,42.79 C766.02,56.96 766.07,103.22 766.25,262.22 ZM 1156.09 227.13 C1157.94,234.97 1158.95,243.50 1159.00,252.26 C1158.60,242.47 1157.67,234.29 1156.09,227.13 ZM 1161.13 385.96 C1160.55,399.03 1159.02,404.69 1154.94,414.49 C1153.90,417.00 1152.25,420.08 1150.43,423.03 C1154.62,416.14 1157.89,408.00 1159.55,400.14 C1160.21,397.02 1160.75,391.88 1161.13,385.96 ZM 1032.93 488.87 C1052.58,488.83 1060.25,488.53 1064.58,487.74 C1060.13,488.66 1053.62,488.94 1032.93,488.87 ZM 905.87 443.73 C902.29,437.54 900.07,432.51 898.65,426.02 C899.80,430.82 901.47,435.06 904.19,440.50 C904.79,441.70 905.34,442.77 905.87,443.73 ZM 975.01 416.25 C975.42,416.45 975.88,416.64 976.38,416.82 C973.05,415.68 970.90,413.86 969.96,411.38 C969.80,410.99 969.67,408.38 969.55,403.84 C969.73,408.97 969.97,411.38 970.28,412.00 C970.97,413.38 973.10,415.29 975.01,416.25 ZM 969.00 324.18 C969.01,257.37 969.04,243.16 970.08,238.52 C969.05,243.17 969.01,257.42 969.02,324.50 C969.02,334.90 969.03,344.15 969.04,352.36 C969.02,343.71 969.00,334.28 969.00,324.18 ZM 1170.13 127.31 C1172.20,126.57 1172.32,126.19 1171.17,126.17 C1171.98,126.12 1172.49,126.15 1172.62,126.29 C1172.75,126.42 1171.79,126.80 1170.13,127.31 ZM 769.01 487.71 C768.36,487.54 767.92,487.35 767.65,487.13 C767.60,487.09 767.55,487.03 767.51,486.95 L 769.00 487.71 Z\" fill=\"rgb(216,112,44)\"/>\n</g>\n</svg>`;\nexport const tabbarLogoSvg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1024\" height=\"1024\" viewBox=\"0 0 1024 1024\">\n<g>\n<path d=\"M 318.50 710.49 C315.20,711.26 311.55,712.17 310.40,712.52 C307.71,713.33 306.05,711.65 301.13,703.18 C293.61,690.23 288.22,675.88 285.58,661.76 C282.40,644.82 281.97,629.53 282.56,555.82 C282.88,515.84 282.77,482.31 282.32,481.32 C281.87,480.32 281.75,449.12 282.06,412.00 L 282.61 344.50 L 285.27 334.50 C288.45,322.58 295.99,307.08 304.32,295.38 C314.87,280.54 322.55,273.94 380.00,230.25 C429.66,192.50 443.04,185.40 469.17,182.96 C485.43,181.44 640.59,182.24 653.00,183.91 C663.20,185.28 679.81,189.55 681.96,191.36 C682.80,192.08 685.97,193.69 689.00,194.95 C692.03,196.20 696.97,198.70 700.00,200.49 C703.03,202.28 706.94,204.59 708.69,205.62 C720.60,212.62 725.49,217.04 745.14,238.56 C759.73,254.53 769.58,277.30 775.20,308.00 C776.94,317.54 777.38,324.66 777.74,349.72 L 778.18 379.95 L 769.84 390.09 C765.25,395.66 758.09,403.89 753.92,408.36 C749.75,412.84 743.50,419.88 740.04,424.00 C736.57,428.12 728.06,437.18 721.12,444.13 C714.18,451.08 706.91,458.50 704.97,460.63 C703.02,462.76 696.56,469.00 690.59,474.50 C676.24,487.74 665.08,498.19 657.61,505.39 C651.89,510.90 646.43,514.99 644.78,515.00 C644.38,515.00 643.93,470.79 643.78,416.75 L 643.50 318.50 L 641.33 314.46 C638.38,308.94 631.03,302.38 626.35,301.08 C623.75,300.36 593.15,300.01 531.79,300.01 C442.88,300.00 440.97,300.04 435.62,302.04 C429.58,304.30 424.10,309.57 421.38,315.73 C419.85,319.19 419.71,332.47 419.61,477.50 L 419.50 635.50 L 421.71 639.62 C425.88,647.44 433.20,652.01 442.74,652.77 C446.46,653.07 448.93,653.71 448.73,654.31 C448.19,655.93 436.40,662.87 426.50,667.40 C421.55,669.66 413.55,673.66 408.71,676.28 C403.88,678.90 396.68,682.36 392.71,683.95 C388.75,685.55 382.79,688.18 379.47,689.81 C376.15,691.44 371.26,693.51 368.60,694.41 C365.94,695.32 362.80,696.58 361.63,697.21 C360.46,697.85 356.80,699.14 353.50,700.10 C350.20,701.05 342.33,703.46 336.00,705.46 C329.67,707.45 321.80,709.72 318.50,710.49 ZM 590.03 786.96 C576.83,788.58 441.72,788.11 426.00,786.40 C412.11,784.88 402.89,782.97 402.86,781.60 C402.83,780.50 407.87,777.80 434.00,764.91 C458.43,752.86 468.21,747.76 473.23,744.45 C475.28,743.10 477.22,742.00 477.55,742.00 C478.06,742.00 490.62,734.77 495.00,731.96 C495.83,731.43 497.51,730.56 498.74,730.03 C501.03,729.03 511.25,722.59 521.00,715.98 C524.03,713.93 528.75,711.06 531.50,709.60 C534.25,708.14 540.33,704.49 545.00,701.50 C549.67,698.50 554.88,695.40 556.57,694.59 C558.25,693.78 562.75,690.70 566.57,687.73 C572.88,682.81 586.92,673.39 595.22,668.51 C599.11,666.22 605.83,661.28 611.01,656.89 C614.04,654.32 619.44,650.24 623.01,647.83 C635.20,639.59 657.62,622.53 670.00,612.08 C679.61,603.97 685.79,599.08 688.60,597.38 C690.31,596.35 696.26,591.28 701.82,586.12 C707.38,580.96 714.96,574.43 718.67,571.62 C722.37,568.80 729.70,562.41 734.95,557.42 C740.20,552.42 748.55,544.78 753.50,540.43 C758.45,536.09 765.17,529.68 768.43,526.20 C774.29,519.94 779.57,516.24 780.84,517.51 C782.11,518.78 782.23,597.03 780.98,606.00 C778.42,624.31 774.06,638.87 766.75,653.50 C760.23,666.57 754.14,674.86 742.50,686.54 C726.82,702.27 680.51,739.24 645.50,763.99 C627.99,776.36 607.61,784.80 590.03,786.96 Z\" fill=\"rgb(100,102,88)\"/>\n<path d=\"M 781.70 537.56 C781.54,526.19 781.25,517.91 780.84,517.51 C779.57,516.24 774.29,519.94 768.43,526.20 C765.17,529.68 758.45,536.09 753.50,540.43 C748.55,544.78 740.20,552.42 734.95,557.42 C729.70,562.41 722.37,568.80 718.67,571.62 C714.96,574.43 707.38,580.96 701.82,586.12 C696.26,591.28 690.31,596.35 688.60,597.38 C685.79,599.08 679.61,603.97 670.00,612.08 C657.62,622.53 635.20,639.59 623.01,647.83 C619.44,650.24 614.04,654.32 611.01,656.89 C605.83,661.28 599.11,666.22 595.22,668.51 C586.92,673.39 572.88,682.81 566.57,687.73 C562.75,690.70 558.25,693.78 556.57,694.59 C554.88,695.40 549.67,698.50 545.00,701.50 C540.33,704.49 534.25,708.14 531.50,709.60 C528.75,711.06 524.03,713.93 521.00,715.98 C511.25,722.59 501.03,729.03 498.74,730.03 C497.51,730.56 495.83,731.43 495.00,731.96 C490.62,734.77 478.06,742.00 477.55,742.00 C477.22,742.00 475.28,743.10 473.23,744.45 C468.21,747.76 458.43,752.86 434.00,764.91 C407.87,777.80 402.83,780.50 402.86,781.60 C402.88,782.29 405.26,783.13 409.41,783.98 C403.38,782.89 398.48,781.78 397.22,781.09 C396.96,780.94 396.71,780.85 396.67,780.69 C396.48,780.02 400.05,778.29 422.41,767.46 L 425.35 766.03 C436.82,760.48 449.65,753.98 453.85,751.59 C458.06,749.20 470.73,742.16 482.00,735.95 C493.27,729.74 507.23,721.66 513.00,717.99 C518.78,714.32 529.58,707.57 537.00,702.99 C552.59,693.37 587.09,670.61 597.88,662.83 C601.94,659.90 609.14,654.70 613.88,651.28 C618.62,647.86 625.79,642.69 629.81,639.78 C646.64,627.62 701.04,583.66 709.50,575.38 C711.70,573.23 716.04,569.55 719.14,567.20 C722.24,564.85 729.44,558.55 735.14,553.21 C740.84,547.86 747.97,541.38 751.00,538.81 C754.03,536.24 762.12,528.64 769.00,521.92 L 781.50 509.70 ZM 643.50 318.50 L 643.78 416.75 C643.93,470.79 644.38,515.00 644.78,515.00 C646.43,514.99 651.89,510.90 657.61,505.39 C665.08,498.19 676.24,487.74 690.59,474.50 C696.56,469.00 703.02,462.76 704.97,460.63 C706.91,458.50 714.18,451.08 721.12,444.13 C728.06,437.18 736.57,428.12 740.04,424.00 C743.50,419.88 749.75,412.84 753.92,408.36 C758.09,403.89 765.25,395.66 769.84,390.09 L 778.15 379.97 L 778.17 384.09 L 775.38 387.79 C770.92,393.70 756.87,410.31 754.47,412.50 C753.27,413.60 746.65,420.80 739.76,428.50 C732.87,436.20 725.50,444.13 723.37,446.11 C721.24,448.10 716.51,452.83 712.86,456.61 C706.74,462.96 683.75,485.05 669.50,498.26 C666.20,501.32 660.58,506.55 657.00,509.89 C653.42,513.23 650.20,515.97 649.83,515.98 C649.46,515.99 647.88,517.07 646.33,518.38 L 643.50 520.76 L 643.50 318.50 ZM 318.50 710.49 C321.80,709.72 329.67,707.45 336.00,705.46 C342.33,703.46 350.20,701.05 353.50,700.10 C356.80,699.14 360.46,697.85 361.63,697.21 C362.80,696.58 365.94,695.32 368.60,694.41 C371.26,693.51 376.15,691.44 379.47,689.81 C382.79,688.18 388.75,685.55 392.71,683.95 C396.68,682.36 403.88,678.90 408.71,676.28 C413.55,673.66 421.55,669.66 426.50,667.40 C436.40,662.87 448.19,655.93 448.73,654.31 C448.93,653.71 446.46,653.07 442.74,652.77 C442.25,652.73 441.77,652.68 441.29,652.62 C443.73,652.85 446.67,652.99 449.37,652.99 C457.15,653.00 458.09,653.19 457.00,654.50 C456.32,655.33 455.25,656.00 454.63,656.00 C454.01,656.00 449.72,658.25 445.10,661.00 C440.47,663.75 436.42,666.00 436.10,666.01 C435.77,666.01 434.15,666.72 432.50,667.58 C412.88,677.87 396.05,685.98 385.00,690.48 C381.42,691.94 376.70,693.94 374.50,694.94 C372.30,695.93 365.77,698.40 360.00,700.42 C354.23,702.44 348.47,704.52 347.21,705.04 C344.40,706.21 329.70,710.39 317.12,713.60 C306.12,716.42 305.90,716.44 306.61,714.57 C306.88,713.87 305.48,710.61 303.40,706.97 C306.65,712.16 308.17,713.19 310.40,712.52 C311.55,712.17 315.20,711.26 318.50,710.49 ZM 284.60 655.99 C284.35,654.52 284.12,653.03 283.89,651.50 C282.48,642.17 280.91,588.39 280.35,530.25 C280.07,501.54 279.52,483.00 278.95,483.00 C277.33,483.00 277.91,480.39 279.94,478.56 C281.18,477.44 281.63,476.05 281.89,456.65 C281.93,471.24 282.07,480.77 282.32,481.32 C282.77,482.31 282.88,515.84 282.56,555.82 C282.03,621.19 282.32,640.61 284.60,655.99 ZM 758.58 257.58 C754.65,250.40 750.18,244.08 745.14,238.56 C725.49,217.04 720.60,212.62 708.69,205.62 C706.94,204.59 703.03,202.28 700.00,200.49 C696.97,198.70 692.03,196.20 689.00,194.95 C685.97,193.69 682.80,192.08 681.96,191.36 C680.19,189.88 668.68,186.73 658.97,184.89 C662.69,185.57 666.66,186.40 669.44,187.12 C676.94,189.03 681.06,189.14 685.18,187.51 C686.44,187.01 686.75,187.29 686.44,188.67 C686.10,190.13 688.06,191.50 696.26,195.49 C715.66,204.95 722.25,210.17 745.24,234.29 C747.30,236.45 748.73,238.63 748.41,239.14 C748.10,239.65 750.07,243.53 752.79,247.78 C754.88,251.04 756.80,254.29 758.58,257.58 ZM 280.81 873.85 C275.48,874.48 270.19,875.00 269.06,875.00 C266.03,875.00 266.55,873.31 269.75,872.73 C271.26,872.46 276.46,871.50 281.31,870.60 C286.44,869.65 290.75,869.32 291.62,869.81 C294.39,871.36 290.93,872.65 280.81,873.85 ZM 798.26 106.91 C789.36,109.48 788.09,109.51 788.27,107.12 C788.41,105.18 789.34,104.99 806.00,103.53 C810.21,103.16 810.80,103.26 808.50,103.93 C806.85,104.42 802.24,105.76 798.26,106.91 ZM 782.43 111.02 C778.94,112.35 775.85,112.20 773.73,110.59 C772.06,109.33 772.09,109.21 774.23,108.64 C777.89,107.65 785.00,107.91 785.00,109.02 C785.00,109.59 783.85,110.49 782.43,111.02 ZM 778.14 377.74 L 777.74 349.72 C777.53,335.58 777.31,327.14 776.83,320.77 C777.69,328.88 777.97,337.95 778.05,355.62 ZM 781.40 599.34 C781.72,590.47 781.87,575.02 781.86,559.90 C781.97,579.09 781.91,590.69 781.40,599.34 ZM 432.42 650.07 C427.85,647.83 424.21,644.31 421.71,639.62 L 419.50 635.50 L 422.00 639.95 C424.15,643.79 428.28,647.69 432.42,650.07 ZM 697.02 725.24 C710.23,714.79 722.38,704.75 731.50,696.72 C725.40,702.21 718.52,708.06 711.00,714.13 C706.63,717.66 701.91,721.41 697.02,725.24 ZM 421.63 785.89 C423.03,786.06 424.49,786.23 426.00,786.40 C431.73,787.02 453.28,787.48 479.02,787.73 C453.57,787.51 432.39,787.10 427.00,786.53 C425.29,786.35 423.48,786.13 421.63,785.89 ZM 749.23 679.46 C754.57,673.54 758.57,668.13 762.28,661.81 C760.95,664.10 759.56,666.33 758.13,668.50 C756.07,671.62 753.06,675.31 749.23,679.46 ZM 299.40 700.11 C295.31,692.73 292.17,685.52 289.67,677.63 C292.19,685.32 295.47,692.91 299.40,700.11 ZM 771.50 291.24 C768.94,281.46 765.84,272.64 762.18,264.73 C766.02,272.94 769.06,281.57 771.50,291.24 ZM 774.58 634.52 C777.00,627.13 778.85,619.38 780.25,610.83 C779.68,614.64 778.93,618.27 777.96,622.51 C777.05,626.48 775.92,630.50 774.58,634.52 ZM 282.56 350.91 L 282.59 344.50 L 285.27 334.50 C286.44,330.10 288.21,325.21 290.40,320.20 C294.13,311.65 299.07,302.76 304.32,295.38 C299.07,302.75 294.13,311.64 290.40,320.20 C288.22,325.20 286.45,330.10 285.27,334.50 L 282.61 344.50 Z\" fill=\"rgb(146,104,68)\"/>\n<path d=\"M 781.70 537.56 L 781.50 509.70 L 769.00 521.92 C762.12,528.64 754.03,536.24 751.00,538.81 C747.97,541.38 740.84,547.86 735.14,553.21 C729.44,558.55 722.24,564.85 719.14,567.20 C716.04,569.55 711.70,573.23 709.50,575.38 C701.04,583.66 646.64,627.62 629.81,639.78 C625.79,642.69 618.62,647.86 613.88,651.28 C609.14,654.70 601.94,659.90 597.88,662.83 C587.09,670.61 552.59,693.37 537.00,702.99 C529.58,707.57 518.78,714.32 513.00,717.99 C507.23,721.66 493.27,729.74 482.00,735.95 C470.73,742.16 458.06,749.20 453.85,751.59 C449.65,753.98 436.82,760.48 425.35,766.03 L 422.41 767.46 C400.05,778.29 396.48,780.02 396.67,780.69 C396.71,780.85 396.96,780.94 397.22,781.09 C398.14,781.59 401.00,782.32 404.83,783.10 C400.40,782.42 396.22,782.06 394.54,782.25 C392.32,782.50 384.65,785.22 377.50,788.29 C324.99,810.83 278.31,824.81 236.50,830.51 C222.36,832.44 186.45,833.29 176.60,831.93 C146.13,827.72 122.52,813.00 112.24,791.81 C107.92,782.91 104.51,770.53 103.48,760.00 C102.78,752.89 104.89,734.09 107.55,723.75 C123.45,661.92 177.67,581.88 260.13,498.50 L 280.48 477.92 C280.32,478.19 280.14,478.38 279.94,478.56 C277.91,480.39 277.33,483.00 278.95,483.00 C279.52,483.00 280.07,501.54 280.35,530.25 C280.51,547.00 280.76,563.38 281.06,578.45 C280.72,575.03 280.29,573.32 279.80,573.62 C278.84,574.22 266.21,590.12 262.02,596.02 C245.24,619.64 235.17,638.56 228.41,659.15 C226.13,666.09 225.66,669.36 225.59,678.50 C225.51,688.55 225.74,690.02 228.30,695.50 C233.60,706.86 243.51,714.27 257.60,717.42 C269.81,720.16 304.59,718.03 306.64,714.48 C306.63,714.51 306.62,714.54 306.61,714.57 C305.90,716.44 306.12,716.42 317.12,713.60 C329.70,710.39 344.40,706.21 347.21,705.04 C348.47,704.52 354.23,702.44 360.00,700.42 C365.77,698.40 372.30,695.93 374.50,694.94 C376.70,693.94 381.42,691.94 385.00,690.48 C396.05,685.98 412.88,677.87 432.50,667.58 C434.15,666.72 435.77,666.01 436.10,666.01 C436.42,666.00 440.47,663.75 445.10,661.00 C449.72,658.25 454.01,656.00 454.63,656.00 C455.25,656.00 456.32,655.33 457.00,654.50 C458.09,653.19 457.15,653.00 449.37,652.99 C446.67,652.99 443.73,652.85 441.29,652.62 C440.58,652.54 439.89,652.43 439.20,652.29 C441.95,652.69 445.60,652.94 449.18,652.96 L 458.86 653.00 L 474.18 643.88 C499.68,628.70 540.63,601.07 574.06,576.50 C589.46,565.18 608.69,549.97 635.17,528.16 L 642.84 521.84 L 643.48 514.67 C643.49,514.61 643.49,514.54 643.50,514.46 L 643.50 520.76 L 646.33 518.38 C647.88,517.07 649.46,515.99 649.83,515.98 C650.20,515.97 653.42,513.23 657.00,509.89 C660.58,506.55 666.20,501.32 669.50,498.26 C683.75,485.05 706.74,462.96 712.86,456.61 C716.51,452.83 721.24,448.10 723.37,446.11 C725.50,444.13 732.87,436.20 739.76,428.50 C746.65,420.80 753.27,413.60 754.47,412.50 C756.87,410.31 770.92,393.70 775.38,387.79 L 778.17 384.09 L 778.15 379.97 L 778.18 379.95 L 778.14 377.74 L 778.11 370.82 C778.28,378.15 778.64,380.00 779.41,380.00 C782.68,380.00 801.47,353.32 812.87,332.48 C835.22,291.62 837.69,266.88 821.15,249.67 C812.95,241.14 802.62,237.34 785.00,236.36 C774.77,235.79 750.50,238.10 748.81,239.79 C748.42,240.18 750.14,243.65 752.64,247.50 C757.04,254.29 760.75,261.20 763.90,268.56 C763.34,267.27 762.77,266.00 762.18,264.73 C761.03,262.26 759.83,259.88 758.58,257.58 L 758.58 257.58 C756.80,254.29 754.87,251.04 752.79,247.78 C750.07,243.53 748.10,239.65 748.41,239.14 C748.73,238.63 747.30,236.45 745.24,234.29 C722.25,210.17 715.66,204.95 696.26,195.49 C688.06,191.50 686.10,190.13 686.44,188.67 C686.75,187.29 686.44,187.01 685.18,187.51 C684.02,187.96 682.87,188.28 681.64,188.47 C682.98,188.11 684.41,187.50 686.31,186.63 C691.13,184.43 700.14,180.81 724.00,171.48 C736.74,166.49 769.12,155.80 781.50,152.49 C801.94,147.02 810.10,145.06 820.67,143.08 C866.29,134.55 901.31,136.66 927.50,149.50 C956.65,163.80 970.01,190.29 966.83,227.50 C964.79,251.49 956.10,277.76 939.97,308.73 C927.29,333.08 918.62,346.99 895.55,380.00 C890.20,387.66 869.70,414.27 863.58,421.50 C840.94,448.26 822.56,468.41 798.79,492.56 L 782.00 509.63 L 781.98 555.06 C781.98,563.66 781.95,570.95 781.90,577.14 C781.92,572.16 781.90,566.48 781.86,559.90 L 781.86 559.90 C781.85,552.03 781.80,544.26 781.70,537.56 ZM 274.21 871.91 C276.59,871.40 279.42,870.88 282.50,870.38 C284.70,870.03 286.95,869.40 287.50,868.99 C288.05,868.57 290.08,867.90 292.00,867.49 C303.53,865.03 332.38,855.82 349.50,849.14 L 359.50 845.23 L 360.04 837.76 C360.78,827.49 364.43,821.92 373.50,817.20 C389.33,808.95 408.27,820.03 409.74,838.40 C410.40,846.62 408.82,851.21 403.27,857.15 C397.51,863.30 391.96,865.49 384.20,864.68 C377.10,863.94 372.26,861.99 369.08,858.59 C365.99,855.29 363.89,855.34 352.11,858.94 C333.73,864.57 308.96,869.89 286.73,873.01 C292.19,872.04 293.75,871.00 291.62,869.81 C290.75,869.32 286.44,869.65 281.31,870.60 C278.86,871.05 276.32,871.52 274.21,871.91 ZM 701.98 163.75 C697.33,166.56 695.69,166.99 690.04,166.96 C679.39,166.90 672.36,162.46 667.33,152.63 C665.21,148.47 664.91,146.79 665.32,141.13 C666.03,131.16 666.55,129.36 669.93,125.08 C675.28,118.34 686.36,114.67 694.67,116.91 C698.28,117.88 700.08,118.94 707.62,124.49 C709.56,125.93 710.29,125.86 716.62,123.64 C733.17,117.83 753.19,112.39 767.00,109.94 C770.58,109.30 774.18,108.45 775.01,108.03 C775.85,107.62 780.57,106.74 785.51,106.08 C786.77,105.92 788.12,105.73 789.49,105.53 C788.48,105.93 788.32,106.40 788.27,107.12 C788.16,108.49 788.54,109.07 790.58,108.82 L 790.00 108.97 C787.42,109.65 784.99,110.30 782.67,110.93 C783.97,110.39 785.00,109.56 785.00,109.02 C785.00,107.91 777.89,107.65 774.23,108.64 C772.09,109.21 772.06,109.33 773.73,110.59 C775.22,111.72 777.20,112.14 779.46,111.81 C763.55,116.24 752.65,120.02 734.88,127.04 C720.30,132.80 716.03,134.23 714.64,137.08 C713.93,138.53 713.98,140.36 713.79,143.34 C713.17,153.07 709.67,159.11 701.98,163.75 ZM 131.60 596.60 C127.85,600.35 124.66,600.86 119.38,598.56 C115.11,596.70 113.00,593.34 113.00,588.38 C113.00,584.83 113.54,583.73 116.66,580.99 C119.99,578.07 120.78,577.82 125.35,578.26 C131.82,578.88 134.99,582.36 135.00,588.85 C135.00,592.47 134.43,593.77 131.60,596.60 ZM 427.75 786.60 C434.16,787.14 454.63,787.52 479.02,787.73 C497.78,787.92 518.78,787.99 537.47,787.94 C494.78,788.14 440.51,787.81 431.05,786.94 C430.03,786.85 428.92,786.73 427.75,786.60 ZM 803.21 105.47 C805.51,104.81 807.52,104.22 808.50,103.93 C810.80,103.26 810.21,103.16 806.00,103.53 C802.83,103.81 800.24,104.04 798.10,104.25 C798.79,104.13 799.41,104.03 799.92,103.94 C802.90,103.42 806.72,103.05 808.42,103.10 L 811.50 103.20 L 808.50 104.05 C807.79,104.25 805.86,104.77 803.21,105.47 ZM 774.58 634.52 L 774.58 634.52 C774.87,633.64 775.15,632.77 775.43,631.90 C772.09,643.14 767.72,653.05 762.01,662.27 C762.10,662.11 762.19,661.96 762.28,661.81 C763.79,659.22 765.26,656.49 766.75,653.50 C769.87,647.26 772.45,641.03 774.58,634.52 ZM 282.08 617.93 C282.36,626.68 282.67,634.14 282.98,639.89 C282.49,633.20 282.20,625.93 282.08,617.93 ZM 776.77 320.01 C777.55,326.73 777.87,333.52 777.96,345.02 C777.82,333.96 777.50,327.07 776.83,320.77 C776.81,320.51 776.79,320.26 776.77,320.01 ZM 288.38 673.48 C288.79,674.87 289.22,676.25 289.67,677.63 C292.17,685.52 295.31,692.73 299.40,700.11 C294.63,691.51 291.03,682.98 288.38,673.48 ZM 423.56 642.32 C422.97,641.53 422.44,640.74 422.00,639.95 L 419.50 635.50 L 422.00 639.91 C422.47,640.73 422.99,641.54 423.56,642.32 ZM 780.88 606.72 C780.78,607.57 780.68,608.32 780.57,609.00 C780.44,609.79 780.31,610.56 780.17,611.34 C780.20,611.17 780.22,611.00 780.25,610.83 C780.47,609.48 780.68,608.11 780.88,606.72 ZM 670.37 187.35 C670.07,187.27 669.76,187.20 669.44,187.12 C666.66,186.40 662.69,185.57 658.97,184.89 C658.76,184.85 658.55,184.81 658.35,184.77 C662.17,185.45 666.30,186.31 669.11,187.03 C669.55,187.14 669.97,187.25 670.37,187.35 ZM 781.40 599.34 C781.44,598.68 781.47,598.01 781.51,597.32 C781.45,598.70 781.39,599.96 781.33,601.11 C781.35,600.55 781.37,599.96 781.40,599.34 ZM 267.53 874.77 C267.15,874.65 267.00,874.46 267.00,874.21 C267.00,874.16 267.03,874.11 267.08,874.05 C266.98,874.33 267.12,874.59 267.53,874.77 ZM 275.15 874.48 C273.78,874.63 272.63,874.75 271.66,874.82 C272.64,874.73 273.84,874.62 275.15,874.48 Z\" fill=\"rgb(215,111,43)\"/>\n</g>\n</svg>`;\n","/**\n * Cell Buttons Plugin\n * Injects E, F, ? action buttons into notebook cells\n * Communicates with sidebar panel instead of Chrome extension\n */\nimport { INotebookTracker } from '@jupyterlab/notebook';\nimport { CellAction } from '../types';\n/**\n * Cell Buttons Plugin\n */\nexport const cellButtonsPlugin = {\n id: '@hdsp-agent/cell-buttons',\n autoStart: true,\n requires: [INotebookTracker],\n activate: (app, notebookTracker) => {\n console.log('[CellButtonsPlugin] Activated');\n // Store app reference globally for later use\n window.jupyterapp = app;\n // Handle new notebooks\n notebookTracker.widgetAdded.connect((sender, panel) => {\n console.log('[CellButtonsPlugin] Notebook widget added:', panel.id);\n // Wait for session to be ready\n panel.sessionContext.ready.then(() => {\n observeNotebook(panel);\n }).catch(err => {\n console.error('[CellButtonsPlugin] Error waiting for session:', err);\n });\n });\n // Handle current notebook if exists\n if (notebookTracker.currentWidget) {\n observeNotebook(notebookTracker.currentWidget);\n }\n // Handle notebook focus changes\n notebookTracker.currentChanged.connect((sender, panel) => {\n if (panel) {\n observeNotebook(panel);\n }\n });\n }\n};\n/**\n * Observe a notebook for cell changes and inject buttons\n */\nfunction observeNotebook(panel) {\n const notebook = panel.content;\n // Initial injection with delay to ensure cells are rendered\n setTimeout(() => {\n injectButtonsIntoAllCells(notebook, panel);\n // Retry after longer delay for cells that weren't ready\n setTimeout(() => {\n injectButtonsIntoAllCells(notebook, panel);\n }, 500);\n }, 200);\n // Watch for cell changes (add/remove/reorder)\n const cellsChangedHandler = () => {\n setTimeout(() => {\n injectButtonsIntoAllCells(notebook, panel);\n }, 100);\n };\n // Remove any previous listeners to avoid duplicates\n try {\n notebook.model?.cells.changed.disconnect(cellsChangedHandler);\n }\n catch {\n // Ignore if not connected\n }\n // Connect new listener\n notebook.model?.cells.changed.connect(cellsChangedHandler);\n}\n/**\n * Inject buttons into all cells in a notebook\n */\nfunction injectButtonsIntoAllCells(notebook, panel) {\n for (let i = 0; i < notebook.widgets.length; i++) {\n const cell = notebook.widgets[i];\n injectButtonsIntoCell(cell, panel);\n }\n}\n/**\n * Inject buttons into a single cell\n * Buttons are placed outside the cell (above), aligned with code start position\n */\nfunction injectButtonsIntoCell(cell, panel) {\n // Safety checks\n if (!cell || !cell.model) {\n return;\n }\n // Only inject buttons into code cells (not markdown, raw, etc.)\n if (cell.model.type !== 'code') {\n return;\n }\n const cellNode = cell.node;\n if (!cellNode || !cellNode.classList.contains('jp-CodeCell')) {\n return;\n }\n // Check if buttons already exist (using our unique class name)\n if (cellNode.querySelector('.jp-hdsp-cell-buttons')) {\n return;\n }\n // Find the prompt area to get the correct left offset\n const promptNode = cellNode.querySelector('.jp-InputPrompt, .jp-OutputPrompt');\n const promptWidth = promptNode ? promptNode.getBoundingClientRect().width : 64;\n // Find the input wrapper to insert before\n const inputWrapper = cellNode.querySelector('.jp-Cell-inputWrapper');\n if (!inputWrapper) {\n return;\n }\n // Create button container with unique class name to avoid conflicts\n const buttonContainer = document.createElement('div');\n buttonContainer.className = 'jp-hdsp-cell-buttons';\n buttonContainer.style.cssText = `\n display: flex;\n gap: 4px;\n padding: 4px 8px;\n padding-left: ${promptWidth}px;\n background: transparent;\n `;\n // Create E button (Explain)\n const explainBtn = createButton('E', '설명 요청', () => {\n handleCellAction(CellAction.EXPLAIN, cell);\n });\n // Create F button (Fix)\n const fixBtn = createButton('F', '수정 제안 요청', () => {\n handleCellAction(CellAction.FIX, cell);\n });\n // Create ? button (Custom Prompt)\n const customBtn = createButton('?', '질문하기', () => {\n handleCellAction(CellAction.CUSTOM_PROMPT, cell);\n });\n buttonContainer.appendChild(explainBtn);\n buttonContainer.appendChild(fixBtn);\n buttonContainer.appendChild(customBtn);\n // Insert before the input wrapper (outside the cell, above it)\n inputWrapper.parentNode?.insertBefore(buttonContainer, inputWrapper);\n}\n/**\n * Create a button element\n */\nfunction createButton(label, title, onClick) {\n const btn = document.createElement('button');\n btn.className = 'jp-agent-button';\n btn.textContent = label;\n btn.title = title;\n btn.setAttribute('aria-label', title);\n btn.onclick = onClick;\n // Add inline styles as fallback\n btn.style.cssText = `\n width: 24px !important;\n height: 24px !important;\n border: 1px solid #ddd !important;\n border-radius: 4px !important;\n background: white !important;\n cursor: pointer !important;\n display: inline-flex !important;\n align-items: center !important;\n justify-content: center !important;\n font-size: 12px !important;\n font-weight: bold !important;\n color: #333 !important;\n padding: 0 !important;\n margin: 0 2px !important;\n opacity: 1 !important;\n visibility: visible !important;\n `;\n return btn;\n}\n/**\n * Handle cell action button click\n */\nasync function handleCellAction(action, cell) {\n const cellContent = cell?.model?.sharedModel?.getSource() || '';\n if (!cellContent.trim()) {\n showNotification('셀 내용이 비어있습니다.', 'warning');\n return;\n }\n const cellIndex = getCellIndex(cell);\n if (action === CellAction.CUSTOM_PROMPT) {\n // For custom prompt, show dialog (no confirmation needed)\n showCustomPromptDialog(cell);\n }\n else {\n // Get cell output\n const cellOutput = getCellOutput(cell);\n // Determine confirmation dialog content based on action\n let title = '';\n let message = '';\n switch (action) {\n case CellAction.EXPLAIN:\n title = `셀 ${cellIndex}번째: 설명 요청`;\n message = '이 셀의 코드 설명을 보시겠습니까?';\n break;\n case CellAction.FIX:\n title = `셀 ${cellIndex}번째: 수정 제안 요청`;\n message = '이 셀의 코드 개선 제안을 받으시겠습니까?';\n break;\n }\n // Show confirmation dialog first\n const confirmed = await showConfirmDialog(title, message);\n if (confirmed) {\n // User confirmed, send to sidebar panel\n sendToSidebarPanel(action, cell, cellContent, cellIndex, cellOutput);\n }\n }\n}\n/**\n * Get the index of a cell in the notebook\n */\nfunction getCellIndex(cell) {\n const app = window.jupyterapp;\n const notebookTracker = app?.shell?.currentWidget?.content;\n if (!notebookTracker) {\n return -1;\n }\n const widgets = notebookTracker.widgets;\n for (let i = 0; i < widgets.length; i++) {\n if (widgets[i] === cell) {\n return i + 1; // Return 1-based index\n }\n }\n return -1;\n}\n/**\n * Extract output from a code cell\n */\nfunction getCellOutput(cell) {\n // cell.model이 null일 수 있으므로 안전하게 체크\n if (!cell?.model || cell.model.type !== 'code') {\n return '';\n }\n const codeCell = cell;\n const outputs = codeCell.model?.outputs;\n if (!outputs || outputs.length === 0) {\n return '';\n }\n const outputTexts = [];\n for (let i = 0; i < outputs.length; i++) {\n const output = outputs.get(i);\n const outputType = output.type;\n if (outputType === 'stream') {\n // stdout, stderr\n const text = output.text;\n if (Array.isArray(text)) {\n outputTexts.push(text.join(''));\n }\n else {\n outputTexts.push(text);\n }\n }\n else if (outputType === 'execute_result' || outputType === 'display_data') {\n // Execution results or display data\n const data = output.data;\n if (data['text/plain']) {\n const text = data['text/plain'];\n if (Array.isArray(text)) {\n outputTexts.push(text.join(''));\n }\n else {\n outputTexts.push(text);\n }\n }\n }\n else if (outputType === 'error') {\n // Error output\n const traceback = output.traceback;\n if (Array.isArray(traceback)) {\n outputTexts.push(traceback.join('\\n'));\n }\n }\n }\n return outputTexts.join('\\n');\n}\n/**\n * Get or assign cell ID to a cell\n */\nfunction getOrAssignCellId(cell) {\n const cellModel = cell.model;\n let cellId;\n // Try to get cell ID from metadata\n try {\n if (cellModel.metadata && typeof cellModel.metadata.get === 'function') {\n cellId = cellModel.metadata.get('jupyterAgentCellId');\n }\n else if (cellModel.metadata?.jupyterAgentCellId) {\n cellId = cellModel.metadata.jupyterAgentCellId;\n }\n }\n catch (e) {\n // Ignore errors\n }\n if (!cellId) {\n cellId = `cell-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n // Try to set cell ID in metadata\n try {\n if (cellModel.metadata && typeof cellModel.metadata.set === 'function') {\n cellModel.metadata.set('jupyterAgentCellId', cellId);\n }\n else if (cellModel.metadata) {\n cellModel.metadata.jupyterAgentCellId = cellId;\n }\n }\n catch (e) {\n // Ignore errors\n }\n }\n return cellId;\n}\n/**\n * Send cell action to sidebar panel\n */\nfunction sendToSidebarPanel(action, cell, cellContent, cellIndex, cellOutput) {\n const agentPanel = window._hdspAgentPanel;\n if (!agentPanel) {\n console.error('[CellButtonsPlugin] Agent panel not found. Make sure sidebar plugin is loaded.');\n return;\n }\n // Get or assign cell ID\n const cellId = getOrAssignCellId(cell);\n // Activate the sidebar panel\n const app = window.jupyterapp;\n if (app) {\n app.shell.activateById(agentPanel.id);\n }\n // Create user-facing display prompt\n let displayPrompt = '';\n // Create actual LLM prompt (based on chrome_agent)\n // Note: Use original content, not escaped. JSON.stringify will handle escaping.\n let llmPrompt = '';\n switch (action) {\n case CellAction.EXPLAIN:\n // User sees: \"Cell x: 설명 요청\" (chrome_agent와 동일)\n displayPrompt = `${cellIndex}번째 셀: 설명 요청`;\n // LLM receives: chrome_agent와 동일한 프롬프트\n llmPrompt = `다음 Jupyter 셀의 내용을 자세히 설명해주세요:\n\n\\`\\`\\`python\n${cellContent}\n\\`\\`\\``;\n break;\n case CellAction.FIX:\n // Check if there's an error in the output\n const hasError = cellOutput && (cellOutput.includes('Error') ||\n cellOutput.includes('Traceback') ||\n cellOutput.includes('Exception') ||\n cellOutput.includes('에러') ||\n cellOutput.includes('오류'));\n if (hasError && cellOutput) {\n // 에러가 있는 경우 (chrome_agent와 동일)\n displayPrompt = `${cellIndex}번째 셀: 에러 수정 요청`;\n llmPrompt = `다음 Jupyter 셀 코드에 에러가 발생했습니다.\n\n원본 코드:\n\\`\\`\\`python\n${cellContent}\n\\`\\`\\`\n\n에러:\n\\`\\`\\`\n${cellOutput}\n\\`\\`\\`\n\n다음 형식으로 응답해주세요:\n\n## 에러 원인\n(에러가 발생한 원인을 간단히 설명)\n\n## 수정 방법\n\n### 방법 1: (수정 방법 제목)\n(이 방법에 대한 간단한 설명)\n\\`\\`\\`python\n(수정된 코드)\n\\`\\`\\`\n\n### 방법 2: (수정 방법 제목)\n(이 방법에 대한 간단한 설명)\n\\`\\`\\`python\n(수정된 코드)\n\\`\\`\\`\n\n### 방법 3: (수정 방법 제목) (있는 경우)\n(이 방법에 대한 간단한 설명)\n\\`\\`\\`python\n(수정된 코드)\n\\`\\`\\`\n\n최소 2개, 최대 3개의 다양한 수정 방법을 제안해주세요.`;\n }\n else {\n // 에러가 없는 경우 - 코드 리뷰/개선 제안 (chrome_agent와 동일)\n displayPrompt = `${cellIndex}번째 셀: 개선 제안 요청`;\n llmPrompt = `다음 Jupyter 셀 코드를 리뷰하고 개선 방법을 제안해주세요.\n\n코드:\n\\`\\`\\`python\n${cellContent}\n\\`\\`\\`\n\n다음 형식으로 응답해주세요:\n\n## 코드 분석\n(현재 코드의 기능과 특징을 간단히 설명)\n\n## 개선 방법\n\n### 방법 1: (개선 방법 제목)\n(이 방법에 대한 간단한 설명)\n\\`\\`\\`python\n(개선된 코드)\n\\`\\`\\`\n\n### 방법 2: (개선 방법 제목)\n(이 방법에 대한 간단한 설명)\n\\`\\`\\`python\n(개선된 코드)\n\\`\\`\\`\n\n### 방법 3: (개선 방법 제목) (있는 경우)\n(이 방법에 대한 간단한 설명)\n\\`\\`\\`python\n(개선된 코드)\n\\`\\`\\`\n\n최소 2개, 최대 3개의 다양한 개선 방법을 제안해주세요.`;\n }\n break;\n }\n // Send both prompts to panel with cell ID and cell index\n if (agentPanel.addCellActionMessage) {\n // cellIndex is 1-based (for display), convert to 0-based for array access\n const cellIndexZeroBased = cellIndex - 1;\n agentPanel.addCellActionMessage(action, cellContent, displayPrompt, llmPrompt, cellId, cellIndexZeroBased);\n }\n}\n/**\n * Helper: Remove existing dialog if present\n */\nfunction removeExistingDialog(className) {\n const existingDialog = document.querySelector(`.${className}`);\n if (existingDialog) {\n existingDialog.remove();\n }\n}\n/**\n * Helper: Create dialog overlay element\n */\nfunction createDialogOverlay(className) {\n const dialogOverlay = document.createElement('div');\n dialogOverlay.className = className;\n dialogOverlay.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.3);\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n `;\n return dialogOverlay;\n}\n/**\n * Helper: Create dialog container element\n */\nfunction createDialogContainer(maxWidth = '400px') {\n const dialogContainer = document.createElement('div');\n dialogContainer.style.cssText = `\n background: #fafafa;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n padding: 24px;\n max-width: ${maxWidth};\n width: 90%;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n `;\n return dialogContainer;\n}\n/**\n * Show confirmation dialog\n * Based on chrome_agent's showConfirmDialog implementation\n */\nfunction showConfirmDialog(title, message) {\n return new Promise((resolve) => {\n removeExistingDialog('jp-agent-confirm-dialog');\n const dialogOverlay = createDialogOverlay('jp-agent-confirm-dialog');\n const dialogContainer = createDialogContainer();\n // Dialog content\n dialogContainer.innerHTML = `\n <div style=\"margin-bottom: 20px;\">\n <h3 style=\"margin: 0 0 12px 0; color: #424242; font-size: 16px; font-weight: 500;\">\n ${escapeHtml(title)}\n </h3>\n <p style=\"margin: 0; color: #616161; font-size: 14px; line-height: 1.5;\">\n ${escapeHtml(message)}\n </p>\n </div>\n <div style=\"display: flex; gap: 12px; justify-content: flex-end;\">\n <button class=\"jp-agent-confirm-cancel-btn\" style=\"\n background: transparent;\n color: #616161;\n border: 1px solid #d1d5db;\n border-radius: 3px;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n \">취소</button>\n <button class=\"jp-agent-confirm-submit-btn\" style=\"\n background: #1976d2;\n color: white;\n border: 1px solid #1976d2;\n border-radius: 3px;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n \">확인</button>\n </div>\n `;\n dialogOverlay.appendChild(dialogContainer);\n document.body.appendChild(dialogOverlay);\n // Button event listeners\n const cancelBtn = dialogContainer.querySelector('.jp-agent-confirm-cancel-btn');\n const submitBtn = dialogContainer.querySelector('.jp-agent-confirm-submit-btn');\n // Cancel button\n cancelBtn.addEventListener('click', () => {\n dialogOverlay.remove();\n resolve(false);\n });\n cancelBtn.addEventListener('mouseenter', () => {\n cancelBtn.style.background = '#f5f5f5';\n });\n cancelBtn.addEventListener('mouseleave', () => {\n cancelBtn.style.background = 'transparent';\n });\n // Submit button\n submitBtn.addEventListener('click', () => {\n submitBtn.disabled = true;\n submitBtn.textContent = '처리 중...';\n // Small delay to show processing state\n setTimeout(() => {\n dialogOverlay.remove();\n resolve(true);\n }, 100);\n });\n submitBtn.addEventListener('mouseenter', () => {\n if (!submitBtn.disabled) {\n submitBtn.style.background = '#1565c0';\n }\n });\n submitBtn.addEventListener('mouseleave', () => {\n if (!submitBtn.disabled) {\n submitBtn.style.background = '#1976d2';\n }\n });\n // ESC key to close\n const handleEsc = (e) => {\n if (e.key === 'Escape') {\n dialogOverlay.remove();\n document.removeEventListener('keydown', handleEsc);\n resolve(false);\n }\n };\n document.addEventListener('keydown', handleEsc);\n // Close on overlay click\n dialogOverlay.addEventListener('click', (e) => {\n if (e.target === dialogOverlay) {\n dialogOverlay.remove();\n document.removeEventListener('keydown', handleEsc);\n resolve(false);\n }\n });\n });\n}\n/**\n * Escape HTML to prevent XSS\n */\nfunction escapeHtml(text) {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n}\n/**\n * Escape content for markdown code blocks\n * Based on chrome_agent's escapeContent function\n */\nfunction escapeContent(content) {\n if (!content)\n return '';\n // 백슬래시를 먼저 escape (다른 escape 처리 전에 수행)\n let escaped = content.replace(/\\\\/g, '\\\\\\\\');\n // 백틱 escape (마크다운 코드 블록 안에서 문제 방지)\n escaped = escaped.replace(/`/g, '\\\\`');\n return escaped;\n}\n/**\n * Get notification background color\n */\nfunction getNotificationColor(type) {\n switch (type) {\n case 'error': return '#f56565';\n case 'warning': return '#ed8936';\n default: return '#4299e1';\n }\n}\n/**\n * Create notification element with styles\n */\nfunction createNotificationElement(message, backgroundColor) {\n const notification = document.createElement('div');\n notification.style.cssText = `\n position: fixed;\n top: 20px;\n right: 20px;\n padding: 12px 16px;\n border-radius: 6px;\n color: white;\n font-size: 14px;\n font-weight: 500;\n z-index: 10001;\n opacity: 0;\n transition: opacity 0.3s ease;\n max-width: 300px;\n word-wrap: break-word;\n background: ${backgroundColor};\n `;\n notification.textContent = message;\n return notification;\n}\n/**\n * Animate notification appearance and removal\n */\nfunction animateNotification(notification) {\n // Show animation\n setTimeout(() => notification.style.opacity = '1', 10);\n // Remove after 3 seconds\n setTimeout(() => {\n notification.style.opacity = '0';\n setTimeout(() => {\n if (notification.parentNode) {\n notification.parentNode.removeChild(notification);\n }\n }, 300);\n }, 3000);\n}\n/**\n * Show notification (simple implementation)\n */\nfunction showNotification(message, type = 'info') {\n const notification = createNotificationElement(message, getNotificationColor(type));\n document.body.appendChild(notification);\n animateNotification(notification);\n}\n/**\n * Show custom prompt dialog\n * Based on chrome_agent's showCustomPromptDialog implementation\n */\nfunction showCustomPromptDialog(cell) {\n const cellContent = cell?.model?.sharedModel?.getSource() || '';\n const cellIndex = getCellIndex(cell);\n const cellId = getOrAssignCellId(cell);\n console.log('커스텀 프롬프트 다이얼로그 표시', cellIndex, cellId);\n removeExistingDialog('jp-agent-custom-prompt-dialog');\n const dialogOverlay = createDialogOverlay('jp-agent-custom-prompt-dialog');\n const dialogContainer = createDialogContainer('500px');\n // 다이얼로그 내용\n dialogContainer.innerHTML = `\n <div style=\"margin-bottom: 20px;\">\n <h3 style=\"margin: 0 0 8px 0; color: #424242; font-size: 16px; font-weight: 500;\">\n 셀에 대해 질문하기\n </h3>\n <p style=\"margin: 0; color: #757575; font-size: 13px;\">\n 물리적 위치: 셀 ${cellIndex + 1}번째 (위에서 아래로 카운트)\n </p>\n </div>\n <div style=\"margin-bottom: 20px;\">\n <textarea class=\"jp-agent-custom-prompt-input\"\n placeholder=\"이 셀에 대해 질문할 내용을 입력하세요...\"\n style=\"\n width: 100%;\n min-height: 100px;\n padding: 12px;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n font-size: 14px;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n resize: vertical;\n box-sizing: border-box;\n \"\n ></textarea>\n </div>\n <div style=\"display: flex; gap: 12px; justify-content: flex-end;\">\n <button class=\"jp-agent-custom-prompt-cancel-btn\" style=\"\n background: transparent;\n color: #616161;\n border: 1px solid #d1d5db;\n border-radius: 3px;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n \">취소</button>\n <button class=\"jp-agent-custom-prompt-submit-btn\" style=\"\n background: transparent;\n color: #1976d2;\n border: 1px solid #1976d2;\n border-radius: 3px;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n \">질문</button>\n </div>\n `;\n dialogOverlay.appendChild(dialogContainer);\n document.body.appendChild(dialogOverlay);\n // 입력 필드에 포커스\n const inputField = dialogContainer.querySelector('.jp-agent-custom-prompt-input');\n setTimeout(() => inputField?.focus(), 100);\n // 버튼 이벤트 리스너\n const cancelBtn = dialogContainer.querySelector('.jp-agent-custom-prompt-cancel-btn');\n const submitBtn = dialogContainer.querySelector('.jp-agent-custom-prompt-submit-btn');\n // 취소 버튼\n cancelBtn.addEventListener('click', () => {\n dialogOverlay.remove();\n });\n cancelBtn.addEventListener('mouseenter', () => {\n cancelBtn.style.background = '#f5f5f5';\n cancelBtn.style.borderColor = '#9ca3af';\n });\n cancelBtn.addEventListener('mouseleave', () => {\n cancelBtn.style.background = 'transparent';\n cancelBtn.style.borderColor = '#d1d5db';\n });\n // 제출 버튼\n const handleSubmit = async () => {\n const promptText = inputField?.value.trim() || '';\n if (!promptText) {\n showNotification('질문 내용을 입력해주세요.', 'warning');\n return;\n }\n dialogOverlay.remove();\n // Get cell output\n const cellOutput = getCellOutput(cell);\n // Create display prompt: \"Cell x: Custom request\"\n const displayPrompt = `${cellIndex}번째 셀: ${promptText}`;\n // Create LLM prompt with code and output\n let llmPrompt = `${promptText}\\n\\n셀 내용:\\n\\`\\`\\`\\n${cellContent}\\n\\`\\`\\``;\n if (cellOutput) {\n llmPrompt += `\\n\\n실행 결과:\\n\\`\\`\\`\\n${cellOutput}\\n\\`\\`\\``;\n }\n const agentPanel = window._hdspAgentPanel;\n if (agentPanel) {\n // Activate the sidebar panel\n const app = window.jupyterapp;\n if (app) {\n app.shell.activateById(agentPanel.id);\n }\n // Send both prompts with cell ID and cell index\n if (agentPanel.addCellActionMessage) {\n // cellIndex is 1-based (for display), convert to 0-based for array access\n const cellIndexZeroBased = cellIndex - 1;\n agentPanel.addCellActionMessage(CellAction.CUSTOM_PROMPT, cellContent, displayPrompt, llmPrompt, cellId, cellIndexZeroBased);\n }\n }\n };\n submitBtn.addEventListener('click', handleSubmit);\n submitBtn.addEventListener('mouseenter', () => {\n submitBtn.style.background = 'rgba(25, 118, 210, 0.1)';\n });\n submitBtn.addEventListener('mouseleave', () => {\n submitBtn.style.background = 'transparent';\n });\n // Enter 키로 제출 (Shift+Enter는 줄바꿈)\n inputField?.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n handleSubmit();\n }\n });\n // 오버레이 클릭 시 다이얼로그 닫기\n dialogOverlay.addEventListener('click', (e) => {\n if (e.target === dialogOverlay) {\n dialogOverlay.remove();\n }\n });\n // ESC 키로 다이얼로그 닫기\n const handleEscapeKey = (e) => {\n if (e.key === 'Escape') {\n dialogOverlay.remove();\n document.removeEventListener('keydown', handleEscapeKey);\n }\n };\n document.addEventListener('keydown', handleEscapeKey);\n}\n","/**\n * Idle Monitor Plugin\n *\n * Monitors user activity and triggers shutdown after idle timeout.\n * Activity is tracked by:\n * - Keyboard events\n * - Mouse events\n * - Notebook cell execution (kernel busy status)\n * - Terminal output\n */\nimport { INotebookTracker } from '@jupyterlab/notebook';\nimport { ITerminalTracker } from '@jupyterlab/terminal';\nimport { PageConfig } from '@jupyterlab/coreutils';\n/**\n * Plugin namespace\n */\nconst PLUGIN_ID = '@hdsp-agent/idle-monitor';\nconst CONFIG_STORAGE_KEY = 'hdsp-agent-llm-config';\n/**\n * Default idle timeout (in minutes)\n */\nconst DEFAULT_IDLE_TIMEOUT_MINUTES = 60;\n// Check intervals - must be shorter than warning period to detect it\nconst CHECK_INTERVAL_NORMAL_MS = 10 * 1000; // Check every 10 seconds (normal mode)\nconst CHECK_INTERVAL_WARNING_MS = 1000; // Check every second (warning mode for countdown)\n// Warning period: show countdown before timeout (minimum 30 seconds, or 25% of timeout)\nconst MIN_WARNING_PERIOD_MS = 30 * 1000; // Minimum 30 seconds warning\n/**\n * Idle Monitor Service\n */\nclass IdleMonitorService {\n constructor() {\n this.checkIntervalId = null;\n this.countdownElement = null;\n this.isShuttingDown = false;\n this.notebookTracker = null;\n this.terminalTracker = null;\n this.isInWarningMode = false;\n this.isDisabled = false;\n /**\n * Throttle mousemove events to reduce performance impact\n */\n this.mouseMoveThrottleTime = 0;\n this.lastActivityTime = Date.now();\n // Initialize timeout from localStorage config\n this.idleTimeoutMinutes = this.loadTimeoutFromConfig();\n this.isDisabled = this.idleTimeoutMinutes === 0;\n this.idleTimeoutMs = this.idleTimeoutMinutes * 60 * 1000;\n // Warning period: 25% of timeout or minimum 30 seconds\n const warningPeriod = Math.max(MIN_WARNING_PERIOD_MS, this.idleTimeoutMs * 0.25);\n this.warningStartMs = this.idleTimeoutMs - warningPeriod;\n this.setupActivityListeners();\n this.setupStorageListener();\n this.createCountdownElement();\n if (!this.isDisabled) {\n this.startIdleCheck();\n }\n console.log('[IdleMonitor] Service initialized. Idle timeout:', this.idleTimeoutMinutes, 'minutes, warning at:', Math.round(this.warningStartMs / 1000), 'sec', this.isDisabled ? '(DISABLED)' : '');\n }\n /**\n * Load timeout configuration from localStorage\n */\n loadTimeoutFromConfig() {\n try {\n const stored = localStorage.getItem(CONFIG_STORAGE_KEY);\n if (stored) {\n const config = JSON.parse(stored);\n if (typeof config.idleTimeoutMinutes === 'number') {\n return config.idleTimeoutMinutes;\n }\n }\n }\n catch (e) {\n console.warn('[IdleMonitor] Failed to load config from localStorage:', e);\n }\n return DEFAULT_IDLE_TIMEOUT_MINUTES;\n }\n /**\n * Setup listener for localStorage changes (config updates from settings panel)\n */\n setupStorageListener() {\n window.addEventListener('storage', (e) => {\n if (e.key === CONFIG_STORAGE_KEY) {\n const newTimeout = this.loadTimeoutFromConfig();\n if (newTimeout !== this.idleTimeoutMinutes) {\n this.updateTimeout(newTimeout);\n }\n }\n });\n // Also listen for custom event (same-tab config changes)\n window.addEventListener('hdsp-config-updated', () => {\n const newTimeout = this.loadTimeoutFromConfig();\n if (newTimeout !== this.idleTimeoutMinutes) {\n this.updateTimeout(newTimeout);\n }\n });\n }\n /**\n * Update timeout dynamically\n */\n updateTimeout(minutes) {\n const wasDisabled = this.isDisabled;\n this.idleTimeoutMinutes = minutes;\n this.isDisabled = minutes === 0;\n this.idleTimeoutMs = minutes * 60 * 1000;\n // Warning period: 25% of timeout or minimum 30 seconds\n const warningPeriod = Math.max(MIN_WARNING_PERIOD_MS, this.idleTimeoutMs * 0.25);\n this.warningStartMs = this.idleTimeoutMs - warningPeriod;\n console.log('[IdleMonitor] Timeout updated to:', minutes, 'minutes', this.isDisabled ? '(DISABLED)' : '');\n // Stop idle check if disabled\n if (this.isDisabled) {\n this.stopIdleCheck();\n this.hideCountdown();\n }\n else if (wasDisabled) {\n // Re-enable if was disabled\n this.resetIdleTimer();\n this.startIdleCheck();\n }\n }\n /**\n * Stop idle check interval\n */\n stopIdleCheck() {\n if (this.checkIntervalId !== null) {\n clearInterval(this.checkIntervalId);\n this.checkIntervalId = null;\n }\n this.isInWarningMode = false;\n console.log('[IdleMonitor] Idle check stopped');\n }\n /**\n * Set notebook tracker for monitoring cell execution\n */\n setNotebookTracker(tracker) {\n this.notebookTracker = tracker;\n // Monitor cell execution changes\n tracker.currentChanged.connect(() => {\n this.resetIdleTimer();\n });\n // Monitor active cell changes\n tracker.activeCellChanged.connect(() => {\n this.resetIdleTimer();\n });\n console.log('[IdleMonitor] Notebook tracker connected');\n }\n /**\n * Set terminal tracker for monitoring terminal activity\n */\n setTerminalTracker(tracker) {\n this.terminalTracker = tracker;\n // Monitor terminal changes\n tracker.currentChanged.connect(() => {\n this.resetIdleTimer();\n });\n // Setup monitoring for existing terminals\n tracker.forEach(terminal => {\n this.setupTerminalMonitoring(terminal);\n });\n // Monitor new terminals\n tracker.widgetAdded.connect((_, terminal) => {\n this.setupTerminalMonitoring(terminal);\n });\n console.log('[IdleMonitor] Terminal tracker connected');\n }\n /**\n * Setup monitoring for a single terminal\n * Simply reset idle timer on any terminal output\n */\n setupTerminalMonitoring(terminal) {\n const terminalId = terminal.id || terminal.session?.name || 'unknown';\n const session = terminal.session;\n if (!session)\n return;\n // Listen for terminal output - any output resets idle timer\n session.messageReceived.connect(() => {\n this.resetIdleTimer();\n });\n console.log('[IdleMonitor] Terminal monitoring setup:', terminalId);\n }\n /**\n * Setup global activity listeners\n */\n setupActivityListeners() {\n // Keyboard events\n document.addEventListener('keydown', this.handleActivity.bind(this), true);\n document.addEventListener('keyup', this.handleActivity.bind(this), true);\n document.addEventListener('keypress', this.handleActivity.bind(this), true);\n // Mouse events\n document.addEventListener('mousedown', this.handleActivity.bind(this), true);\n document.addEventListener('mouseup', this.handleActivity.bind(this), true);\n document.addEventListener('mousemove', this.throttledMouseMove.bind(this), true);\n document.addEventListener('wheel', this.handleActivity.bind(this), true);\n document.addEventListener('click', this.handleActivity.bind(this), true);\n // Touch events (for tablet support)\n document.addEventListener('touchstart', this.handleActivity.bind(this), true);\n document.addEventListener('touchend', this.handleActivity.bind(this), true);\n console.log('[IdleMonitor] Activity listeners attached');\n }\n throttledMouseMove() {\n const now = Date.now();\n if (now - this.mouseMoveThrottleTime > 5000) { // Only update every 5 seconds for mouse move\n this.mouseMoveThrottleTime = now;\n this.handleActivity();\n }\n }\n /**\n * Handle any user activity\n */\n handleActivity() {\n this.resetIdleTimer();\n }\n /**\n * Reset the idle timer\n */\n resetIdleTimer() {\n this.lastActivityTime = Date.now();\n this.isShuttingDown = false;\n this.hideCountdown();\n // Switch back to normal mode if in warning mode\n this.switchToNormalMode();\n }\n /**\n * Create countdown display element (fixed position, top-right)\n */\n createCountdownElement() {\n // Create countdown container with fixed positioning\n this.countdownElement = document.createElement('div');\n this.countdownElement.id = 'hdsp-idle-countdown';\n this.countdownElement.className = 'hdsp-idle-countdown';\n this.countdownElement.style.cssText = `\n display: none;\n position: fixed;\n right: 20px;\n top: 10px;\n background: linear-gradient(135deg, #ff6b6b 0%, #ee5a5a 100%);\n color: white;\n padding: 8px 16px;\n border-radius: 20px;\n font-size: 14px;\n font-weight: 600;\n box-shadow: 0 4px 12px rgba(238, 90, 90, 0.5);\n z-index: 99999;\n white-space: nowrap;\n cursor: pointer;\n user-select: none;\n `;\n // Click to dismiss and reset timer\n this.countdownElement.addEventListener('click', () => {\n this.resetIdleTimer();\n console.log('[IdleMonitor] User clicked countdown - timer reset');\n });\n // Add pulse animation\n const style = document.createElement('style');\n style.id = 'hdsp-idle-countdown-style';\n style.textContent = `\n @keyframes hdsp-pulse {\n 0% { transform: scale(1); }\n 50% { transform: scale(1.03); }\n 100% { transform: scale(1); }\n }\n\n .hdsp-idle-countdown {\n animation: hdsp-pulse 2s infinite;\n }\n\n .hdsp-idle-countdown.warning {\n background: linear-gradient(135deg, #ffa726 0%, #fb8c00 100%) !important;\n box-shadow: 0 4px 12px rgba(251, 140, 0, 0.5) !important;\n }\n\n .hdsp-idle-countdown.critical {\n background: linear-gradient(135deg, #ef5350 0%, #d32f2f 100%) !important;\n box-shadow: 0 4px 12px rgba(211, 47, 47, 0.6) !important;\n animation: hdsp-pulse-critical 0.5s infinite !important;\n }\n\n @keyframes hdsp-pulse-critical {\n 0% { transform: scale(1); opacity: 1; }\n 50% { transform: scale(1.05); opacity: 0.9; }\n 100% { transform: scale(1); opacity: 1; }\n }\n `;\n // Remove existing style if any\n const existingStyle = document.getElementById('hdsp-idle-countdown-style');\n if (existingStyle) {\n existingStyle.remove();\n }\n document.head.appendChild(style);\n // Append to body for fixed positioning\n document.body.appendChild(this.countdownElement);\n console.log('[IdleMonitor] Countdown element created (fixed position)');\n }\n /**\n * Show countdown in toolbar\n */\n showCountdown(secondsRemaining) {\n console.log(`[IdleMonitor] showCountdown called: ${secondsRemaining}s remaining`);\n if (!this.countdownElement) {\n this.createCountdownElement();\n return;\n }\n this.countdownElement.style.display = 'block';\n this.countdownElement.textContent = `Idle Shutdown ${secondsRemaining}초 전`;\n // Update styling based on urgency\n this.countdownElement.classList.remove('warning', 'critical');\n if (secondsRemaining <= 10) {\n this.countdownElement.classList.add('critical');\n }\n else if (secondsRemaining <= 30) {\n this.countdownElement.classList.add('warning');\n }\n }\n /**\n * Hide countdown display\n */\n hideCountdown() {\n if (this.countdownElement) {\n this.countdownElement.style.display = 'none';\n }\n }\n /**\n * Check if any notebook cells are currently executing\n * Uses kernel status for reliability\n */\n hasRunningCells() {\n if (!this.notebookTracker)\n return false;\n // Check all open notebooks via kernel status\n const notebooks = this.notebookTracker.filter(() => true);\n for (const notebook of notebooks) {\n const session = notebook.sessionContext?.session;\n if (session) {\n const kernelStatus = session.kernel?.status;\n // Kernel is busy = cells are executing\n if (kernelStatus === 'busy') {\n return true;\n }\n }\n // Fallback: also check DOM for [*] indicator (for edge cases)\n if (notebook.content) {\n const cells = notebook.content.widgets;\n for (const cell of cells) {\n const model = cell.model;\n if (model && model.type === 'code') {\n const promptNode = cell.node.querySelector('.jp-InputPrompt');\n if (promptNode && promptNode.textContent?.includes('*')) {\n return true;\n }\n }\n }\n }\n }\n return false;\n }\n /**\n * Start idle checking interval\n * Starts in normal mode (10 min), switches to warning mode (1 sec) when needed\n */\n startIdleCheck() {\n this.checkIntervalId = window.setInterval(() => {\n this.checkIdleStatus();\n }, CHECK_INTERVAL_NORMAL_MS);\n console.log('[IdleMonitor] Started in normal mode: check every 10 seconds');\n }\n /**\n * Switch to warning mode (faster checks for countdown)\n */\n switchToWarningMode() {\n if (this.isInWarningMode)\n return;\n this.isInWarningMode = true;\n // Clear normal interval\n if (this.checkIntervalId !== null) {\n clearInterval(this.checkIntervalId);\n }\n // Start warning interval (1 second)\n this.checkIntervalId = window.setInterval(() => {\n this.checkIdleStatus();\n }, CHECK_INTERVAL_WARNING_MS);\n console.log('[IdleMonitor] Switched to warning mode: check every', CHECK_INTERVAL_WARNING_MS / 1000, 'seconds');\n }\n /**\n * Switch back to normal mode\n */\n switchToNormalMode() {\n if (!this.isInWarningMode)\n return;\n this.isInWarningMode = false;\n // Clear warning interval\n if (this.checkIntervalId !== null) {\n clearInterval(this.checkIntervalId);\n }\n // Start normal interval (10 minutes)\n this.checkIntervalId = window.setInterval(() => {\n this.checkIdleStatus();\n }, CHECK_INTERVAL_NORMAL_MS);\n console.log('[IdleMonitor] Switched to normal mode: check every 10 seconds');\n }\n /**\n * Check current idle status\n */\n checkIdleStatus() {\n // Skip if disabled\n if (this.isDisabled) {\n return;\n }\n // Skip if notebook cells are running (not idle)\n if (this.hasRunningCells()) {\n this.resetIdleTimer();\n return;\n }\n const now = Date.now();\n const idleTime = now - this.lastActivityTime;\n const remainingTime = this.idleTimeoutMs - idleTime;\n const secondsRemaining = Math.ceil(remainingTime / 1000);\n // Debug log every 10 seconds\n if (Math.floor(idleTime / 1000) % 10 === 0) {\n console.log(`[IdleMonitor] idle=${Math.round(idleTime / 1000)}s, warningAt=${Math.round(this.warningStartMs / 1000)}s, timeout=${Math.round(this.idleTimeoutMs / 1000)}s, remaining=${secondsRemaining}s`);\n }\n // Enter warning period - switch to fast checking for countdown\n if (idleTime >= this.warningStartMs && !this.isShuttingDown) {\n this.switchToWarningMode();\n this.showCountdown(secondsRemaining);\n }\n // Trigger shutdown if idle timeout reached\n if (idleTime >= this.idleTimeoutMs && !this.isShuttingDown) {\n this.isShuttingDown = true;\n this.triggerShutdown();\n }\n }\n /**\n * Trigger shutdown process\n */\n async triggerShutdown() {\n console.log('[IdleMonitor] Idle timeout reached. Triggering shutdown...');\n // Hide countdown\n this.hideCountdown();\n // Show alert popup\n alert(`${this.idleTimeoutMinutes}분 동안 활동이 없어 세션이 종료됩니다.`);\n // Call shutdown API\n await this.callShutdownApi();\n }\n /**\n * Call HDSP shutdown API via Jupyter server endpoint\n * Server-side call to avoid CORS issues\n */\n async callShutdownApi() {\n try {\n // Get base URL from PageConfig\n const baseUrl = PageConfig.getBaseUrl();\n const apiUrl = `${baseUrl}hdsp-agent/idle-shutdown`;\n console.log('[IdleMonitor] Calling shutdown API via server:', apiUrl);\n // Call server endpoint (which will call HDSP API)\n const response = await fetch(apiUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n const result = await response.json();\n if (response.ok && result.success) {\n console.log('[IdleMonitor] Shutdown API call successful:', result.message);\n }\n else {\n console.error('[IdleMonitor] Shutdown API call failed:', result.error || response.statusText);\n }\n }\n catch (error) {\n console.error('[IdleMonitor] Shutdown API call error:', error);\n }\n }\n /**\n * Get current idle time in milliseconds\n */\n getIdleTimeMs() {\n return Date.now() - this.lastActivityTime;\n }\n /**\n * Manually trigger activity (for external use)\n */\n triggerActivity() {\n this.resetIdleTimer();\n }\n /**\n * Cleanup service\n */\n dispose() {\n if (this.checkIntervalId !== null) {\n clearInterval(this.checkIntervalId);\n this.checkIntervalId = null;\n }\n if (this.countdownElement && this.countdownElement.parentNode) {\n this.countdownElement.parentNode.removeChild(this.countdownElement);\n }\n console.log('[IdleMonitor] Service disposed');\n }\n}\n/**\n * Global idle monitor instance\n */\nlet idleMonitor = null;\n/**\n * Get idle monitor instance\n */\nexport function getIdleMonitor() {\n return idleMonitor;\n}\n/**\n * Idle Monitor Plugin\n */\nexport const idleMonitorPlugin = {\n id: PLUGIN_ID,\n autoStart: true,\n requires: [],\n optional: [INotebookTracker, ITerminalTracker],\n activate: (app, notebookTracker, terminalTracker) => {\n console.log('[IdleMonitorPlugin] Activating Idle Monitor');\n try {\n // Create idle monitor service\n idleMonitor = new IdleMonitorService();\n // Connect notebook tracker if available\n if (notebookTracker) {\n idleMonitor.setNotebookTracker(notebookTracker);\n }\n // Connect terminal tracker if available\n if (terminalTracker) {\n idleMonitor.setTerminalTracker(terminalTracker);\n }\n // Store reference globally for debugging\n window._hdspIdleMonitor = idleMonitor;\n console.log('[IdleMonitorPlugin] Idle Monitor activated successfully');\n }\n catch (error) {\n console.error('[IdleMonitorPlugin] Failed to activate:', error);\n }\n }\n};\n","/**\n * LSP Bridge Plugin\n *\n * jupyterlab-lsp와 HDSP Agent를 연결하는 브릿지\n * Crush 패턴 적용: Version-based 캐싱, 이벤트 기반 업데이트\n *\n * 주요 기능:\n * - LSP 진단 결과 캐싱 및 조회\n * - 심볼 참조 검색\n * - Agent 도구와 연동\n */\nimport { INotebookTracker } from '@jupyterlab/notebook';\n/**\n * Plugin namespace\n */\nconst PLUGIN_ID = '@hdsp-agent/lsp-bridge';\n/**\n * LSP Diagnostic severity levels\n */\nexport var DiagnosticSeverity;\n(function (DiagnosticSeverity) {\n DiagnosticSeverity[DiagnosticSeverity[\"Error\"] = 1] = \"Error\";\n DiagnosticSeverity[DiagnosticSeverity[\"Warning\"] = 2] = \"Warning\";\n DiagnosticSeverity[DiagnosticSeverity[\"Information\"] = 3] = \"Information\";\n DiagnosticSeverity[DiagnosticSeverity[\"Hint\"] = 4] = \"Hint\";\n})(DiagnosticSeverity || (DiagnosticSeverity = {}));\n/**\n * LSP State Cache (Crush의 VersionedMap 패턴)\n * 버전 기반 캐시 무효화로 불필요한 재계산 방지\n */\nclass LSPStateCache {\n constructor() {\n this.diagnosticsVersion = 0;\n this.diagnosticsCache = new Map();\n this.summaryCache = null;\n this.summaryVersion = -1;\n }\n /**\n * Update diagnostics for a file\n */\n updateDiagnostics(uri, diagnostics) {\n this.diagnosticsCache.set(uri, diagnostics);\n this.diagnosticsVersion++;\n }\n /**\n * Get diagnostics for a specific file or all files\n */\n getDiagnostics(uri) {\n if (uri) {\n return {\n version: this.diagnosticsVersion,\n diagnostics: this.diagnosticsCache.get(uri) || []\n };\n }\n return {\n version: this.diagnosticsVersion,\n diagnostics: this.diagnosticsCache\n };\n }\n /**\n * Get diagnostic counts with caching (Crush 패턴)\n */\n getDiagnosticSummary() {\n // Return cached if version matches\n if (this.summaryVersion === this.diagnosticsVersion && this.summaryCache) {\n return this.summaryCache;\n }\n // Recalculate\n let errors = 0;\n let warnings = 0;\n let hints = 0;\n for (const diags of this.diagnosticsCache.values()) {\n for (const d of diags) {\n switch (d.severity) {\n case DiagnosticSeverity.Error:\n errors++;\n break;\n case DiagnosticSeverity.Warning:\n warnings++;\n break;\n default:\n hints++;\n }\n }\n }\n this.summaryCache = {\n errors,\n warnings,\n hints,\n total: errors + warnings + hints,\n version: this.diagnosticsVersion\n };\n this.summaryVersion = this.diagnosticsVersion;\n return this.summaryCache;\n }\n /**\n * Clear diagnostics for a file\n */\n clearDiagnostics(uri) {\n if (this.diagnosticsCache.has(uri)) {\n this.diagnosticsCache.delete(uri);\n this.diagnosticsVersion++;\n }\n }\n /**\n * Clear all diagnostics\n */\n clearAll() {\n this.diagnosticsCache.clear();\n this.diagnosticsVersion++;\n }\n /**\n * Get current version\n */\n getVersion() {\n return this.diagnosticsVersion;\n }\n}\n/**\n * LSP Bridge Service\n * Agent 도구에서 LSP 기능을 사용할 수 있게 해주는 서비스\n */\nclass LSPBridgeService {\n constructor() {\n this.cache = new LSPStateCache();\n this.lspAvailable = false;\n this.notebookTracker = null;\n this.checkLSPAvailability();\n }\n /**\n * Check if jupyterlab-lsp is available\n */\n async checkLSPAvailability() {\n try {\n // jupyterlab-lsp가 설치되어 있는지 확인\n // 실제로는 ILSPDocumentConnectionManager 토큰을 통해 확인\n this.lspAvailable = false; // 기본값, 실제 연결 시 true로 변경\n console.log('[LSPBridge] LSP availability check - will be updated when connection established');\n }\n catch (error) {\n console.warn('[LSPBridge] LSP not available:', error);\n this.lspAvailable = false;\n }\n }\n /**\n * Set notebook tracker for document access\n */\n setNotebookTracker(tracker) {\n this.notebookTracker = tracker;\n }\n /**\n * Mark LSP as available (called when connection is established)\n */\n setLSPAvailable(available) {\n this.lspAvailable = available;\n console.log('[LSPBridge] LSP available:', available);\n }\n /**\n * Check if LSP is available\n */\n isLSPAvailable() {\n return this.lspAvailable;\n }\n /**\n * Update diagnostics from external source (e.g., jupyterlab-lsp)\n */\n updateDiagnostics(uri, diagnostics) {\n this.cache.updateDiagnostics(uri, diagnostics);\n this.notifyDiagnosticsChanged(uri);\n }\n /**\n * Get diagnostics for Agent tool\n * Returns formatted diagnostics for a file or entire project\n */\n async getDiagnostics(filePath) {\n const result = this.cache.getDiagnostics(filePath ? this.pathToUri(filePath) : undefined);\n const summary = this.cache.getDiagnosticSummary();\n const formattedDiagnostics = [];\n if (filePath) {\n // Single file\n const diags = result.diagnostics;\n for (const d of diags) {\n formattedDiagnostics.push({\n severity: this.severityToString(d.severity),\n line: d.range.start.line + 1,\n character: d.range.start.character,\n message: d.message,\n source: d.source,\n code: d.code,\n file: filePath\n });\n }\n }\n else {\n // All files\n const diagsMap = result.diagnostics;\n for (const [uri, diags] of diagsMap) {\n const file = this.uriToPath(uri);\n for (const d of diags) {\n formattedDiagnostics.push({\n severity: this.severityToString(d.severity),\n line: d.range.start.line + 1,\n character: d.range.start.character,\n message: d.message,\n source: d.source,\n code: d.code,\n file\n });\n }\n }\n }\n // Sort: errors first, then by file and line (Crush 패턴)\n formattedDiagnostics.sort((a, b) => {\n const severityOrder = { error: 0, warning: 1, information: 2, hint: 3 };\n const severityDiff = (severityOrder[a.severity] || 3) - (severityOrder[b.severity] || 3);\n if (severityDiff !== 0)\n return severityDiff;\n const fileDiff = a.file.localeCompare(b.file);\n if (fileDiff !== 0)\n return fileDiff;\n return a.line - b.line;\n });\n return {\n success: true,\n diagnostics: formattedDiagnostics,\n summary\n };\n }\n /**\n * Find references for a symbol (Crush의 Grep-then-LSP 패턴)\n * 현재는 grep 기반으로 구현, LSP 연결 시 향상 가능\n */\n async getReferences(symbol, filePath, line, character) {\n // LSP가 없으면 grep 기반으로 검색하도록 안내\n if (!this.lspAvailable) {\n return {\n success: false,\n locations: []\n };\n }\n // TODO: jupyterlab-lsp 연결 시 구현\n // 현재는 빈 결과 반환 (Agent가 search_workspace_tool 사용하도록)\n return {\n success: false,\n locations: []\n };\n }\n /**\n * Get diagnostic summary\n */\n getDiagnosticSummary() {\n return this.cache.getDiagnosticSummary();\n }\n /**\n * Notify diagnostics changed event\n */\n notifyDiagnosticsChanged(uri) {\n const summary = this.cache.getDiagnosticSummary();\n window.dispatchEvent(new CustomEvent('hdsp-lsp-diagnostics-changed', {\n detail: { uri, summary }\n }));\n }\n /**\n * Convert severity number to string\n */\n severityToString(severity) {\n switch (severity) {\n case DiagnosticSeverity.Error:\n return 'error';\n case DiagnosticSeverity.Warning:\n return 'warning';\n case DiagnosticSeverity.Information:\n return 'information';\n case DiagnosticSeverity.Hint:\n return 'hint';\n default:\n return 'hint';\n }\n }\n /**\n * Convert file path to URI\n */\n pathToUri(path) {\n if (path.startsWith('file://')) {\n return path;\n }\n return `file://${path.startsWith('/') ? '' : '/'}${path}`;\n }\n /**\n * Convert URI to file path\n */\n uriToPath(uri) {\n if (uri.startsWith('file://')) {\n return uri.replace('file://', '');\n }\n return uri;\n }\n /**\n * Dispose service\n */\n dispose() {\n this.cache.clearAll();\n console.log('[LSPBridge] Service disposed');\n }\n}\n/**\n * Global LSP Bridge instance\n */\nlet lspBridge = null;\n/**\n * Get LSP Bridge instance\n */\nexport function getLSPBridge() {\n return lspBridge;\n}\n/**\n * LSP Bridge Plugin\n *\n * jupyterlab-lsp가 설치되어 있으면 연동하고,\n * 없으면 기본 기능만 제공\n */\nexport const lspBridgePlugin = {\n id: PLUGIN_ID,\n autoStart: true,\n requires: [],\n optional: [INotebookTracker],\n activate: (app, notebookTracker) => {\n console.log('[LSPBridgePlugin] Activating LSP Bridge...');\n // Create LSP Bridge service\n lspBridge = new LSPBridgeService();\n // Connect notebook tracker if available\n if (notebookTracker) {\n lspBridge.setNotebookTracker(notebookTracker);\n }\n // Store reference globally for debugging and Agent access\n window._hdspLSPBridge = lspBridge;\n // Try to connect to jupyterlab-lsp if available\n // This is done dynamically to avoid hard dependency\n tryConnectToLSP(app, lspBridge);\n console.log('[LSPBridgePlugin] LSP Bridge activated');\n }\n};\n/**\n * Try to connect to jupyterlab-lsp extension\n */\nasync function tryConnectToLSP(app, bridge) {\n try {\n // Check if ILSPDocumentConnectionManager is available\n // This token is provided by jupyterlab-lsp\n const lspToken = '@jupyterlab/lsp:ILSPDocumentConnectionManager';\n // Try to get the LSP manager from the application\n // Note: This requires jupyterlab-lsp to be installed\n const hasLSP = app.commands.hasCommand('lsp:show-diagnostics-panel');\n if (hasLSP) {\n console.log('[LSPBridgePlugin] jupyterlab-lsp detected');\n bridge.setLSPAvailable(true);\n // Listen for LSP diagnostic events\n // jupyterlab-lsp publishes diagnostics through its own mechanism\n setupLSPEventListeners(bridge);\n }\n else {\n console.log('[LSPBridgePlugin] jupyterlab-lsp not detected, using fallback mode');\n bridge.setLSPAvailable(false);\n }\n }\n catch (error) {\n console.warn('[LSPBridgePlugin] Failed to connect to jupyterlab-lsp:', error);\n bridge.setLSPAvailable(false);\n }\n}\n/**\n * Setup event listeners for LSP events\n */\nfunction setupLSPEventListeners(bridge) {\n // jupyterlab-lsp uses its own event system\n // We'll listen for changes through polling or direct integration\n // This is a placeholder for future implementation\n console.log('[LSPBridgePlugin] LSP event listeners setup (placeholder)');\n // For now, we can use a simple polling mechanism or\n // hook into JupyterLab's document change events\n}\nexport default lspBridgePlugin;\n","/**\n * Prompt Generation Plugin\n * Adds \"프롬프트 생성\" to Jupyter Launcher\n */\nimport { ILauncher } from '@jupyterlab/launcher';\nimport { ICommandPalette, ReactWidget, Notification } from '@jupyterlab/apputils';\nimport { IDocumentManager } from '@jupyterlab/docmanager';\nimport { LabIcon } from '@jupyterlab/ui-components';\nimport { Widget } from '@lumino/widgets';\nimport React from 'react';\nimport { ThemeProvider, createTheme } from '@mui/material';\nimport hdspIconSvg from '../styles/icons/hdsp-icon.svg';\nimport { PromptGenerationDialog } from '../components/PromptGenerationDialog';\nimport { TaskProgressWidget } from '../components/TaskProgressWidget';\nimport { TaskService } from '../services/TaskService';\n/**\n * Plugin constants\n */\nconst PLUGIN_ID = '@hdsp-agent/prompt-generation';\nconst COMMAND_ID = 'hdsp-agent:generate-from-prompt';\nconst CATEGORY = 'HALO Agent';\n/**\n * HALO Icon\n */\nconst hdspIcon = new LabIcon({\n name: 'hdsp-agent:hdsp-icon',\n svgstr: hdspIconSvg\n});\n/**\n * Task Manager Widget\n * Manages active notebook generation tasks\n */\nclass TaskManagerWidget extends ReactWidget {\n constructor(app, docManager) {\n super();\n this.app = app;\n this.docManager = docManager;\n this.taskService = new TaskService();\n this.activeTasks = new Map();\n this.addClass('jp-TaskManagerWidget');\n }\n /**\n * Start a new notebook generation task\n */\n async startGeneration(prompt) {\n try {\n // Start generation\n const response = await this.taskService.generateNotebook({ prompt });\n const taskId = response.taskId;\n // Subscribe to progress\n this.taskService.subscribeToTaskProgress(taskId, (status) => {\n this.activeTasks.set(taskId, status);\n this.update();\n // Show notification on completion\n if (status.status === 'completed' && status.notebookPath) {\n Notification.success(`노트북 생성 완료: ${status.notebookPath.split('/').pop()}`, {\n autoClose: 5000\n });\n }\n else if (status.status === 'failed') {\n Notification.error(`노트북 생성 실패: ${status.error}`, {\n autoClose: 10000\n });\n }\n }, (error) => {\n console.error('Task progress error:', error);\n Notification.error('진행상황 연결 실패', { autoClose: 5000 });\n }, () => {\n // Task completed, keep in list for user to review\n this.update();\n });\n // Show initial notification\n Notification.info('노트북 생성을 시작했습니다', { autoClose: 3000 });\n this.update();\n }\n catch (error) {\n console.error('Failed to start generation:', error);\n Notification.error(`생성 시작 실패: ${error.message}`, {\n autoClose: 5000\n });\n }\n }\n /**\n * Cancel a task\n */\n async cancelTask(taskId) {\n try {\n await this.taskService.cancelTask(taskId);\n this.activeTasks.delete(taskId);\n this.update();\n Notification.info('작업을 취소했습니다', { autoClose: 3000 });\n }\n catch (error) {\n console.error('Failed to cancel task:', error);\n Notification.error(`취소 실패: ${error.message}`, { autoClose: 5000 });\n }\n }\n /**\n * Close/remove task from display\n */\n closeTask(taskId) {\n this.taskService.unsubscribeFromTask(taskId);\n this.activeTasks.delete(taskId);\n this.update();\n }\n /**\n * Open generated notebook\n */\n async openNotebook(notebookPath, taskId) {\n try {\n // Extract just the filename from the path\n const filename = notebookPath.split('/').pop() || notebookPath;\n // Open the notebook\n await this.docManager.openOrReveal(filename);\n // Close the task widget after opening\n this.closeTask(taskId);\n Notification.success(`노트북을 열었습니다: ${filename}`, {\n autoClose: 3000\n });\n }\n catch (error) {\n console.error('Failed to open notebook:', error);\n Notification.error(`노트북 열기 실패: ${error.message}`, {\n autoClose: 5000\n });\n }\n }\n render() {\n const theme = createTheme();\n const tasks = Array.from(this.activeTasks.entries());\n return (React.createElement(ThemeProvider, { theme: theme },\n React.createElement(\"div\", { style: { position: 'fixed', bottom: 0, right: 0, zIndex: 1300 } }, tasks.map(([taskId, status], index) => (React.createElement(\"div\", { key: taskId, style: {\n marginBottom: index < tasks.length - 1 ? '10px' : '0'\n } },\n React.createElement(TaskProgressWidget, { taskStatus: status, onClose: () => this.closeTask(taskId), onCancel: () => this.cancelTask(taskId), onOpenNotebook: () => status.notebookPath &&\n this.openNotebook(status.notebookPath, taskId) })))))));\n }\n dispose() {\n this.taskService.dispose();\n super.dispose();\n }\n}\n/**\n * Dialog Widget for prompt input\n */\nclass PromptDialogWidget extends ReactWidget {\n constructor(onGenerate, onClose) {\n super();\n this._onGenerate = onGenerate;\n this._onClose = onClose;\n }\n render() {\n const theme = createTheme();\n return (React.createElement(ThemeProvider, { theme: theme },\n React.createElement(PromptGenerationDialog, { open: true, onClose: this._onClose, onGenerate: this._onGenerate })));\n }\n}\n/**\n * Prompt Generation Plugin\n */\nexport const promptGenerationPlugin = {\n id: PLUGIN_ID,\n autoStart: true,\n requires: [IDocumentManager],\n optional: [ILauncher, ICommandPalette],\n activate: (app, docManager, launcher, palette) => {\n console.log('[PromptGenerationPlugin] Activating');\n try {\n // Create task manager widget\n const taskManager = new TaskManagerWidget(app, docManager);\n taskManager.id = 'hdsp-agent-task-manager';\n taskManager.title.label = 'Task Manager';\n // Add to shell but keep it as a floating overlay (not in main area)\n // The widget renders itself as a fixed position element\n Widget.attach(taskManager, document.body);\n // Add command\n app.commands.addCommand(COMMAND_ID, {\n label: '프롬프트로 노트북 만들기',\n caption: '프롬프트로 노트북 만들기',\n icon: hdspIcon,\n execute: () => {\n // Create dialog widget\n let dialogWidget = null;\n const onGenerate = async (prompt) => {\n if (dialogWidget) {\n dialogWidget.dispose();\n dialogWidget = null;\n }\n await taskManager.startGeneration(prompt);\n };\n const onClose = () => {\n if (dialogWidget) {\n dialogWidget.dispose();\n dialogWidget = null;\n }\n };\n dialogWidget = new PromptDialogWidget(onGenerate, onClose);\n dialogWidget.id = 'hdsp-agent-prompt-dialog';\n dialogWidget.title.label = '';\n app.shell.add(dialogWidget, 'main');\n }\n });\n // Add to launcher\n if (launcher) {\n launcher.add({\n command: COMMAND_ID,\n category: 'Notebook',\n rank: 1\n });\n }\n // Add to command palette\n if (palette) {\n palette.addItem({\n command: COMMAND_ID,\n category: CATEGORY\n });\n }\n console.log('[PromptGenerationPlugin] Activated successfully');\n }\n catch (error) {\n console.error('[PromptGenerationPlugin] Failed to activate:', error);\n }\n }\n};\n","/**\n * Save Interceptor Plugin\n * Intercepts notebook save operations to offer code review before saving\n * Based on chrome_agent's save interception functionality\n */\nimport { INotebookTracker } from '@jupyterlab/notebook';\nimport { ICommandPalette } from '@jupyterlab/apputils';\n/**\n * Save Interceptor Plugin\n */\nexport const saveInterceptorPlugin = {\n id: '@hdsp-agent/save-interceptor',\n autoStart: true,\n requires: [INotebookTracker],\n optional: [ICommandPalette],\n activate: (app, notebookTracker, palette) => {\n console.log('[SaveInterceptorPlugin] Activated');\n let isSaving = false;\n let originalSaveCommand = null;\n // Store original save command and wrap it\n if (app.commands.hasCommand('docmanager:save')) {\n console.log('[SaveInterceptorPlugin] Found docmanager:save command');\n const commandRegistry = app.commands;\n const commandId = 'docmanager:save';\n // Get the original command's execute function\n const originalCommand = commandRegistry._commands.get(commandId);\n if (originalCommand) {\n originalSaveCommand = originalCommand.execute.bind(originalCommand);\n console.log('[SaveInterceptorPlugin] Stored original save command');\n // Replace the execute function\n originalCommand.execute = async (args) => {\n // Use app.shell.currentWidget to get the actually focused widget\n const currentWidget = app.shell.currentWidget;\n console.log('[SaveInterceptorPlugin] Save command triggered', {\n hasCurrentWidget: !!currentWidget,\n isSaving: isSaving,\n widgetType: currentWidget?.constructor?.name\n });\n // Only intercept for Python files (.ipynb, .py)\n if (!currentWidget) {\n console.log('[SaveInterceptorPlugin] No current widget, allowing default save');\n return originalSaveCommand(args);\n }\n // Check if the current widget has a context (is a document)\n const context = currentWidget.context;\n if (!context) {\n console.log('[SaveInterceptorPlugin] No context, allowing default save');\n return originalSaveCommand(args);\n }\n // Check if the current document is a Python file\n const path = context?.path || '';\n const isPythonFile = path.endsWith('.ipynb') || path.endsWith('.py');\n if (!isPythonFile) {\n console.log('[SaveInterceptorPlugin] Not a Python file, allowing default save', { path });\n return originalSaveCommand(args);\n }\n if (isSaving) {\n console.log('[SaveInterceptorPlugin] Already in save process, executing actual save');\n return originalSaveCommand(args);\n }\n console.log('[SaveInterceptorPlugin] Intercepting Python file save, showing modal', { path });\n await handleSaveIntercept(currentWidget, app, originalSaveCommand, args, () => isSaving = true, () => isSaving = false);\n };\n console.log('[SaveInterceptorPlugin] Wrapped save command installed');\n }\n else {\n console.error('[SaveInterceptorPlugin] Could not find original save command');\n }\n }\n // Also intercept keyboard shortcut as backup\n document.addEventListener('keydown', async (e) => {\n if ((e.ctrlKey || e.metaKey) && e.key === 's') {\n // Use app.shell.currentWidget instead of notebookTracker to get the actually focused widget\n const currentWidget = app.shell.currentWidget;\n if (!currentWidget) {\n return; // Not in a document, let default save happen\n }\n // Check if the current widget has a context (is a document)\n const context = currentWidget.context;\n if (!context) {\n return; // Not a document widget, let default save happen\n }\n // Check if the current document is a Python file\n const path = context?.path || '';\n const isPythonFile = path.endsWith('.ipynb') || path.endsWith('.py');\n if (!isPythonFile) {\n console.log('[SaveInterceptorPlugin] Not a Python file, allowing default save', { path });\n return; // Not a Python file, let default save happen\n }\n console.log('[SaveInterceptorPlugin] Save shortcut (Ctrl/Cmd+S) detected for Python file', { path });\n e.preventDefault();\n e.stopPropagation();\n if (isSaving) {\n console.log('[SaveInterceptorPlugin] Already saving, ignoring duplicate request');\n return;\n }\n if (!originalSaveCommand) {\n console.error('[SaveInterceptorPlugin] No original save command stored');\n return;\n }\n await handleSaveIntercept(currentWidget, app, originalSaveCommand, undefined, () => isSaving = true, () => isSaving = false);\n }\n }, true); // Use capture phase to intercept before JupyterLab\n console.log('[SaveInterceptorPlugin] Save interception enabled');\n }\n};\n/**\n * Handle save intercept - show modal and collect cells if needed\n */\nasync function handleSaveIntercept(panel, app, originalSaveCommand, args, setSaving, clearSaving) {\n // Show confirmation modal\n const shouldAnalyze = await showSaveConfirmModal();\n if (shouldAnalyze === null) {\n // User cancelled\n clearSaving();\n return;\n }\n if (shouldAnalyze) {\n // Collect all cells and send for analysis\n await performAnalysisSave(panel, app, originalSaveCommand, args, setSaving, clearSaving);\n }\n else {\n // Direct save without analysis\n await performDirectSave(panel, app, originalSaveCommand, args, setSaving, clearSaving);\n }\n}\n/**\n * Show save confirmation modal\n * Returns: true for analysis, false for direct save, null for cancel\n */\nfunction showSaveConfirmModal() {\n return new Promise((resolve) => {\n // Remove existing modal if any\n const existingModal = document.querySelector('.jp-agent-save-confirm-modal');\n if (existingModal) {\n existingModal.remove();\n }\n // Create modal overlay\n const modalOverlay = document.createElement('div');\n modalOverlay.className = 'jp-agent-save-confirm-modal';\n modalOverlay.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.3);\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n `;\n // Create modal container\n const modalContainer = document.createElement('div');\n modalContainer.style.cssText = `\n background: var(--jp-layout-color1);\n border: 1px solid var(--jp-border-color1);\n border-radius: 4px;\n padding: 24px;\n max-width: 400px;\n width: 90%;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n font-family: var(--jp-ui-font-family);\n `;\n // Modal content\n modalContainer.innerHTML = `\n <div style=\"margin-bottom: 20px;\">\n <h3 style=\"margin: 0 0 12px 0; color: var(--jp-ui-font-color1); font-size: 16px; font-weight: 500;\">\n 저장 전에 코드 검수를 하겠습니까?\n </h3>\n <p style=\"margin: 0; color: var(--jp-ui-font-color2); font-size: 14px; line-height: 1.5;\">\n 노트북을 저장하기 전에 모든 셀의 코드와 출력을 분석하여 최적화 제안을 받으시겠습니까?\n </p>\n </div>\n <div style=\"display: flex; gap: 12px; justify-content: flex-end;\">\n <button class=\"jp-agent-save-direct-btn\" style=\"\n background: transparent;\n color: var(--jp-ui-font-color2);\n border: 1px solid var(--jp-border-color2);\n border-radius: 3px;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n \">그냥 저장하기</button>\n <button class=\"jp-agent-save-analysis-btn\" style=\"\n background: var(--jp-brand-color1);\n color: white;\n border: 1px solid var(--jp-brand-color1);\n border-radius: 3px;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n \">검수 후 저장하기</button>\n </div>\n `;\n modalOverlay.appendChild(modalContainer);\n document.body.appendChild(modalOverlay);\n // Button event listeners\n const directSaveBtn = modalContainer.querySelector('.jp-agent-save-direct-btn');\n const analysisSaveBtn = modalContainer.querySelector('.jp-agent-save-analysis-btn');\n // Direct save button\n directSaveBtn.addEventListener('click', () => {\n console.log('[SaveInterceptorPlugin] User chose direct save');\n modalOverlay.remove();\n resolve(false);\n });\n directSaveBtn.addEventListener('mouseenter', () => {\n directSaveBtn.style.background = 'var(--jp-layout-color2)';\n });\n directSaveBtn.addEventListener('mouseleave', () => {\n directSaveBtn.style.background = 'transparent';\n });\n // Analysis save button\n analysisSaveBtn.addEventListener('click', () => {\n console.log('[SaveInterceptorPlugin] User chose analysis save');\n modalOverlay.remove();\n resolve(true);\n });\n analysisSaveBtn.addEventListener('mouseenter', () => {\n analysisSaveBtn.style.opacity = '0.9';\n });\n analysisSaveBtn.addEventListener('mouseleave', () => {\n analysisSaveBtn.style.opacity = '1';\n });\n // Close on overlay click\n modalOverlay.addEventListener('click', (e) => {\n if (e.target === modalOverlay) {\n console.log('[SaveInterceptorPlugin] User cancelled save');\n modalOverlay.remove();\n resolve(null);\n }\n });\n // ESC key to close\n const handleEscapeKey = (e) => {\n if (e.key === 'Escape') {\n console.log('[SaveInterceptorPlugin] User cancelled save with ESC');\n modalOverlay.remove();\n document.removeEventListener('keydown', handleEscapeKey);\n resolve(null);\n }\n };\n document.addEventListener('keydown', handleEscapeKey);\n });\n}\n/**\n * Perform direct save without analysis\n */\nasync function performDirectSave(panel, app, originalSaveCommand, args, setSaving, clearSaving) {\n console.log('[SaveInterceptorPlugin] Performing direct save');\n setSaving();\n try {\n // Use the original save command\n if (originalSaveCommand) {\n await originalSaveCommand(args);\n showNotification('저장이 완료되었습니다.', 'success');\n }\n else {\n // Fallback: Use JupyterLab's document manager to save\n const context = panel.context;\n if (context) {\n await context.save();\n showNotification('저장이 완료되었습니다.', 'success');\n }\n }\n }\n catch (error) {\n console.error('[SaveInterceptorPlugin] Save failed:', error);\n showNotification('저장에 실패했습니다.', 'error');\n }\n finally {\n clearSaving();\n }\n}\n/**\n * Perform analysis save - collect cells and send to AgentPanel\n */\nasync function performAnalysisSave(panel, app, originalSaveCommand, args, setSaving, clearSaving) {\n console.log('[SaveInterceptorPlugin] Performing analysis save');\n try {\n // Collect all code cells\n const cells = await collectAllCodeCells(panel);\n if (cells.length === 0) {\n showNotification('분석할 코드 셀이 없습니다. 그냥 저장합니다.', 'warning');\n await performDirectSave(panel, app, originalSaveCommand, args, setSaving, clearSaving);\n return;\n }\n showNotification('코드 분석을 시작합니다...', 'info');\n // Get AgentPanel instance\n const agentPanel = window._hdspAgentPanel;\n if (!agentPanel) {\n console.error('[SaveInterceptorPlugin] Agent panel not found');\n showNotification('Agent panel을 찾을 수 없습니다. 그냥 저장합니다.', 'warning');\n await performDirectSave(panel, app, originalSaveCommand, args, setSaving, clearSaving);\n return;\n }\n // Activate the sidebar panel\n if (app) {\n app.shell.activateById(agentPanel.id);\n }\n // Send cells to AgentPanel for analysis\n if (agentPanel.analyzeNotebook) {\n agentPanel.analyzeNotebook(cells, async () => {\n // Callback after analysis complete - perform save\n await performDirectSave(panel, app, originalSaveCommand, args, setSaving, clearSaving);\n });\n }\n else {\n console.error('[SaveInterceptorPlugin] analyzeNotebook method not found on AgentPanel');\n showNotification('분석 기능을 사용할 수 없습니다. 그냥 저장합니다.', 'warning');\n await performDirectSave(panel, app, originalSaveCommand, args, setSaving, clearSaving);\n }\n }\n catch (error) {\n console.error('[SaveInterceptorPlugin] Analysis save failed:', error);\n showNotification('분석에 실패했습니다. 그냥 저장합니다.', 'warning');\n await performDirectSave(panel, app, originalSaveCommand, args, setSaving, clearSaving);\n }\n}\n/**\n * Collect all code cells with content, output, and imports\n * Based on chrome_agent's collectAllCodeCells implementation\n */\nasync function collectAllCodeCells(panel) {\n const notebook = panel.content;\n const cells = [];\n for (let i = 0; i < notebook.widgets.length; i++) {\n const cell = notebook.widgets[i];\n // Only collect code cells - null safety 추가\n if (!cell?.model || cell.model.type !== 'code') {\n continue;\n }\n const content = cell.model.sharedModel?.getSource() || '';\n if (!content.trim()) {\n continue;\n }\n // Get or assign cell ID\n const cellId = getOrAssignCellId(cell);\n // Extract output\n const output = getCellOutput(cell);\n // Extract imports\n const imports = extractImports(content);\n // Get local module sources (simplified - would need kernel API access for full implementation)\n const localImports = imports.filter(imp => imp.isLocal);\n const localModuleSources = {};\n // Note: Full module source extraction would require kernel websocket connection\n // This is a simplified version for now\n for (const imp of localImports) {\n if (!imp.module.startsWith('.')) {\n // Could implement kernel-based source extraction here\n localModuleSources[imp.module] = `# Source for ${imp.module} would be fetched from kernel`;\n }\n }\n cells.push({\n index: i,\n id: cellId,\n content: content,\n type: 'code',\n output: output,\n imports: imports,\n localModuleSources: Object.keys(localModuleSources).length > 0 ? localModuleSources : undefined\n });\n }\n console.log(`[SaveInterceptorPlugin] Collected ${cells.length} code cells`);\n return cells;\n}\n/**\n * Get or assign cell ID to a cell\n */\nfunction getOrAssignCellId(cell) {\n const cellModel = cell.model;\n let cellId;\n // Try to get cell ID from metadata\n try {\n if (cellModel.metadata && typeof cellModel.metadata.get === 'function') {\n cellId = cellModel.metadata.get('jupyterAgentCellId');\n }\n else if (cellModel.metadata?.jupyterAgentCellId) {\n cellId = cellModel.metadata.jupyterAgentCellId;\n }\n }\n catch (e) {\n // Ignore errors\n }\n if (!cellId) {\n cellId = `cell-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n // Try to set cell ID in metadata\n try {\n if (cellModel.metadata && typeof cellModel.metadata.set === 'function') {\n cellModel.metadata.set('jupyterAgentCellId', cellId);\n }\n else if (cellModel.metadata) {\n cellModel.metadata.jupyterAgentCellId = cellId;\n }\n }\n catch (e) {\n // Ignore errors\n }\n }\n return cellId;\n}\n/**\n * Extract output from a code cell\n */\nfunction getCellOutput(cell) {\n // cell.model이 null일 수 있으므로 안전하게 체크\n if (!cell?.model || cell.model.type !== 'code') {\n return '';\n }\n const codeCell = cell;\n const outputs = codeCell.model?.outputs;\n if (!outputs || outputs.length === 0) {\n return '';\n }\n const outputTexts = [];\n for (let i = 0; i < outputs.length; i++) {\n const output = outputs.get(i);\n const outputType = output.type;\n if (outputType === 'stream') {\n // stdout, stderr\n const text = output.text;\n if (Array.isArray(text)) {\n outputTexts.push(text.join(''));\n }\n else {\n outputTexts.push(text);\n }\n }\n else if (outputType === 'execute_result' || outputType === 'display_data') {\n // Execution results or display data\n const data = output.data;\n if (data['text/plain']) {\n const text = data['text/plain'];\n if (Array.isArray(text)) {\n outputTexts.push(text.join(''));\n }\n else {\n outputTexts.push(text);\n }\n }\n }\n else if (outputType === 'error') {\n // Error output\n const traceback = output.traceback;\n if (Array.isArray(traceback)) {\n outputTexts.push(traceback.join('\\n'));\n }\n }\n }\n return outputTexts.join('\\n');\n}\n/**\n * Extract imports from Python code\n * Based on chrome_agent's extractImports implementation\n */\nfunction extractImports(cellContent) {\n const imports = [];\n const lines = cellContent.split('\\n');\n for (const line of lines) {\n const trimmed = line.trim();\n // Skip comments and empty lines\n if (trimmed.startsWith('#') || trimmed === '')\n continue;\n // Relative import: from . import ... or from .. import ...\n const relativeMatch = trimmed.match(/^from\\s+(\\.+[\\w.]*)\\s+import\\s+([\\w,\\s*]+)/);\n if (relativeMatch) {\n imports.push({\n module: relativeMatch[1],\n items: relativeMatch[2],\n isLocal: true\n });\n continue;\n }\n // from X import Y form\n const fromImportMatch = trimmed.match(/^from\\s+([\\w.]+)\\s+import\\s+([\\w,\\s*]+)/);\n if (fromImportMatch) {\n const moduleName = fromImportMatch[1];\n imports.push({\n module: moduleName,\n items: fromImportMatch[2],\n isLocal: isLocalImport(moduleName)\n });\n continue;\n }\n // import X, Y, Z form\n const importMatch = trimmed.match(/^import\\s+([\\w,\\s.]+)/);\n if (importMatch) {\n const modules = importMatch[1].split(',').map(m => m.trim().split(' as ')[0]);\n modules.forEach(moduleName => {\n imports.push({\n module: moduleName,\n items: moduleName,\n isLocal: isLocalImport(moduleName)\n });\n });\n }\n }\n return imports;\n}\n/**\n * Determine if a module import is local (not a standard library)\n */\nfunction isLocalImport(moduleName) {\n // Relative paths are always local\n if (moduleName.startsWith('.')) {\n return true;\n }\n // Common Python libraries (not exhaustive)\n const commonLibraries = [\n 'numpy', 'np', 'pandas', 'pd', 'matplotlib', 'sklearn', 'scipy',\n 'torch', 'tensorflow', 'tf', 'keras', 'PIL', 'cv2', 'requests',\n 'json', 'os', 'sys', 'datetime', 'time', 'math', 'random',\n 'collections', 'itertools', 'functools', 're', 'pathlib',\n 'typing', 'dataclasses', 'abc', 'argparse', 'logging',\n 'pickle', 'csv', 'sqlite3', 'http', 'urllib', 'email',\n 'plotly', 'seaborn', 'statsmodels', 'xgboost', 'lightgbm',\n 'transformers', 'datasets', 'accelerate', 'peft',\n 'openai', 'anthropic', 'langchain', 'llama_index'\n ];\n // Get top-level module name\n const topLevelModule = moduleName.split('.')[0];\n // If not in common libraries, consider it local\n return !commonLibraries.includes(topLevelModule);\n}\n/**\n * Get notification background color based on type\n */\nfunction getNotificationColor(type) {\n const colors = {\n success: '#48bb78',\n error: '#f56565',\n warning: '#ed8936',\n info: '#4299e1'\n };\n return colors[type] || colors.info;\n}\n/**\n * Create notification element with styles\n */\nfunction createNotificationElement(message, backgroundColor) {\n const notification = document.createElement('div');\n notification.style.cssText = `\n position: fixed;\n top: 20px;\n right: 20px;\n padding: 12px 20px;\n border-radius: 8px;\n color: white;\n font-size: 14px;\n font-weight: 500;\n z-index: 10001;\n opacity: 0;\n transition: opacity 0.3s ease;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n max-width: 300px;\n word-wrap: break-word;\n background: ${backgroundColor};\n `;\n notification.textContent = message;\n return notification;\n}\n/**\n * Animate notification appearance and removal\n */\nfunction animateNotification(notification) {\n // Show animation\n setTimeout(() => {\n notification.style.opacity = '1';\n }, 10);\n // Remove after 3 seconds\n setTimeout(() => {\n notification.style.opacity = '0';\n setTimeout(() => {\n if (notification.parentNode) {\n notification.parentNode.removeChild(notification);\n }\n }, 300);\n }, 3000);\n}\n/**\n * Show notification\n */\nfunction showNotification(message, type = 'info') {\n const notification = createNotificationElement(message, getNotificationColor(type));\n document.body.appendChild(notification);\n animateNotification(notification);\n}\n","/**\n * Sidebar Plugin\n * Adds Agent panel to JupyterLab sidebar\n */\nimport { ILayoutRestorer } from '@jupyterlab/application';\nimport { ICommandPalette, WidgetTracker } from '@jupyterlab/apputils';\nimport { INotebookTracker } from '@jupyterlab/notebook';\nimport { IConsoleTracker } from '@jupyterlab/console';\nimport { AgentPanelWidget } from '../components/AgentPanel';\nimport { ApiService } from '../services/ApiService';\n/**\n * Sidebar plugin namespace\n */\nconst PLUGIN_ID = '@hdsp-agent/sidebar';\nconst COMMAND_ID = 'hdsp-agent:toggle-sidebar';\n/**\n * Agent Sidebar Plugin\n */\nexport const sidebarPlugin = {\n id: PLUGIN_ID,\n autoStart: true,\n requires: [],\n optional: [ILayoutRestorer, ICommandPalette, INotebookTracker, IConsoleTracker],\n activate: (app, restorer, palette, notebookTracker, consoleTracker) => {\n console.log('[SidebarPlugin] Activating Jupyter Agent Sidebar');\n try {\n // Create API service\n const apiService = new ApiService();\n // Create agent panel widget with notebook tracker and console tracker\n const agentPanel = new AgentPanelWidget(apiService, notebookTracker, consoleTracker);\n // Create tracker for panel state restoration if restorer available\n if (restorer) {\n const tracker = new WidgetTracker({\n namespace: 'hdsp-agent'\n });\n // Add panel to tracker\n tracker.add(agentPanel);\n // Restore panel state\n restorer.restore(tracker, {\n command: COMMAND_ID,\n name: () => 'hdsp-agent'\n });\n }\n // Add panel to right sidebar\n app.shell.add(agentPanel, 'right', { rank: 100 });\n // Add command to toggle sidebar\n app.commands.addCommand(COMMAND_ID, {\n label: 'HALO Agent 사이드바 토글',\n caption: 'HALO Agent 패널 표시/숨기기',\n execute: () => {\n if (agentPanel.isVisible) {\n agentPanel.close();\n }\n else {\n app.shell.activateById(agentPanel.id);\n }\n }\n });\n // Add command to palette\n if (palette) {\n palette.addItem({\n command: COMMAND_ID,\n category: 'HALO Agent',\n args: {}\n });\n }\n // Store reference globally for cell buttons to access\n window._hdspAgentPanel = agentPanel;\n console.log('[SidebarPlugin] HALO Agent Sidebar activated successfully');\n }\n catch (error) {\n console.error('[SidebarPlugin] Failed to activate:', error);\n }\n }\n};\n","/**\n * AgentOrchestrator - Plan-and-Execute 오케스트레이터\n *\n * HuggingFace Jupyter Agent 패턴 기반:\n * - 사용자 요청을 단계별 실행 계획으로 분해\n * - Tool Calling을 통한 순차 실행\n * - Self-Healing (에러 발생 시 자동 수정 및 재시도)\n */\nimport { ApiService } from './ApiService';\nimport { ToolExecutor } from './ToolExecutor';\nimport { SafetyChecker } from '../utils/SafetyChecker';\nimport { StateVerifier } from './StateVerifier';\nimport { ContextManager } from './ContextManager';\nimport { CheckpointManager } from './CheckpointManager';\nimport { DEFAULT_AUTO_AGENT_CONFIG, EXECUTION_SPEED_DELAYS, } from '../types/auto-agent';\nexport class AgentOrchestrator {\n constructor(notebook, sessionContext, apiService, config) {\n this.abortController = null;\n this.isRunning = false;\n // Step-by-step 모드를 위한 상태\n this.stepByStepResolver = null;\n this.isPaused = false;\n // Validation & Reflection 설정\n this.enablePreValidation = true;\n this.enableReflection = true;\n // ★ State Verification 설정 (Phase 1)\n this.enableStateVerification = true;\n // ★ 현재 Plan 실행 중 정의된 변수 추적 (cross-step validation용)\n this.executedStepVariables = new Set();\n // ★ 현재 Plan 실행 중 import된 이름 추적 (cross-step validation용)\n this.executedStepImports = new Set();\n // ★ 실행된 변수의 실제 값 추적 (finalAnswer 변수 치환용)\n this.executedStepVariableValues = {};\n // 디버깅: 생성자에 전달된 노트북 로그\n console.log('[Orchestrator] Constructor - notebook path:', notebook?.context?.path);\n console.log('[Orchestrator] Constructor - notebook title:', notebook?.title?.label);\n this.notebook = notebook;\n this.apiService = apiService || new ApiService();\n this.toolExecutor = new ToolExecutor(notebook, sessionContext, this.apiService);\n this.safetyChecker = new SafetyChecker({\n enableSafetyCheck: config?.enableSafetyCheck ?? true,\n maxExecutionTime: (config?.executionTimeout ?? 30000) / 1000,\n });\n // ★ State Verifier 초기화 (Phase 1)\n this.stateVerifier = new StateVerifier(this.apiService);\n // ★ Context Manager 초기화 (Phase 2)\n this.contextManager = new ContextManager();\n // ★ Checkpoint Manager 초기화 (Phase 3)\n this.checkpointManager = new CheckpointManager(notebook);\n this.config = { ...DEFAULT_AUTO_AGENT_CONFIG, ...config };\n console.log('[Orchestrator] Initialized with config:', {\n executionSpeed: this.config.executionSpeed,\n stepDelay: this.config.stepDelay,\n });\n // ToolExecutor에 자동 스크롤 설정 연동\n this.toolExecutor.setAutoScroll(this.config.autoScrollToCell);\n }\n /**\n * 메인 실행 함수: 사용자 요청을 받아 자동 실행\n */\n async executeTask(userRequest, notebook, onProgress, llmConfig) {\n if (this.isRunning) {\n return {\n success: false,\n plan: null,\n executedSteps: [],\n createdCells: [],\n modifiedCells: [],\n error: 'Auto-Agent가 이미 실행 중입니다',\n totalAttempts: 0,\n };\n }\n this.isRunning = true;\n this.abortController = new AbortController();\n // ★ 새 실행 시작 시 이전 실행에서 추적된 변수/import 초기화\n this.executedStepVariables.clear();\n this.executedStepImports.clear();\n this.executedStepVariableValues = {};\n // ★ State Verification 이력 초기화 (Phase 1)\n this.stateVerifier.clearHistory();\n // ★ Checkpoint Manager 새 세션 시작 (Phase 3)\n this.checkpointManager.startNewSession();\n const createdCells = [];\n const modifiedCells = [];\n const executedSteps = [];\n const startTime = Date.now();\n try {\n // ═══════════════════════════════════════════════════════════════════════\n // PHASE 1: PLANNING - 작업 분해\n // ═══════════════════════════════════════════════════════════════════════\n onProgress({ phase: 'planning', message: '작업 계획 수립 중...' });\n const notebookContext = this.extractNotebookContext(notebook);\n const planResponse = await this.apiService.generateExecutionPlan({\n request: userRequest,\n notebookContext,\n availableTools: ['jupyter_cell', 'markdown', 'final_answer'],\n llmConfig, // Include API keys with request\n });\n const plan = planResponse.plan;\n onProgress({\n phase: 'planned',\n plan,\n message: `${plan.totalSteps}단계 실행 계획 생성됨`,\n });\n // ═══════════════════════════════════════════════════════════════════════\n // PHASE 2: EXECUTION - 단계별 실행 (Adaptive Replanning 포함)\n // ═══════════════════════════════════════════════════════════════════════\n console.log('[Orchestrator] Starting execution phase with', plan.steps.length, 'steps');\n let currentPlan = plan;\n let stepIndex = 0;\n let replanAttempts = 0;\n const MAX_REPLAN_ATTEMPTS = 3;\n while (stepIndex < currentPlan.steps.length) {\n const step = currentPlan.steps[stepIndex];\n console.log('[Orchestrator] Executing step', step.stepNumber, ':', step.description);\n // 중단 요청 확인\n if (this.abortController.signal.aborted) {\n return {\n success: false,\n plan: currentPlan,\n executedSteps,\n createdCells,\n modifiedCells,\n error: '사용자에 의해 취소됨',\n totalAttempts: this.countTotalAttempts(executedSteps),\n executionTime: Date.now() - startTime,\n };\n }\n onProgress({\n phase: 'executing',\n currentStep: step.stepNumber,\n totalSteps: currentPlan.totalSteps,\n description: step.description,\n });\n const stepResult = await this.executeStepWithRetry(step, notebook, onProgress);\n // 생성/수정된 셀 추적 및 하이라이트/스크롤\n stepResult.toolResults.forEach((tr) => {\n if (tr.cellIndex !== undefined) {\n if (tr.wasModified) {\n modifiedCells.push(tr.cellIndex);\n }\n else {\n createdCells.push(tr.cellIndex);\n }\n // 셀 하이라이트 및 스크롤\n this.scrollToAndHighlightCell(tr.cellIndex);\n }\n });\n // 단계 실패 시 Adaptive Replanning 시도\n if (!stepResult.success) {\n // Check for FILE_SELECTION_REQUIRED - throw special error for AutoAgentPanel\n console.log('[Orchestrator] Step failed. Error:', stepResult.error);\n console.log('[Orchestrator] Tool results:', stepResult.toolResults);\n if (stepResult.error === 'FILE_SELECTION_REQUIRED') {\n const fileSelectionMetadata = stepResult.toolResults.find(r => r.metadata?.type === 'file_selection')?.metadata;\n console.log('[Orchestrator] FILE_SELECTION_REQUIRED detected, metadata:', fileSelectionMetadata);\n if (fileSelectionMetadata) {\n const error = new Error('FILE_SELECTION_REQUIRED');\n error.name = 'FileSelectionError';\n error.fileSelectionMetadata = fileSelectionMetadata;\n console.log('[Orchestrator] Throwing FileSelectionError', error);\n console.log('[Orchestrator] Error object:', { name: error.name, message: error.message, hasMetadata: !!error.fileSelectionMetadata });\n throw error;\n }\n }\n console.log('[Orchestrator] Step failed, attempting adaptive replanning');\n if (replanAttempts >= MAX_REPLAN_ATTEMPTS) {\n onProgress({\n phase: 'failed',\n message: `최대 재계획 시도 횟수(${MAX_REPLAN_ATTEMPTS})를 초과했습니다.`,\n failedStep: step.stepNumber, // 실패한 step UI에 표시\n });\n return {\n success: false,\n plan: currentPlan,\n executedSteps,\n createdCells,\n modifiedCells,\n error: `Step ${step.stepNumber} 실패: ${stepResult.error}`,\n totalAttempts: this.countTotalAttempts(executedSteps),\n executionTime: Date.now() - startTime,\n };\n }\n // 실패 정보 구성 (errorName 포함 - ModuleNotFoundError 등 식별용)\n const executionError = {\n type: 'runtime',\n message: stepResult.error || '알 수 없는 오류',\n errorName: stepResult.toolResults.find(r => r.errorName)?.errorName,\n traceback: stepResult.toolResults.find(r => r.traceback)?.traceback || [],\n recoverable: true,\n };\n // 에러 타입에서 핵심 정보 추출\n const errorName = executionError.errorName || '런타임 에러';\n const shortErrorMsg = executionError.message.length > 80\n ? executionError.message.substring(0, 80) + '...'\n : executionError.message;\n onProgress({\n phase: 'replanning',\n message: `에러 분석 중: ${errorName}`,\n currentStep: step.stepNumber,\n failedStep: step.stepNumber,\n replanInfo: {\n errorType: errorName,\n rootCause: shortErrorMsg,\n },\n });\n // 마지막 실행 출력\n const lastOutput = stepResult.toolResults\n .map(r => r.output)\n .filter(Boolean)\n .join('\\n');\n try {\n const replanResponse = await this.apiService.replanExecution({\n originalPlan: currentPlan,\n currentStepIndex: stepIndex,\n error: executionError,\n executionHistory: executedSteps.map(s => ({\n stepNumber: s.stepNumber,\n success: s.success,\n attempts: s.attempts,\n })),\n previousAttempts: replanAttempts,\n previousCodes: [],\n useLlmFallback: true,\n });\n console.log('[Orchestrator] Replan decision:', replanResponse.decision);\n console.log('[Orchestrator] Replan reasoning:', replanResponse.reasoning);\n // ★ 결정 결과를 UI에 즉시 반영 (에러 분석 → 결정 완료)\n const decisionLabel = this.getReplanDecisionLabel(replanResponse.decision);\n // ModuleNotFoundError일 때 패키지명 추출\n const missingPackage = errorName === 'ModuleNotFoundError'\n ? this.extractMissingPackage(executionError.message)\n : undefined;\n onProgress({\n phase: 'replanning',\n message: `결정: ${decisionLabel}`,\n currentStep: step.stepNumber,\n failedStep: step.stepNumber,\n replanInfo: {\n errorType: errorName,\n rootCause: shortErrorMsg,\n decision: replanResponse.decision,\n reasoning: replanResponse.reasoning,\n missingPackage,\n },\n });\n // 실패한 스텝에서 생성된 셀 인덱스 추출 (재사용 위해)\n const failedCellIndex = stepResult.toolResults.find(r => r.cellIndex !== undefined)?.cellIndex;\n console.log('[Orchestrator] Failed cell index for reuse:', failedCellIndex);\n // Replan 결과에 따른 계획 수정 (실패한 셀 인덱스 전달)\n currentPlan = this.applyReplanChanges(currentPlan, stepIndex, replanResponse, failedCellIndex);\n // ★ 업데이트된 계획을 UI에 반영 (plan item list에 새 스텝 표시)\n onProgress({\n phase: 'planned',\n plan: currentPlan,\n message: `계획 수정됨 (${this.getReplanDecisionLabel(replanResponse.decision)})`,\n currentStep: step.stepNumber,\n totalSteps: currentPlan.totalSteps,\n });\n replanAttempts++;\n // stepIndex는 그대로 유지 (수정된 현재 단계를 다시 실행)\n continue;\n }\n catch (replanError) {\n console.error('[Orchestrator] Replan failed:', replanError);\n onProgress({\n phase: 'failed',\n message: `Step ${step.stepNumber} 실패 (재계획 실패): ${stepResult.error}`,\n });\n return {\n success: false,\n plan: currentPlan,\n executedSteps,\n createdCells,\n modifiedCells,\n error: `Step ${step.stepNumber} 실패: ${stepResult.error}`,\n totalAttempts: this.countTotalAttempts(executedSteps),\n executionTime: Date.now() - startTime,\n };\n }\n }\n // 성공한 단계 기록\n executedSteps.push(stepResult);\n replanAttempts = 0; // 성공 시 재계획 시도 횟수 리셋\n // ★ 성공한 Step에서 정의된 변수 추적 (cross-step validation용)\n this.trackVariablesFromStep(step);\n // ═══════════════════════════════════════════════════════════════════════\n // CHECKPOINT CREATION (Phase 3): 성공 스텝 후 체크포인트 저장\n // ═══════════════════════════════════════════════════════════════════════\n const newVars = this.extractVariablesFromCode(step.toolCalls\n .filter(tc => tc.tool === 'jupyter_cell')\n .map(tc => tc.parameters.code)\n .join('\\n'));\n this.checkpointManager.createCheckpoint(step.stepNumber, step.description, currentPlan, stepResult, newVars);\n console.log(`[Orchestrator] Checkpoint created for step ${step.stepNumber}`);\n // ═══════════════════════════════════════════════════════════════════════\n // STATE VERIFICATION (Phase 1): 상태 검증 레이어\n // ═══════════════════════════════════════════════════════════════════════\n if (this.enableStateVerification && stepResult.toolResults.length > 0) {\n const stateVerificationResult = await this.verifyStepStateAfterExecution(step, stepResult, onProgress);\n // 검증 결과에 따른 처리\n if (stateVerificationResult) {\n const { recommendation, confidence } = stateVerificationResult;\n console.log('[Orchestrator] State verification result:', {\n confidence,\n recommendation,\n isValid: stateVerificationResult.isValid,\n });\n // 권장 사항에 따른 액션\n if (recommendation === 'replan') {\n console.log('[Orchestrator] State verification recommends replan (confidence:', confidence, ')');\n // Replan을 위한 에러 상태로 전환 (다음 반복에서 replanning 트리거)\n onProgress({\n phase: 'replanning',\n message: `상태 검증 신뢰도 낮음: ${(confidence * 100).toFixed(0)}%`,\n currentStep: step.stepNumber,\n replanInfo: {\n errorType: 'StateVerificationFailed',\n rootCause: stateVerificationResult.mismatches.map(m => m.description).join('; '),\n },\n });\n // Note: 현재는 로깅만 수행, 향후 자동 replanning 트리거 구현\n }\n else if (recommendation === 'escalate') {\n console.log('[Orchestrator] State verification recommends escalation (confidence:', confidence, ')');\n onProgress({\n phase: 'failed',\n message: `상태 검증 실패 - 사용자 개입 필요: ${stateVerificationResult.mismatches.map(m => m.description).join(', ')}`,\n });\n // 심각한 상태 불일치 시 실행 중단\n return {\n success: false,\n plan: currentPlan,\n executedSteps,\n createdCells,\n modifiedCells,\n error: `상태 검증 실패 (신뢰도: ${(confidence * 100).toFixed(0)}%): ${stateVerificationResult.mismatches.map(m => m.description).join('; ')}`,\n totalAttempts: this.countTotalAttempts(executedSteps),\n executionTime: Date.now() - startTime,\n };\n }\n else if (recommendation === 'warning') {\n console.log('[Orchestrator] State verification warning (confidence:', confidence, ')');\n onProgress({\n phase: 'verifying',\n message: `상태 검증 경고: ${(confidence * 100).toFixed(0)}% 신뢰도`,\n currentStep: step.stepNumber,\n });\n }\n // 'proceed'는 별도 처리 없이 계속 진행\n }\n }\n // ═══════════════════════════════════════════════════════════════════════\n // REFLECTION: 실행 결과 분석 및 적응적 조정\n // ═══════════════════════════════════════════════════════════════════════\n if (this.enableReflection && stepResult.toolResults.length > 0) {\n // Reflection 시작 알림\n onProgress({\n phase: 'reflecting',\n reflectionStatus: 'analyzing',\n currentStep: step.stepNumber,\n message: '실행 결과 분석 중...',\n });\n const remainingSteps = currentPlan.steps.slice(stepIndex + 1);\n const reflection = await this.performReflection(step, stepResult.toolResults, remainingSteps);\n if (reflection) {\n const { shouldContinue, action, reason } = this.shouldContinueAfterReflection(reflection);\n console.log('[Orchestrator] Reflection decision:', { shouldContinue, action, reason });\n // Reflection 결과 UI 업데이트\n if (reflection.evaluation.checkpoint_passed) {\n onProgress({\n phase: 'reflecting',\n reflectionStatus: 'passed',\n currentStep: step.stepNumber,\n message: '검증 통과',\n });\n }\n else {\n onProgress({\n phase: 'reflecting',\n reflectionStatus: 'adjusting',\n currentStep: step.stepNumber,\n message: `조정 권고: ${reason}`,\n });\n }\n // Reflection 결과가 retry 또는 replan을 요구하면 해당 로직으로 이동\n if (!shouldContinue) {\n if (action === 'replan') {\n console.log('[Orchestrator] Reflection recommends replan, triggering adaptive replanning');\n }\n else if (action === 'retry') {\n console.log('[Orchestrator] Reflection recommends retry - but step succeeded, continuing');\n }\n }\n }\n }\n // 다음 스텝 전에 지연 적용 (사용자가 결과를 확인할 시간)\n await this.applyStepDelay();\n // final_answer 도구 호출 시 완료\n if (stepResult.isFinalAnswer) {\n onProgress({\n phase: 'completed',\n message: stepResult.finalAnswer || '작업 완료',\n });\n return {\n success: true,\n plan: currentPlan,\n executedSteps,\n createdCells,\n modifiedCells,\n finalAnswer: stepResult.finalAnswer,\n totalAttempts: this.countTotalAttempts(executedSteps),\n executionTime: Date.now() - startTime,\n };\n }\n stepIndex++;\n }\n // 모든 단계 성공\n onProgress({\n phase: 'completed',\n message: '모든 단계 성공적으로 완료',\n });\n return {\n success: true,\n plan,\n executedSteps,\n createdCells,\n modifiedCells,\n totalAttempts: this.countTotalAttempts(executedSteps),\n executionTime: Date.now() - startTime,\n };\n }\n catch (error) {\n console.log('[Orchestrator] Caught error in executeTask:', error);\n console.log('[Orchestrator] Error name:', error.name);\n console.log('[Orchestrator] Error message:', error.message);\n // FileSelectionError는 다시 throw해서 AutoAgentPanel이 처리하도록 함\n if (error.name === 'FileSelectionError') {\n console.log('[Orchestrator] Re-throwing FileSelectionError to AutoAgentPanel');\n this.isRunning = false;\n this.abortController = null;\n throw error;\n }\n onProgress({\n phase: 'failed',\n message: error.message || '알 수 없는 오류 발생',\n });\n return {\n success: false,\n plan: null,\n executedSteps,\n createdCells,\n modifiedCells,\n error: error.message || '알 수 없는 오류 발생',\n totalAttempts: this.countTotalAttempts(executedSteps),\n executionTime: Date.now() - startTime,\n };\n }\n finally {\n this.isRunning = false;\n this.abortController = null;\n }\n }\n /**\n * 출력 결과가 부정적인지 분석 (에러는 아니지만 실패 의미를 가진 출력)\n * Fast Fail: 모든 에러 → Adaptive Replanning으로 처리\n */\n analyzeOutputForFailure(output) {\n if (!output) {\n return { isNegative: false };\n }\n const negativePatterns = [\n // 환경/의존성 에러\n { pattern: /ModuleNotFoundError/i, reason: '모듈을 찾을 수 없음 (패키지 설치 필요)' },\n { pattern: /ImportError/i, reason: 'import 에러 (패키지 설치 필요)' },\n { pattern: /No module named/i, reason: '모듈이 없음 (패키지 설치 필요)' },\n { pattern: /cannot import name/i, reason: 'import 실패' },\n // 파일/경로 관련 오류\n { pattern: /FileNotFoundError|No such file or directory|파일을 찾을 수 없습니다/i, reason: '파일을 찾을 수 없음' },\n // 런타임 에러\n { pattern: /NameError:\\s*name\\s*'([^']+)'\\s*is not defined/i, reason: '변수가 정의되지 않음' },\n { pattern: /KeyError/i, reason: '키를 찾을 수 없음' },\n { pattern: /IndexError/i, reason: '인덱스 범위 초과' },\n { pattern: /TypeError/i, reason: '타입 오류' },\n { pattern: /ValueError/i, reason: '값 오류' },\n { pattern: /AttributeError/i, reason: '속성 오류' },\n // 데이터 검증 에러 (GridSearchCV, NaN/Inf 등)\n { pattern: /사전\\s*검증.*실패|사전\\s*검증.*오류|pre-?validation.*fail|validation.*fail/i, reason: '사전 검증 실패' },\n { pattern: /NaN.*값|Inf.*값|invalid value|contains NaN|contains Inf/i, reason: 'NaN 또는 Inf 값 감지' },\n { pattern: /수행되지\\s*않음|was not performed|did not execute|not executed/i, reason: '실행되지 않음' },\n { pattern: /fit.*fail|GridSearchCV.*fail|학습.*실패/i, reason: '모델 학습 실패' },\n // 명시적 실패 메시지\n { pattern: /실패|failed|Fail/i, reason: '명시적 오류 메시지 감지' },\n { pattern: /not found|cannot find|찾을 수 없/i, reason: '리소스를 찾을 수 없음' },\n // Note: Empty DataFrame, 0 rows 등은 정상적인 분석 결과일 수 있으므로 부정적 패턴에서 제외\n ];\n for (const { pattern, reason } of negativePatterns) {\n if (pattern.test(output)) {\n console.log('[Orchestrator] Negative output detected:', reason);\n return { isNegative: true, reason };\n }\n }\n return { isNegative: false };\n }\n /**\n * 단계 실행 (Fast Fail 방식)\n * 에러 발생 시 재시도 없이 바로 실패 반환 → Adaptive Replanning으로 처리\n * + Pre-Validation: 실행 전 Pyflakes/AST 검증\n */\n async executeStepWithRetry(step, notebook, onProgress) {\n console.log('[Orchestrator] executeStep called for step:', step.stepNumber);\n console.log('[Orchestrator] Step toolCalls:', JSON.stringify(step.toolCalls, null, 2));\n const toolResults = [];\n try {\n // Tool Calling 실행\n console.log('[Orchestrator] Processing', step.toolCalls.length, 'tool calls');\n for (const toolCall of step.toolCalls) {\n console.log('[Orchestrator] Processing toolCall:', toolCall.tool);\n // 중단 요청 확인\n if (this.abortController?.signal.aborted) {\n return {\n success: false,\n stepNumber: step.stepNumber,\n toolResults,\n attempts: 1,\n error: '사용자에 의해 취소됨',\n };\n }\n onProgress({\n phase: 'tool_calling',\n tool: toolCall.tool,\n attempt: 1,\n currentStep: step.stepNumber,\n });\n // jupyter_cell인 경우: 안전성 검사 + Pre-Validation\n if (toolCall.tool === 'jupyter_cell') {\n const params = toolCall.parameters;\n // 1. 안전성 검사\n const safetyResult = this.safetyChecker.checkCodeSafety(params.code);\n if (!safetyResult.safe) {\n return {\n success: false,\n stepNumber: step.stepNumber,\n toolResults,\n attempts: 1,\n error: `안전성 검사 실패: ${safetyResult.blockedPatterns?.join(', ')}`,\n };\n }\n // 2. Pre-Validation (Pyflakes/AST 기반)\n onProgress({\n phase: 'validating',\n validationStatus: 'checking',\n currentStep: step.stepNumber,\n message: '코드 품질 검증 중...',\n });\n const validation = await this.validateCodeBeforeExecution(params.code);\n if (validation) {\n if (validation.hasErrors) {\n // ★ 디버깅용 상세 로그 (robust version)\n console.log('');\n console.log('╔══════════════════════════════════════════════════════════════╗');\n console.log('║ 🔴 [Orchestrator] PRE-VALIDATION FAILED ║');\n console.log('╠══════════════════════════════════════════════════════════════╣');\n console.log(`║ Step: ${step.stepNumber} - ${step.description?.substring(0, 45) || 'N/A'}...`);\n console.log(`║ Summary: ${validation.summary || 'No summary'}`);\n console.log('╠══════════════════════════════════════════════════════════════╣');\n console.log('║ Code:');\n console.log('║ ' + (params.code || '').split('\\n').join('\\n║ '));\n console.log('╠══════════════════════════════════════════════════════════════╣');\n console.log('║ Issues:');\n if (validation.issues && validation.issues.length > 0) {\n validation.issues.forEach((issue, idx) => {\n console.log(`║ ${idx + 1}. [${issue.severity || 'unknown'}] ${issue.category || 'unknown'}: ${issue.message || 'no message'}`);\n if (issue.line)\n console.log(`║ Line ${issue.line}${issue.column ? `:${issue.column}` : ''}`);\n if (issue.code_snippet)\n console.log(`║ Snippet: ${issue.code_snippet}`);\n });\n }\n else {\n console.log('║ (No issues array available)');\n }\n if (validation.dependencies) {\n console.log('╠══════════════════════════════════════════════════════════════╣');\n console.log('║ Dependencies:', JSON.stringify(validation.dependencies));\n }\n console.log('╚══════════════════════════════════════════════════════════════╝');\n console.log('');\n onProgress({\n phase: 'validating',\n validationStatus: 'failed',\n currentStep: step.stepNumber,\n message: `검증 실패: ${validation.summary}`,\n });\n // Fast Fail: 검증 오류 → 바로 Adaptive Replanning\n return {\n success: false,\n stepNumber: step.stepNumber,\n toolResults,\n attempts: 1,\n error: `사전 검증 오류: ${validation.summary}`,\n };\n }\n else if (validation.hasWarnings) {\n console.log('[Orchestrator] ⚠️ Pre-validation warnings:', validation.issues.map(i => i.message).join('; '));\n onProgress({\n phase: 'validating',\n validationStatus: 'warning',\n currentStep: step.stepNumber,\n message: `경고 감지: ${validation.issues.length}건 (실행 계속)`,\n });\n }\n else {\n console.log('[Orchestrator] ✅ Pre-validation passed for step', step.stepNumber);\n onProgress({\n phase: 'validating',\n validationStatus: 'passed',\n currentStep: step.stepNumber,\n message: '코드 검증 통과',\n });\n }\n }\n }\n // 타임아웃과 함께 실행 (stepNumber 전달)\n console.log('[Orchestrator] Calling toolExecutor.executeTool for:', toolCall.tool, 'step:', step.stepNumber);\n const result = await this.executeWithTimeout(() => this.toolExecutor.executeTool(toolCall, step.stepNumber), this.config.executionTimeout);\n console.log('[Orchestrator] Tool execution result:', JSON.stringify(result));\n toolResults.push(result);\n // jupyter_cell 실행 실패 시 → Fast Fail\n if (!result.success && toolCall.tool === 'jupyter_cell') {\n const errorMsg = result.error || '알 수 없는 오류';\n console.log('[Orchestrator] jupyter_cell execution failed:', errorMsg.substring(0, 100));\n return {\n success: false,\n stepNumber: step.stepNumber,\n toolResults,\n attempts: 1,\n error: errorMsg,\n };\n }\n // final_answer 도구 감지\n if (toolCall.tool === 'final_answer') {\n let finalAnswerText = result.output;\n // ★ 변수 값 추출 및 치환\n try {\n // finalAnswer에서 {변수명} 패턴 찾기\n const varPattern = /\\{(\\w+)\\}/g;\n const matches = [...finalAnswerText.matchAll(varPattern)];\n const varNames = [...new Set(matches.map(m => m[1]))];\n if (varNames.length > 0) {\n console.log('[Orchestrator] Extracting variable values for finalAnswer:', varNames);\n // Jupyter kernel에서 변수 값 추출\n const variableValues = await this.toolExecutor.getVariableValues(varNames);\n console.log('[Orchestrator] Extracted variable values:', variableValues);\n // 변수 치환\n finalAnswerText = finalAnswerText.replace(varPattern, (match, varName) => {\n return variableValues[varName] ?? match;\n });\n console.log('[Orchestrator] Final answer after substitution:', finalAnswerText);\n }\n }\n catch (error) {\n console.error('[Orchestrator] Failed to substitute variables in finalAnswer:', error);\n // 실패해도 원본 텍스트 사용\n }\n return {\n success: true,\n stepNumber: step.stepNumber,\n toolResults,\n attempts: 1,\n isFinalAnswer: true,\n finalAnswer: finalAnswerText,\n };\n }\n }\n // 모든 도구 실행 성공\n if (toolResults.length > 0 && toolResults.every((r) => r.success)) {\n // 출력 결과 분석: 실행은 성공했지만 출력이 부정적인 경우\n const allOutputs = toolResults\n .map((r) => {\n const output = r.output;\n if (!output)\n return '';\n if (typeof output === 'string')\n return output;\n if (typeof output === 'object' && output !== null) {\n // Jupyter output format: {text/plain: ..., text/html: ...}\n if ('text/plain' in output) {\n const textPlain = output['text/plain'];\n return typeof textPlain === 'string' ? textPlain : String(textPlain || '');\n }\n try {\n return JSON.stringify(output);\n }\n catch {\n return '[object]';\n }\n }\n // Primitive types (number, boolean, etc.)\n try {\n return String(output);\n }\n catch {\n return '[unknown]';\n }\n })\n .join('\\n');\n const outputAnalysis = this.analyzeOutputForFailure(allOutputs);\n if (outputAnalysis.isNegative) {\n console.log('[Orchestrator] Negative output detected:', outputAnalysis.reason);\n // Fast Fail: 부정적 출력 → 바로 Adaptive Replanning\n return {\n success: false,\n stepNumber: step.stepNumber,\n toolResults,\n attempts: 1,\n error: outputAnalysis.reason || '출력 결과에서 문제 감지',\n };\n }\n return {\n success: true,\n stepNumber: step.stepNumber,\n toolResults,\n attempts: 1,\n };\n }\n // 도구 실행 결과가 없는 경우\n // Use the first tool's error message if available\n const firstToolError = toolResults.find(r => r.error)?.error;\n return {\n success: false,\n stepNumber: step.stepNumber,\n toolResults,\n attempts: 1,\n error: firstToolError || '도구 실행 결과 없음',\n };\n }\n catch (error) {\n const isTimeout = error.message?.includes('timeout');\n if (isTimeout) {\n // 타임아웃 시 커널 인터럽트\n await this.toolExecutor.interruptKernel();\n }\n return {\n success: false,\n stepNumber: step.stepNumber,\n toolResults,\n attempts: 1,\n error: error.message || '알 수 없는 오류',\n };\n }\n }\n /**\n * 노트북 컨텍스트 추출\n * ★ Phase 2: ContextManager를 사용하여 토큰 예산 내에서 최적화된 컨텍스트 반환\n */\n extractNotebookContext(notebook, currentCellIndex) {\n const cells = notebook.content.model?.cells;\n const cellCount = cells?.length || 0;\n // 모든 셀 정보 추출 (ContextManager가 우선순위에 따라 필터링)\n const allCells = [];\n if (cells) {\n for (let i = 0; i < cellCount; i++) {\n const cell = cells.get(i);\n allCells.push({\n index: i,\n type: cell.type,\n source: cell.sharedModel.getSource(),\n output: this.toolExecutor.getCellOutput(i),\n });\n }\n }\n const rawContext = {\n cellCount,\n recentCells: allCells,\n importedLibraries: this.detectImportedLibraries(notebook),\n definedVariables: this.detectDefinedVariables(notebook),\n notebookPath: notebook.context?.path,\n };\n // ★ ContextManager를 통한 토큰 예산 기반 최적화\n const { context: optimizedContext, usage, pruneResult } = this.contextManager.extractOptimizedContext(rawContext, currentCellIndex);\n // 로깅\n if (pruneResult) {\n console.log(`[Orchestrator] Context optimized: ${pruneResult.originalTokens} → ${pruneResult.prunedTokens} tokens ` +\n `(removed: ${pruneResult.removedCellCount}, truncated: ${pruneResult.truncatedCellCount})`);\n }\n else {\n console.log(`[Orchestrator] Context usage: ${usage.totalTokens} tokens (${(usage.usagePercent * 100).toFixed(1)}%)`);\n }\n return optimizedContext;\n }\n /**\n * 원본 노트북 컨텍스트 추출 (최적화 없이)\n * ★ Phase 2: 디버깅/테스트용\n */\n extractRawNotebookContext(notebook) {\n const cells = notebook.content.model?.cells;\n const cellCount = cells?.length || 0;\n const recentCells = [];\n if (cells) {\n const startIndex = Math.max(0, cellCount - 3);\n for (let i = startIndex; i < cellCount; i++) {\n const cell = cells.get(i);\n recentCells.push({\n index: i,\n type: cell.type,\n source: cell.sharedModel.getSource().slice(0, 500),\n output: this.toolExecutor.getCellOutput(i).slice(0, 300),\n });\n }\n }\n return {\n cellCount,\n recentCells,\n importedLibraries: this.detectImportedLibraries(notebook),\n definedVariables: this.detectDefinedVariables(notebook),\n notebookPath: notebook.context?.path,\n };\n }\n /**\n * import된 라이브러리 감지\n */\n detectImportedLibraries(notebook) {\n const libraries = new Set();\n const cells = notebook.content.model?.cells;\n if (!cells)\n return [];\n for (let i = 0; i < cells.length; i++) {\n const cell = cells.get(i);\n if (cell.type === 'code') {\n const source = cell.sharedModel.getSource();\n // import xxx 패턴\n const importMatches = source.matchAll(/^import\\s+(\\w+)/gm);\n for (const match of importMatches) {\n libraries.add(match[1]);\n }\n // from xxx import 패턴\n const fromMatches = source.matchAll(/^from\\s+(\\w+)/gm);\n for (const match of fromMatches) {\n libraries.add(match[1]);\n }\n }\n }\n return Array.from(libraries);\n }\n /**\n * 정의된 변수 감지\n *\n * ★ 수정: 인덴트된 코드 (try/except, with, if 블록 내부)에서도 변수 감지\n * 기존 regex: /^(\\w+)\\s*=/gm - 라인 시작에서만 매칭\n * 수정 regex: /^[ \\t]*(\\w+)\\s*=/gm - 인덴트된 할당문도 매칭\n */\n detectDefinedVariables(notebook) {\n const variables = new Set();\n const cells = notebook.content.model?.cells;\n if (!cells)\n return [];\n for (let i = 0; i < cells.length; i++) {\n const cell = cells.get(i);\n if (cell.type === 'code') {\n const source = cell.sharedModel.getSource();\n // ★ 인덴트 허용하는 할당 패턴: [공백/탭]* variable = ...\n const assignMatches = source.matchAll(/^[ \\t]*(\\w+)\\s*=/gm);\n for (const match of assignMatches) {\n // 예약어 및 비교 연산자 제외\n // == 는 비교연산자이므로 제외 (예: if x == 1)\n const varName = match[1];\n if (!['if', 'for', 'while', 'def', 'class', 'import', 'from', 'elif', 'return', 'yield', 'assert', 'raise', 'del', 'pass', 'break', 'continue', 'global', 'nonlocal', 'lambda', 'with', 'as', 'try', 'except', 'finally'].includes(varName)) {\n // == 비교 연산자 체크 (matchAll 결과에서 원래 문자열 확인)\n const fullMatch = match[0];\n if (!fullMatch.includes('==') && !fullMatch.includes('!=') && !fullMatch.includes('<=') && !fullMatch.includes('>=')) {\n variables.add(varName);\n }\n }\n }\n // ★ 추가: 튜플 언패킹 패턴도 감지 (예: x, y = func())\n const tupleMatches = source.matchAll(/^[ \\t]*(\\w+(?:\\s*,\\s*\\w+)+)\\s*=/gm);\n for (const match of tupleMatches) {\n const tupleVars = match[1].split(',').map(v => v.trim());\n for (const varName of tupleVars) {\n if (varName && /^\\w+$/.test(varName)) {\n variables.add(varName);\n }\n }\n }\n // ★ 추가: for 루프 변수 감지 (예: for x in items:, for i, v in enumerate())\n const forMatches = source.matchAll(/^[ \\t]*for\\s+(\\w+(?:\\s*,\\s*\\w+)*)\\s+in\\s+/gm);\n for (const match of forMatches) {\n const loopVars = match[1].split(',').map(v => v.trim());\n for (const varName of loopVars) {\n if (varName && /^\\w+$/.test(varName)) {\n variables.add(varName);\n }\n }\n }\n // ★ 추가: with 문 변수 감지 (예: with open() as f:)\n const withMatches = source.matchAll(/^[ \\t]*with\\s+.*\\s+as\\s+(\\w+)/gm);\n for (const match of withMatches) {\n variables.add(match[1]);\n }\n // ★ 추가: except 문 변수 감지 (예: except Exception as e:)\n const exceptMatches = source.matchAll(/^[ \\t]*except\\s+.*\\s+as\\s+(\\w+)/gm);\n for (const match of exceptMatches) {\n variables.add(match[1]);\n }\n }\n }\n return Array.from(variables);\n }\n /**\n * ★ 단일 코드 문자열에서 정의된 변수 추출\n * detectDefinedVariables와 동일한 로직을 단일 코드 블록에 적용\n */\n extractVariablesFromCode(code) {\n const variables = new Set();\n const reservedWords = ['if', 'for', 'while', 'def', 'class', 'import', 'from', 'elif', 'return', 'yield', 'assert', 'raise', 'del', 'pass', 'break', 'continue', 'global', 'nonlocal', 'lambda', 'with', 'as', 'try', 'except', 'finally'];\n // 인덴트 허용하는 할당 패턴\n const assignMatches = code.matchAll(/^[ \\t]*(\\w+)\\s*=/gm);\n for (const match of assignMatches) {\n const varName = match[1];\n if (!reservedWords.includes(varName)) {\n const fullMatch = match[0];\n if (!fullMatch.includes('==') && !fullMatch.includes('!=') && !fullMatch.includes('<=') && !fullMatch.includes('>=')) {\n variables.add(varName);\n }\n }\n }\n // 튜플 언패킹\n const tupleMatches = code.matchAll(/^[ \\t]*(\\w+(?:\\s*,\\s*\\w+)+)\\s*=/gm);\n for (const match of tupleMatches) {\n const tupleVars = match[1].split(',').map(v => v.trim());\n for (const varName of tupleVars) {\n if (varName && /^\\w+$/.test(varName)) {\n variables.add(varName);\n }\n }\n }\n // for 루프 변수\n const forMatches = code.matchAll(/^[ \\t]*for\\s+(\\w+(?:\\s*,\\s*\\w+)*)\\s+in\\s+/gm);\n for (const match of forMatches) {\n const loopVars = match[1].split(',').map(v => v.trim());\n for (const varName of loopVars) {\n if (varName && /^\\w+$/.test(varName)) {\n variables.add(varName);\n }\n }\n }\n // with 문 변수\n const withMatches = code.matchAll(/^[ \\t]*with\\s+.*\\s+as\\s+(\\w+)/gm);\n for (const match of withMatches) {\n variables.add(match[1]);\n }\n // except 문 변수\n const exceptMatches = code.matchAll(/^[ \\t]*except\\s+.*\\s+as\\s+(\\w+)/gm);\n for (const match of exceptMatches) {\n variables.add(match[1]);\n }\n return Array.from(variables);\n }\n /**\n * ★ 코드에서 import된 이름들 추출\n * - import xxx → xxx\n * - import xxx as yyy → yyy\n * - from xxx import yyy → yyy\n * - from xxx import yyy as zzz → zzz\n * - from xxx import * 는 제외 (추적 불가)\n */\n extractImportsFromCode(code) {\n const imports = new Set();\n // import xxx 또는 import xxx as yyy\n const importMatches = code.matchAll(/^[ \\t]*import\\s+([\\w.]+)(?:\\s+as\\s+(\\w+))?/gm);\n for (const match of importMatches) {\n const asName = match[2]; // as 별칭\n const name = match[1]; // 원래 이름\n if (asName) {\n imports.add(asName);\n }\n else {\n // import pandas.DataFrame 같은 경우 pandas만 추출\n imports.add(name.split('.')[0]);\n }\n }\n // from xxx import yyy, zzz 또는 from xxx import yyy as aaa\n const fromImportMatches = code.matchAll(/^[ \\t]*from\\s+[\\w.]+\\s+import\\s+(.+)$/gm);\n for (const match of fromImportMatches) {\n const importList = match[1];\n if (importList.trim() === '*')\n continue; // from xxx import * 제외\n // 여러 import 처리 (예: from sklearn import train_test_split, cross_val_score)\n const items = importList.split(',');\n for (const item of items) {\n const trimmed = item.trim();\n // as 별칭이 있는 경우: yyy as zzz\n const asMatch = trimmed.match(/^(\\w+)\\s+as\\s+(\\w+)$/);\n if (asMatch) {\n imports.add(asMatch[2]); // 별칭 사용\n }\n else if (/^\\w+$/.test(trimmed)) {\n imports.add(trimmed);\n }\n }\n }\n return Array.from(imports);\n }\n /**\n * ★ 실행된 Step의 코드에서 변수와 import를 추적\n */\n trackVariablesFromStep(step) {\n for (const toolCall of step.toolCalls) {\n if (toolCall.tool === 'jupyter_cell') {\n const params = toolCall.parameters;\n // 변수 추적\n const vars = this.extractVariablesFromCode(params.code);\n vars.forEach(v => this.executedStepVariables.add(v));\n // ★ import 추적\n const imports = this.extractImportsFromCode(params.code);\n imports.forEach(i => this.executedStepImports.add(i));\n console.log('[Orchestrator] Tracked from step', step.stepNumber, '- vars:', vars, 'imports:', imports);\n }\n }\n }\n /**\n * Adaptive Replanning 결과 적용\n *\n * ★ 순차 실행 원칙: 모든 새 셀은 항상 맨 끝에 추가됨\n * - 기존 셀 재사용(cellIndex 주입) 제거 → 항상 새 셀 생성\n * - 이렇게 하면 셀이 항상 위에서 아래로 순서대로 추가됨\n */\n applyReplanChanges(plan, currentStepIndex, replanResponse, failedCellIndex // 이제 사용하지 않음 (API 호환성 유지)\n ) {\n const { decision, changes } = replanResponse;\n const steps = [...plan.steps];\n const currentStep = steps[currentStepIndex];\n console.log('[Orchestrator] Applying replan changes:', decision, '(always append new cells)');\n switch (decision) {\n case 'refine':\n // 현재 단계의 코드만 수정 - 기존 셀 수정 (MODIFY 작업)\n if (changes.refined_code) {\n const newToolCalls = currentStep.toolCalls.map(tc => {\n if (tc.tool === 'jupyter_cell') {\n const params = tc.parameters;\n return {\n ...tc,\n parameters: {\n ...params,\n code: changes.refined_code,\n // ★ cellIndex 보존: 기존 셀 또는 실패한 셀을 수정\n cellIndex: params.cellIndex ?? failedCellIndex,\n operation: 'MODIFY',\n },\n };\n }\n return tc;\n });\n steps[currentStepIndex] = {\n ...currentStep,\n toolCalls: newToolCalls,\n wasReplanned: true,\n cellOperation: 'MODIFY',\n targetCellIndex: failedCellIndex,\n };\n }\n break;\n case 'insert_steps':\n // 새로운 단계들을 현재 위치에 삽입 (예: pip install)\n // 실행 시 모든 셀은 순차적으로 맨 끝에 추가됨\n if (changes.new_steps && changes.new_steps.length > 0) {\n const newSteps = changes.new_steps.map((newStep, idx) => ({\n ...newStep,\n stepNumber: currentStep.stepNumber + idx * 0.1,\n isNew: true,\n cellOperation: 'CREATE', // 새 셀 생성\n }));\n steps.splice(currentStepIndex, 0, ...newSteps);\n // 단계 번호 재정렬\n steps.forEach((step, idx) => {\n step.stepNumber = idx + 1;\n });\n }\n break;\n case 'replace_step':\n // 현재 단계를 완전히 교체 - 새 셀 생성\n if (changes.replacement) {\n const replacementStep = {\n ...changes.replacement,\n stepNumber: currentStep.stepNumber,\n toolCalls: changes.replacement.toolCalls || [],\n isReplaced: true,\n cellOperation: 'CREATE', // 새 셀 생성\n };\n // cellIndex 속성 명시적 제거 (새 셀 생성)\n replacementStep.toolCalls.forEach(tc => {\n if (tc.tool === 'jupyter_cell' && tc.parameters) {\n delete tc.parameters.cellIndex;\n tc.parameters.operation = 'CREATE';\n }\n });\n steps[currentStepIndex] = replacementStep;\n }\n break;\n case 'replan_remaining':\n // 현재 단계부터 끝까지 새로운 계획으로 교체\n if (changes.new_plan && changes.new_plan.length > 0) {\n const existingSteps = steps.slice(0, currentStepIndex);\n const newPlanSteps = changes.new_plan.map((newStep, idx) => ({\n ...newStep,\n stepNumber: currentStepIndex + idx + 1,\n isNew: true,\n cellOperation: 'CREATE', // 새 셀 생성\n }));\n // 새 계획에 final_answer가 없으면 경고 로그\n const hasFinalAnswer = newPlanSteps.some(step => step.toolCalls?.some((tc) => tc.tool === 'final_answer'));\n if (!hasFinalAnswer) {\n console.warn('[Orchestrator] replan_remaining: new_plan does not include final_answer');\n }\n steps.length = 0;\n steps.push(...existingSteps, ...newPlanSteps);\n }\n break;\n }\n return {\n ...plan,\n steps,\n totalSteps: steps.length,\n };\n }\n /**\n * 총 시도 횟수 계산\n */\n countTotalAttempts(steps) {\n return steps.reduce((sum, step) => sum + step.attempts, 0);\n }\n /**\n * 타임아웃 래퍼\n */\n async executeWithTimeout(fn, timeoutMs) {\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n reject(new Error(`실행 타임아웃 (${timeoutMs}ms)`));\n }, timeoutMs);\n fn()\n .then((result) => {\n clearTimeout(timeoutId);\n resolve(result);\n })\n .catch((error) => {\n clearTimeout(timeoutId);\n reject(error);\n });\n });\n }\n /**\n * 지연 유틸리티\n */\n delay(ms) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n /**\n * 실행 취소\n */\n cancel() {\n if (this.abortController) {\n this.abortController.abort();\n }\n }\n /**\n * 실행 상태 확인\n */\n getIsRunning() {\n return this.isRunning;\n }\n /**\n * 설정 업데이트\n */\n updateConfig(config) {\n this.config = { ...this.config, ...config };\n this.safetyChecker.updateConfig({\n enableSafetyCheck: this.config.enableSafetyCheck,\n maxExecutionTime: this.config.executionTimeout / 1000,\n });\n // ToolExecutor 자동 스크롤 설정 동기화\n if (config.autoScrollToCell !== undefined) {\n this.toolExecutor.setAutoScroll(this.config.autoScrollToCell);\n }\n }\n /**\n * 승인 콜백 설정 (Tool Registry 연동)\n * @param callback 승인 요청 시 호출될 콜백 함수\n */\n setApprovalCallback(callback) {\n this.toolExecutor.setApprovalCallback(callback);\n }\n /**\n * 승인 필요 여부 설정\n * @param required true면 위험 도구 실행 전 승인 필요\n */\n setApprovalRequired(required) {\n this.toolExecutor.setApprovalRequired(required);\n }\n /**\n * Tool Registry 인스턴스 반환 (외부 도구 등록용)\n */\n getToolRegistry() {\n return this.toolExecutor.getRegistry();\n }\n /**\n * 현재 실행 중인 셀로 스크롤 및 하이라이트\n */\n scrollToAndHighlightCell(cellIndex) {\n if (!this.config.autoScrollToCell && !this.config.highlightCurrentCell) {\n return;\n }\n const notebookContent = this.notebook.content;\n const cell = notebookContent.widgets[cellIndex];\n if (!cell)\n return;\n // 셀로 스크롤\n if (this.config.autoScrollToCell) {\n cell.node.scrollIntoView({\n behavior: 'smooth',\n block: 'center',\n });\n }\n // 셀 하이라이트 (CSS 클래스 추가)\n if (this.config.highlightCurrentCell) {\n // 기존 하이라이트 제거\n notebookContent.widgets.forEach((w) => {\n w.node.classList.remove('aa-cell-executing');\n });\n // 새 하이라이트 추가\n cell.node.classList.add('aa-cell-executing');\n // 실행 완료 후 하이라이트 제거 (지연 후)\n setTimeout(() => {\n cell.node.classList.remove('aa-cell-executing');\n }, this.getEffectiveDelay() + 500);\n }\n }\n /**\n * 현재 설정에 맞는 지연 시간 반환\n */\n getEffectiveDelay() {\n // executionSpeed 프리셋 사용 (stepDelay는 무시하고 프리셋 값을 우선)\n const presetDelay = EXECUTION_SPEED_DELAYS[this.config.executionSpeed];\n if (presetDelay !== undefined) {\n return presetDelay;\n }\n // 프리셋이 없으면 stepDelay 사용\n return this.config.stepDelay || 0;\n }\n /**\n * 스텝 사이 지연 적용 (step-by-step 모드 포함)\n */\n async applyStepDelay() {\n const delay = this.getEffectiveDelay();\n console.log(`[Orchestrator] applyStepDelay called: speed=${this.config.executionSpeed}, delay=${delay}ms`);\n // step-by-step 모드: 사용자가 다음 버튼을 누를 때까지 대기\n if (this.config.executionSpeed === 'step-by-step' || delay < 0) {\n this.isPaused = true;\n await new Promise((resolve) => {\n this.stepByStepResolver = resolve;\n });\n this.isPaused = false;\n this.stepByStepResolver = null;\n return;\n }\n // 일반 지연\n if (delay > 0) {\n await this.delay(delay);\n }\n }\n /**\n * Step-by-step 모드에서 다음 스텝 진행\n */\n proceedToNextStep() {\n if (this.stepByStepResolver) {\n this.stepByStepResolver();\n }\n }\n /**\n * 일시 정지 상태 확인\n */\n getIsPaused() {\n return this.isPaused;\n }\n /**\n * 현재 실행 속도 설정 반환\n */\n getExecutionSpeed() {\n return this.config.executionSpeed;\n }\n // ═══════════════════════════════════════════════════════════════════════════\n // Code Validation & Reflection Methods\n // ═══════════════════════════════════════════════════════════════════════════\n /**\n * 실행 전 코드 검증 (Pyflakes/AST 기반)\n *\n * @param code 검증할 코드\n * @returns 검증 결과\n */\n async validateCodeBeforeExecution(code) {\n if (!this.enablePreValidation) {\n return null;\n }\n try {\n console.log('[Orchestrator] Pre-validation: Checking code quality');\n const notebookContext = this.extractNotebookContext(this.notebook);\n // ★ 이전 Step에서 추적된 변수들을 notebookContext에 병합\n const allDefinedVariables = new Set([\n ...notebookContext.definedVariables,\n ...this.executedStepVariables,\n ]);\n notebookContext.definedVariables = Array.from(allDefinedVariables);\n // ★ 이전 Step에서 추적된 import들을 notebookContext에 병합\n const allImportedLibraries = new Set([\n ...notebookContext.importedLibraries,\n ...this.executedStepImports,\n ]);\n notebookContext.importedLibraries = Array.from(allImportedLibraries);\n console.log('[Orchestrator] Validation context - tracked vars:', Array.from(this.executedStepVariables));\n console.log('[Orchestrator] Validation context - tracked imports:', Array.from(this.executedStepImports));\n const validationResult = await this.apiService.validateCode({\n code,\n notebookContext,\n });\n console.log('[Orchestrator] Validation result:', {\n valid: validationResult.valid,\n hasErrors: validationResult.hasErrors,\n issueCount: validationResult.issues.length,\n });\n return validationResult;\n }\n catch (error) {\n console.warn('[Orchestrator] Pre-validation failed:', error.message);\n // 검증 실패 시에도 실행은 계속 진행 (graceful degradation)\n return null;\n }\n }\n /**\n * 실행 후 Reflection 수행\n *\n * @param step 실행된 단계\n * @param toolResults 도구 실행 결과\n * @param remainingSteps 남은 단계들\n * @returns Reflection 결과\n */\n async performReflection(step, toolResults, remainingSteps) {\n if (!this.enableReflection) {\n return null;\n }\n try {\n // jupyter_cell 실행 결과 추출\n const jupyterResult = toolResults.find(r => r.cellIndex !== undefined);\n if (!jupyterResult) {\n return null;\n }\n // 실행된 코드 추출\n const jupyterToolCall = step.toolCalls.find(tc => tc.tool === 'jupyter_cell');\n const executedCode = jupyterToolCall\n ? jupyterToolCall.parameters.code\n : '';\n // Checkpoint 정보 추출 (EnhancedPlanStep인 경우)\n const enhancedStep = step;\n const checkpoint = enhancedStep.checkpoint;\n // ★ 프론트엔드에서 먼저 출력 분석 수행\n const outputString = (() => {\n const output = jupyterResult.output;\n if (!output)\n return '';\n if (typeof output === 'string')\n return output;\n if (typeof output === 'object' && output !== null) {\n if ('text/plain' in output) {\n const textPlain = output['text/plain'];\n return typeof textPlain === 'string' ? textPlain : String(textPlain || '');\n }\n try {\n return JSON.stringify(output);\n }\n catch {\n return '[object]';\n }\n }\n // Primitive types (number, boolean, etc.)\n try {\n return String(output);\n }\n catch {\n return '[unknown]';\n }\n })();\n const localOutputAnalysis = this.analyzeOutputForFailure(outputString);\n // 부정적 출력이 감지되면 에러 메시지에 추가\n let effectiveErrorMessage = jupyterResult.error;\n let effectiveStatus = jupyterResult.success ? 'ok' : 'error';\n if (localOutputAnalysis.isNegative) {\n console.log('[Orchestrator] Reflection: Local output analysis detected issue:', localOutputAnalysis.reason);\n effectiveErrorMessage = effectiveErrorMessage\n ? `${effectiveErrorMessage}; 출력 분석: ${localOutputAnalysis.reason}`\n : `출력 분석: ${localOutputAnalysis.reason}`;\n // 실행은 성공했지만 출력이 부정적인 경우도 'error'로 표시\n if (jupyterResult.success && localOutputAnalysis.isNegative) {\n effectiveStatus = 'warning'; // 백엔드에 경고 상태 전달\n }\n }\n console.log('[Orchestrator] Performing reflection for step', step.stepNumber);\n const reflectResponse = await this.apiService.reflectOnExecution({\n stepNumber: step.stepNumber,\n stepDescription: step.description,\n executedCode,\n executionStatus: effectiveStatus,\n executionOutput: outputString,\n errorMessage: effectiveErrorMessage,\n expectedOutcome: checkpoint?.expectedOutcome,\n validationCriteria: checkpoint?.validationCriteria,\n remainingSteps,\n });\n console.log('[Orchestrator] Reflection result:', {\n checkpointPassed: reflectResponse.reflection.evaluation.checkpoint_passed,\n confidenceScore: reflectResponse.reflection.evaluation.confidence_score,\n action: reflectResponse.reflection.recommendations.action,\n });\n return reflectResponse.reflection;\n }\n catch (error) {\n console.warn('[Orchestrator] Reflection failed:', error.message);\n // Reflection 실패 시에도 실행은 계속 진행\n return null;\n }\n }\n /**\n * Reflection 결과에 따른 액션 결정\n *\n * @param reflection Reflection 결과\n * @returns true면 계속 진행, false면 재시도/재계획 필요\n */\n shouldContinueAfterReflection(reflection) {\n if (!reflection) {\n return { shouldContinue: true, action: 'continue', reason: 'No reflection data' };\n }\n const { evaluation, recommendations } = reflection;\n // Checkpoint 통과 및 신뢰도 70% 이상이면 계속 진행\n if (evaluation.checkpoint_passed && evaluation.confidence_score >= 0.7) {\n return {\n shouldContinue: true,\n action: 'continue',\n reason: 'Checkpoint passed with high confidence'\n };\n }\n // Recommendation에 따른 결정\n switch (recommendations.action) {\n case 'continue':\n return {\n shouldContinue: true,\n action: 'continue',\n reason: recommendations.reasoning\n };\n case 'adjust':\n // 경미한 조정은 계속 진행 (다음 단계에서 보정)\n return {\n shouldContinue: true,\n action: 'adjust',\n reason: recommendations.reasoning\n };\n case 'retry':\n return {\n shouldContinue: false,\n action: 'retry',\n reason: recommendations.reasoning\n };\n case 'replan':\n return {\n shouldContinue: false,\n action: 'replan',\n reason: recommendations.reasoning\n };\n default:\n return { shouldContinue: true, action: 'continue', reason: 'Default continue' };\n }\n }\n /**\n * 검증 결과에 따른 코드 자동 수정 시도\n *\n * @param code 원본 코드\n * @param validation 검증 결과\n * @returns 수정된 코드 (수정 불가 시 null)\n */\n async attemptAutoFix(code, validation) {\n // 자동 수정 가능한 경우만 처리\n const autoFixableIssues = validation.issues.filter(issue => issue.category === 'unused_import' || issue.category === 'unused_variable');\n // 심각한 오류가 있으면 자동 수정 불가\n if (validation.hasErrors) {\n return null;\n }\n // 경고만 있는 경우 코드 그대로 반환 (실행 가능)\n if (!validation.hasErrors && autoFixableIssues.length > 0) {\n console.log('[Orchestrator] Code has warnings but is executable');\n return code;\n }\n return null;\n }\n // ═══════════════════════════════════════════════════════════════════════════\n // STATE VERIFICATION Methods (Phase 1)\n // ═══════════════════════════════════════════════════════════════════════════\n /**\n * 스텝 실행 후 상태 검증\n * @param step 실행된 스텝\n * @param stepResult 스텝 실행 결과\n * @param onProgress 진행 상태 콜백\n * @returns 상태 검증 결과 또는 null (검증 불가 시)\n */\n async verifyStepStateAfterExecution(step, stepResult, onProgress) {\n // jupyter_cell 실행 결과만 검증\n const jupyterResult = stepResult.toolResults.find(r => r.cellIndex !== undefined);\n if (!jupyterResult) {\n return null;\n }\n // Progress 업데이트: 검증 시작\n onProgress({\n phase: 'verifying',\n message: '상태 검증 중...',\n currentStep: step.stepNumber,\n });\n try {\n // 실행된 코드 추출\n const jupyterToolCall = step.toolCalls.find(tc => tc.tool === 'jupyter_cell');\n const executedCode = jupyterToolCall\n ? jupyterToolCall.parameters.code\n : '';\n // 출력 문자열 생성\n const outputString = this.extractOutputString(jupyterResult.output);\n // 노트북 컨텍스트에서 현재 변수 목록 추출\n const notebookContext = this.extractNotebookContext(this.notebook);\n // 실행 결과 객체 생성 (StateVerifier 인터페이스에 맞춤)\n const executionResult = {\n status: jupyterResult.success ? 'ok' : 'error',\n stdout: outputString,\n stderr: '',\n result: jupyterResult.output ? String(jupyterResult.output) : '',\n error: jupyterResult.error ? {\n ename: jupyterResult.errorName || 'Error',\n evalue: jupyterResult.error,\n traceback: jupyterResult.traceback || [],\n } : undefined,\n executionTime: 0,\n cellIndex: jupyterResult.cellIndex ?? -1,\n };\n // 상태 기대 정보 추출 (step.checkpoint 또는 expectedOutcome이 있는 경우)\n const enhancedStep = step;\n const expectation = enhancedStep.checkpoint\n ? {\n stepNumber: step.stepNumber,\n expectedVariables: this.extractVariablesFromCode(executedCode),\n expectedOutputPatterns: enhancedStep.checkpoint.validationCriteria,\n }\n : undefined;\n // 검증 컨텍스트 생성\n const verificationContext = {\n stepNumber: step.stepNumber,\n executionResult,\n expectation,\n previousVariables: Array.from(this.executedStepVariables),\n currentVariables: notebookContext.definedVariables,\n notebookContext,\n };\n // 상태 검증 수행\n const verificationResult = await this.stateVerifier.verifyStepState(verificationContext);\n // Progress 업데이트: 검증 완료\n const statusMessage = verificationResult.isValid\n ? `검증 통과 (${(verificationResult.confidence * 100).toFixed(0)}%)`\n : `검증 경고: ${verificationResult.mismatches.length}건 감지`;\n onProgress({\n phase: 'verifying',\n message: statusMessage,\n currentStep: step.stepNumber,\n });\n return verificationResult;\n }\n catch (error) {\n console.warn('[Orchestrator] State verification failed:', error.message);\n // 검증 실패 시에도 실행은 계속 진행 (graceful degradation)\n return null;\n }\n }\n /**\n * 출력 객체에서 문자열 추출\n */\n extractOutputString(output) {\n if (!output)\n return '';\n if (typeof output === 'string')\n return output;\n if (typeof output === 'object' && output !== null) {\n if ('text/plain' in output) {\n const textPlain = output['text/plain'];\n return typeof textPlain === 'string' ? textPlain : String(textPlain || '');\n }\n try {\n return JSON.stringify(output);\n }\n catch {\n return '[object]';\n }\n }\n try {\n return String(output);\n }\n catch {\n return '[unknown]';\n }\n }\n /**\n * Validation & Reflection 설정 업데이트\n */\n setValidationEnabled(enabled) {\n this.enablePreValidation = enabled;\n console.log('[Orchestrator] Pre-validation:', enabled ? 'enabled' : 'disabled');\n }\n setReflectionEnabled(enabled) {\n this.enableReflection = enabled;\n console.log('[Orchestrator] Reflection:', enabled ? 'enabled' : 'disabled');\n }\n /**\n * ★ State Verification 설정 업데이트 (Phase 1)\n */\n setStateVerificationEnabled(enabled) {\n this.enableStateVerification = enabled;\n console.log('[Orchestrator] State verification:', enabled ? 'enabled' : 'disabled');\n }\n /**\n * 현재 설정 확인\n */\n getValidationEnabled() {\n return this.enablePreValidation;\n }\n getReflectionEnabled() {\n return this.enableReflection;\n }\n /**\n * ★ State Verification 설정 확인 (Phase 1)\n */\n getStateVerificationEnabled() {\n return this.enableStateVerification;\n }\n /**\n * ★ State Verification 이력 조회 (Phase 1)\n */\n getStateVerificationHistory(count = 5) {\n return this.stateVerifier.getRecentHistory(count);\n }\n /**\n * ★ State Verification 트렌드 분석 (Phase 1)\n */\n getStateVerificationTrend() {\n return this.stateVerifier.analyzeConfidenceTrend();\n }\n // ═══════════════════════════════════════════════════════════════════════════\n // Context Management Methods (Phase 2)\n // ═══════════════════════════════════════════════════════════════════════════\n /**\n * ★ 컨텍스트 예산 설정 업데이트 (Phase 2)\n * @param budget 새로운 예산 설정 (부분 업데이트 가능)\n */\n updateContextBudget(budget) {\n this.contextManager.updateBudget(budget);\n console.log('[Orchestrator] Context budget updated:', this.contextManager.getBudget());\n }\n /**\n * ★ 현재 컨텍스트 예산 설정 반환 (Phase 2)\n */\n getContextBudget() {\n return this.contextManager.getBudget();\n }\n /**\n * ★ 현재 토큰 사용량 통계 반환 (Phase 2)\n */\n getContextUsage() {\n return this.contextManager.getLastUsage();\n }\n /**\n * ★ 현재 노트북의 컨텍스트 사용량 분석 (Phase 2)\n * 예산 상태와 권장 사항을 반환\n */\n analyzeContextUsage() {\n const rawContext = this.extractRawNotebookContext(this.notebook);\n const usage = this.contextManager.calculateUsage(rawContext);\n const budget = this.contextManager.getBudget();\n let status;\n let recommendation;\n if (usage.usagePercent >= 1.0) {\n status = 'critical';\n recommendation = '토큰 예산 초과. 컨텍스트가 자동 축소됩니다.';\n }\n else if (usage.usagePercent >= budget.warningThreshold) {\n status = 'warning';\n recommendation = '토큰 사용량이 경고 임계값에 근접합니다. 필요 시 예산을 늘리세요.';\n }\n else {\n status = 'ok';\n recommendation = '토큰 사용량이 정상 범위입니다.';\n }\n return { usage, status, recommendation };\n }\n /**\n * ★ ContextManager 인스턴스 반환 (Phase 2)\n * 외부에서 직접 컨텍스트 관리가 필요한 경우\n */\n getContextManager() {\n return this.contextManager;\n }\n /**\n * Replan decision 레이블 반환\n */\n getReplanDecisionLabel(decision) {\n switch (decision) {\n case 'refine':\n return '코드 수정';\n case 'insert_steps':\n return '단계 추가';\n case 'replace_step':\n return '단계 교체';\n case 'replan_remaining':\n return '남은 계획 재수립';\n default:\n return decision;\n }\n }\n /**\n * ModuleNotFoundError 메시지에서 패키지 이름 추출\n */\n extractMissingPackage(errorMessage) {\n // \"No module named 'plotly'\" 패턴\n const match = errorMessage.match(/No module named ['\"]([\\w\\-_.]+)['\"]/);\n if (match) {\n return match[1];\n }\n // \"ModuleNotFoundError: plotly\" 패턴\n const altMatch = errorMessage.match(/ModuleNotFoundError:\\s*([\\w\\-_.]+)/);\n if (altMatch) {\n return altMatch[1];\n }\n return undefined;\n }\n // ═══════════════════════════════════════════════════════════════════════════\n // Checkpoint Management Methods (Phase 3)\n // ═══════════════════════════════════════════════════════════════════════════\n /**\n * ★ CheckpointManager 인스턴스 반환 (Phase 3)\n * 외부에서 직접 체크포인트 관리가 필요한 경우\n */\n getCheckpointManager() {\n return this.checkpointManager;\n }\n /**\n * ★ 특정 체크포인트로 롤백 (Phase 3)\n * @param stepNumber 롤백할 스텝 번호\n * @returns 롤백 결과\n */\n async rollbackToCheckpoint(stepNumber) {\n console.log(`[Orchestrator] Initiating rollback to step ${stepNumber}`);\n return this.checkpointManager.rollbackTo(stepNumber);\n }\n /**\n * ★ 모든 체크포인트 조회 (Phase 3)\n * @returns 체크포인트 배열 (스텝 번호 순)\n */\n getCheckpoints() {\n return this.checkpointManager.getAllCheckpoints();\n }\n /**\n * ★ 가장 최근 체크포인트 조회 (Phase 3)\n * @returns 최신 체크포인트 또는 undefined\n */\n getLatestCheckpoint() {\n return this.checkpointManager.getLatestCheckpoint();\n }\n /**\n * ★ 체크포인트 매니저 상태 조회 (Phase 3)\n * @returns 체크포인트 매니저 상태 정보\n */\n getCheckpointState() {\n return this.checkpointManager.getState();\n }\n /**\n * ★ 체크포인트 설정 업데이트 (Phase 3)\n * @param config 새로운 설정 (부분 업데이트 가능)\n */\n updateCheckpointConfig(config) {\n this.checkpointManager.updateConfig(config);\n console.log('[Orchestrator] Checkpoint config updated:', this.checkpointManager.getConfig());\n }\n /**\n * ★ 현재 체크포인트 설정 반환 (Phase 3)\n */\n getCheckpointConfig() {\n return this.checkpointManager.getConfig();\n }\n /**\n * ★ 가장 최근 체크포인트로 롤백 (Phase 3)\n * @returns 롤백 결과\n */\n async rollbackToLatestCheckpoint() {\n console.log('[Orchestrator] Initiating rollback to latest checkpoint');\n return this.checkpointManager.rollbackToLatest();\n }\n /**\n * ★ 특정 스텝 이전 체크포인트로 롤백 (Phase 3)\n * @param stepNumber 이 스텝 바로 이전 체크포인트로 롤백\n * @returns 롤백 결과\n */\n async rollbackBeforeStep(stepNumber) {\n console.log(`[Orchestrator] Initiating rollback before step ${stepNumber}`);\n return this.checkpointManager.rollbackBefore(stepNumber);\n }\n /**\n * ★ 모든 체크포인트 삭제 (Phase 3)\n */\n clearAllCheckpoints() {\n this.checkpointManager.clearAllCheckpoints();\n console.log('[Orchestrator] All checkpoints cleared');\n }\n}\nexport default AgentOrchestrator;\n","/**\n * API Key Manager Service\n *\n * Manages LLM API keys in browser localStorage.\n * Keys are stored locally and sent with each request to the Agent Server.\n *\n * IMPORTANT (Financial Security Compliance):\n * - All API keys are stored ONLY in browser localStorage\n * - Agent Server receives ONE key per request (no key storage on server)\n * - Key rotation on rate limit (429) is handled by frontend\n */\nconst STORAGE_KEY = 'hdsp-agent-llm-config';\nexport const DEFAULT_LANGCHAIN_SYSTEM_PROMPT = `You are an expert Python data scientist and Jupyter notebook assistant.\nYour role is to help users with data analysis, visualization, and Python coding tasks in Jupyter notebooks. You can use only Korean\n\n# Core Behavior\nBe concise and direct. Answer in fewer than 4 lines unless the user asks for detail.\nAfter working on a file, just stop - don't explain what you did unless asked.\nAvoid unnecessary introductions or conclusions.\n\n## Task Management\nUse write_todos for complex multi-step tasks (3+ steps). Mark tasks in_progress before starting, completed immediately after finishing.\nFor simple 1-2 step tasks, just do them directly without todos.\n\nYou MUST ALWAYS call a tool in every response. After any tool result, you MUST:\n1. Check your todo list - are there pending or in_progress items?\n2. If YES → call the next appropriate tool (jupyter_cell_tool, markdown_tool, etc.)\n3. When you suggest next steps for todo item '다음 단계 제시', you MUST create next steps in json format matching this schema:\n{\n \"next_items\": [\n {\n \"subject\": \"<subject for next step>\",\n \"description\": \"<detailed description for the next step>\"\n }, ...\n ]\n}\n4. If ALL todos are completed → call final_answer_tool with a summary\n\n## 🔴 MANDATORY: Resource Check Before Data Hanlding\n**ALWAYS call check_resource_tool FIRST** when the task involves:\n- Loading files: .csv, .parquet, .json, .xlsx, .pickle, .h5, .feather\n- Handling datasets(dataframe) with pandas, polars, dask, or similar libraries\n- Training ML models on data files\n\n## Mandatory Workflow\n1. After EVERY tool result, immediately call the next tool\n2. Continue until ALL todos show status: \"completed\"\n3. ONLY THEN call final_answer_tool to summarize\n4. Only use jupyter_cell_tool for Python code or when the user explicitly asks to run in a notebook cell\n5. For plots and charts, use English text only.\n\n## ❌ FORBIDDEN (will break the workflow)\n- Producing an empty response (no tool call, no content)\n- Stopping after any tool without calling the next tool\n- Ending without calling final_answer_tool\n- Leaving todos in \"in_progress\" or \"pending\" state without continuing\n\n## 📖 File Reading Best Practices\n**CRITICAL**: When exploring codebases or reading files, use pagination to prevent context overflow.\n\n**Pattern for codebase exploration:**\n1. First scan: read_file_tool(path, limit=100) - See file structure and key sections\n2. Targeted read: read_file_tool(path, offset=100, limit=200) - Read specific sections if needed\n3. Full read: Only read without limit when necessary for immediate editing\n\n**When to paginate (use offset/limit):**\n- Reading any file >500 lines\n- Exploring unfamiliar codebases (always start with limit=100)\n- Reading multiple files in sequence\n- Any research or investigation task\n\n**When full read is OK:**\n- Small files (<500 lines)\n- Files you need to edit immediately after reading\n- After confirming file size with first scan\n\n## 🔧 Code Development\nFor code generation/refactoring, use LSP tools (diagnostics_tool, references_tool) to check errors and find symbol usages. Use multiedit_file_tool for multiple changes in one file.\n`;\n// ═══════════════════════════════════════════════════════════════════════════\n// Key Rotation State (in-memory, not persisted)\n// ═══════════════════════════════════════════════════════════════════════════\n/** Current key index for rotation (per-session, not persisted) */\nlet currentKeyIndex = 0;\n/** Set of rate-limited key indices (reset after successful request) */\nconst rateLimitedKeys = new Set();\n/** Maximum retry attempts with different keys */\nconst MAX_KEY_ROTATION_ATTEMPTS = 10;\n/**\n * Get the current LLM configuration from localStorage\n */\nexport function getLLMConfig() {\n try {\n const stored = localStorage.getItem(STORAGE_KEY);\n if (!stored) {\n return null;\n }\n return JSON.parse(stored);\n }\n catch (e) {\n console.error('[ApiKeyManager] Failed to parse stored config:', e);\n return null;\n }\n}\n/**\n * Save LLM configuration to localStorage\n */\nexport function saveLLMConfig(config) {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(config));\n console.log('[ApiKeyManager] Config saved to localStorage');\n // Dispatch custom event for same-tab listeners (e.g., idle monitor)\n window.dispatchEvent(new CustomEvent('hdsp-config-updated', { detail: config }));\n }\n catch (e) {\n console.error('[ApiKeyManager] Failed to save config:', e);\n }\n}\n/**\n * Clear stored LLM configuration\n */\nexport function clearLLMConfig() {\n localStorage.removeItem(STORAGE_KEY);\n console.log('[ApiKeyManager] Config cleared from localStorage');\n}\n/**\n * Check if API key is configured for the current provider\n */\nexport function hasValidApiKey(config) {\n if (!config)\n return false;\n switch (config.provider) {\n case 'gemini':\n // Check both single key and multiple keys\n const hasMainKey = !!(config.gemini?.apiKey && config.gemini.apiKey.trim());\n const hasArrayKeys = !!(config.gemini?.apiKeys && config.gemini.apiKeys.some(k => k && k.trim()));\n return hasMainKey || hasArrayKeys;\n case 'openai':\n return !!(config.openai?.apiKey && config.openai.apiKey.trim());\n case 'vllm':\n // vLLM may not require API key\n return true;\n default:\n return false;\n }\n}\n/**\n * Get default LLM configuration\n */\nexport function getDefaultLLMConfig() {\n return {\n provider: 'gemini',\n gemini: {\n apiKey: '',\n apiKeys: [],\n model: 'gemini-2.5-flash'\n },\n openai: {\n apiKey: '',\n model: 'gpt-4'\n },\n vllm: {\n endpoint: 'http://localhost:8000',\n model: 'default'\n },\n systemPrompt: DEFAULT_LANGCHAIN_SYSTEM_PROMPT,\n autoApprove: false\n };\n}\n/**\n * Get a random API key from the list (for load balancing)\n */\nexport function getRandomApiKey(config) {\n if (config.provider === 'gemini' && config.gemini) {\n const keys = config.gemini.apiKeys.filter(k => k && k.trim());\n if (keys.length === 0) {\n return config.gemini.apiKey || null;\n }\n // Random selection for load balancing\n const randomIndex = Math.floor(Math.random() * keys.length);\n return keys[randomIndex];\n }\n if (config.provider === 'openai' && config.openai) {\n return config.openai.apiKey || null;\n }\n if (config.provider === 'vllm' && config.vllm) {\n return config.vllm.apiKey || null;\n }\n return null;\n}\n/**\n * Get all valid API keys count\n */\nexport function getValidApiKeysCount(config) {\n if (config.provider === 'gemini' && config.gemini) {\n return config.gemini.apiKeys.filter(k => k && k.trim()).length;\n }\n if (config.provider === 'openai' && config.openai) {\n return config.openai.apiKey && config.openai.apiKey.trim() ? 1 : 0;\n }\n if (config.provider === 'vllm') {\n return 1; // vLLM doesn't require API key\n }\n return 0;\n}\n/**\n * Mask API key for display (show first 4 and last 4 characters)\n */\nexport function maskApiKey(key) {\n if (!key || key.length < 10)\n return '***';\n return `${key.slice(0, 4)}...${key.slice(-4)}`;\n}\n/**\n * Test API key by making a simple request\n */\nexport async function testApiKey(config) {\n try {\n // Simple validation - just check if key exists\n if (!hasValidApiKey(config)) {\n return { success: false, message: 'API key not configured' };\n }\n // For more thorough testing, you could make a minimal API call here\n // But for now, just validate format\n const key = config[config.provider];\n if (config.provider === 'gemini' && key?.apiKey) {\n if (!key.apiKey.startsWith('AIza')) {\n return { success: false, message: 'Invalid Gemini API key format (should start with AIza)' };\n }\n }\n if (config.provider === 'openai' && key?.apiKey) {\n if (!key.apiKey.startsWith('sk-')) {\n return { success: false, message: 'Invalid OpenAI API key format (should start with sk-)' };\n }\n }\n return { success: true, message: 'API key format is valid' };\n }\n catch (e) {\n return { success: false, message: `Error: ${e}` };\n }\n}\n// ═══════════════════════════════════════════════════════════════════════════\n// Key Rotation Functions (Financial Security Compliance)\n// ═══════════════════════════════════════════════════════════════════════════\n/**\n * Get all valid Gemini API keys from config\n */\nexport function getValidGeminiKeys(config) {\n if (config.provider !== 'gemini' || !config.gemini) {\n return [];\n }\n const keys = config.gemini.apiKeys?.filter(k => k && k.trim()) || [];\n // Fallback to single apiKey if no array\n if (keys.length === 0 && config.gemini.apiKey && config.gemini.apiKey.trim()) {\n return [config.gemini.apiKey];\n }\n return keys;\n}\n/**\n * Get current key index for rotation tracking\n */\nexport function getCurrentKeyIndex() {\n return currentKeyIndex;\n}\n/**\n * Reset key rotation state (call after successful request)\n */\nexport function resetKeyRotation() {\n rateLimitedKeys.clear();\n console.log('[ApiKeyManager] Key rotation state reset');\n}\n/**\n * Mark current key as rate-limited and rotate to next available key\n * @returns true if rotation successful, false if all keys exhausted\n */\nexport function rotateToNextKey(config) {\n const keys = getValidGeminiKeys(config);\n if (keys.length <= 1) {\n console.log('[ApiKeyManager] Cannot rotate - only one key available');\n return false;\n }\n // Mark current key as rate-limited\n rateLimitedKeys.add(currentKeyIndex);\n console.log(`[ApiKeyManager] Key ${currentKeyIndex + 1} marked as rate-limited`);\n // Find next available key\n for (let i = 0; i < keys.length; i++) {\n const nextIndex = (currentKeyIndex + 1 + i) % keys.length;\n if (!rateLimitedKeys.has(nextIndex)) {\n currentKeyIndex = nextIndex;\n console.log(`[ApiKeyManager] Rotated to key ${currentKeyIndex + 1}/${keys.length}`);\n return true;\n }\n }\n // All keys rate-limited\n console.log('[ApiKeyManager] All keys rate-limited');\n return false;\n}\n/**\n * Check if there are available keys (not all rate-limited)\n */\nexport function hasAvailableKeys(config) {\n const keys = getValidGeminiKeys(config);\n return keys.length > rateLimitedKeys.size;\n}\n/**\n * Get count of remaining available keys\n */\nexport function getAvailableKeyCount(config) {\n const keys = getValidGeminiKeys(config);\n return Math.max(0, keys.length - rateLimitedKeys.size);\n}\n/**\n * Build config with SINGLE current API key for server request.\n * Server receives only one key - rotation is handled by frontend.\n */\nexport function buildSingleKeyConfig(config) {\n if (config.provider !== 'gemini' || !config.gemini) {\n // Non-Gemini providers - return as-is (no rotation)\n return config;\n }\n const keys = getValidGeminiKeys(config);\n if (keys.length === 0) {\n return config;\n }\n // Ensure currentKeyIndex is within bounds\n if (currentKeyIndex >= keys.length) {\n currentKeyIndex = 0;\n }\n const currentKey = keys[currentKeyIndex];\n console.log(`[ApiKeyManager] Using key ${currentKeyIndex + 1}/${keys.length}`);\n // Return config with single apiKey (server only uses apiKey field)\n return {\n ...config,\n gemini: {\n ...config.gemini,\n apiKey: currentKey,\n // Don't send apiKeys array to server (security)\n apiKeys: [],\n },\n };\n}\n/**\n * Handle rate limit error with automatic key rotation\n * @returns New config with rotated key, or null if all keys exhausted\n */\nexport function handleRateLimitError(config) {\n if (config.provider !== 'gemini') {\n // Non-Gemini providers don't support rotation\n return null;\n }\n const rotated = rotateToNextKey(config);\n if (!rotated) {\n console.log('[ApiKeyManager] Rate limit: All keys exhausted');\n return null;\n }\n return buildSingleKeyConfig(config);\n}\n/**\n * Check if error is a rate limit error (429)\n */\nexport function isRateLimitError(error) {\n const errorMsg = typeof error === 'string' ? error : error.message;\n return errorMsg.includes('RATE_LIMIT_EXCEEDED') ||\n errorMsg.includes('429') ||\n errorMsg.toLowerCase().includes('quota exceeded') ||\n errorMsg.toLowerCase().includes('rate limit');\n}\n","/**\n * API Service Layer for REST communication with backend\n */\nimport { buildSingleKeyConfig, handleRateLimitError, isRateLimitError, resetKeyRotation, getValidGeminiKeys, getCurrentKeyIndex } from './ApiKeyManager';\n// ✅ 핵심 변경 1: ServerConnection 대신 PageConfig 임포트\nimport { URLExt, PageConfig } from '@jupyterlab/coreutils';\nexport class ApiService {\n // 생성자에서 baseUrl을 선택적으로 받도록 하되, 없으면 자동으로 계산\n constructor(baseUrl) {\n this.resourceUsageCache = null;\n this.resourceUsageCacheMs = 15000;\n if (baseUrl) {\n this.baseUrl = baseUrl;\n }\n else {\n // ✅ 핵심 변경 2: ServerConnection 대신 PageConfig로 URL 가져오기\n // PageConfig.getBaseUrl()은 '/user/아이디/프로젝트/' 형태의 주소를 정확히 가져옵니다.\n const serverRoot = PageConfig.getBaseUrl();\n // 3. 경로 합치기\n // 결과: /user/453467/pl2wadmprj/hdsp-agent\n this.baseUrl = URLExt.join(serverRoot, 'hdsp-agent');\n }\n console.log('[ApiService] Base URL initialized:', this.baseUrl); // 디버깅용 로그\n }\n /**\n * Get cookie value by name\n */\n getCookie(name) {\n const value = `; ${document.cookie}`;\n const parts = value.split(`; ${name}=`);\n if (parts.length === 2) {\n return parts.pop()?.split(';').shift() || '';\n }\n return '';\n }\n /**\n * Get CSRF token from cookie\n */\n getCsrfToken() {\n return this.getCookie('_xsrf');\n }\n /**\n * Get headers with CSRF token for POST requests\n */\n getHeaders() {\n return {\n 'Content-Type': 'application/json',\n 'X-XSRFToken': this.getCsrfToken()\n };\n }\n async getResourceUsageSnapshot() {\n const now = Date.now();\n if (this.resourceUsageCache\n && now - this.resourceUsageCache.timestamp < this.resourceUsageCacheMs) {\n return this.resourceUsageCache.resource;\n }\n try {\n const response = await fetch(`${this.baseUrl}/resource-usage`, {\n method: 'GET',\n credentials: 'include'\n });\n if (!response.ok) {\n return null;\n }\n const payload = await response.json().catch(() => ({}));\n const candidate = payload?.resource ?? payload;\n const snapshot = candidate && typeof candidate === 'object' && !Array.isArray(candidate)\n ? candidate\n : null;\n if (snapshot) {\n this.resourceUsageCache = { resource: snapshot, timestamp: now };\n }\n return snapshot;\n }\n catch (error) {\n console.warn('[ApiService] Resource usage fetch failed:', error);\n return null;\n }\n }\n // ═══════════════════════════════════════════════════════════════════════════\n // Global Rate Limit Handling with Key Rotation\n // ═══════════════════════════════════════════════════════════════════════════\n /**\n * Fetch wrapper with automatic API key rotation on rate limit (429)\n *\n * NOTE (Financial Security Compliance):\n * - API key rotation is handled by frontend (not server)\n * - Server receives ONLY ONE key per request\n * - On 429 rate limit, frontend rotates key and retries with next key\n */\n async fetchWithKeyRotation(url, request, options) {\n const MAX_RETRIES = 10;\n const originalConfig = request.llmConfig;\n let lastError = null;\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n // Build request with single key for this attempt\n const requestToSend = originalConfig\n ? { ...request, llmConfig: buildSingleKeyConfig(originalConfig) }\n : request;\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify(requestToSend)\n });\n if (response.ok) {\n // Success - reset key rotation state\n resetKeyRotation();\n return response.json();\n }\n // Handle error response\n const errorText = await response.text();\n // Check if rate limit error\n if (isRateLimitError(errorText) && originalConfig) {\n console.log(`[ApiService] Rate limit on attempt ${attempt + 1}, rotating key...`);\n // Try to rotate to next key\n const rotatedConfig = handleRateLimitError(originalConfig);\n if (rotatedConfig) {\n // Notify UI about key rotation\n const keys = getValidGeminiKeys(originalConfig);\n if (options?.onKeyRotation) {\n options.onKeyRotation(getCurrentKeyIndex(), keys.length);\n }\n continue; // Try next key\n }\n else {\n // All keys exhausted\n throw new Error('모든 API 키가 Rate Limit 상태입니다. 잠시 후 다시 시도해주세요.');\n }\n }\n // Not a rate limit error - parse and throw\n let errorMessage = options?.defaultErrorMessage || 'API 요청 실패';\n try {\n const errorJson = JSON.parse(errorText);\n errorMessage = errorJson.detail || errorJson.error || errorJson.message || errorMessage;\n }\n catch (e) {\n errorMessage = errorText || errorMessage;\n }\n throw new Error(errorMessage);\n }\n catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n lastError = error instanceof Error ? error : new Error(errorMsg);\n // If it's a rate limit error from the catch block, check rotation\n if (isRateLimitError(errorMsg) && originalConfig) {\n const rotatedConfig = handleRateLimitError(originalConfig);\n if (rotatedConfig) {\n const keys = getValidGeminiKeys(originalConfig);\n if (options?.onKeyRotation) {\n options.onKeyRotation(getCurrentKeyIndex(), keys.length);\n }\n continue;\n }\n throw new Error('모든 API 키가 Rate Limit 상태입니다. 잠시 후 다시 시도해주세요.');\n }\n // Not a rate limit error, throw immediately\n throw error;\n }\n }\n throw lastError || new Error('Maximum retry attempts exceeded');\n }\n /**\n * Execute cell action (explain, fix, custom)\n * Uses global rate limit handling with key rotation\n */\n async cellAction(request) {\n console.log('[ApiService] cellAction request:', request);\n return this.fetchWithKeyRotation(`${this.baseUrl}/cell/action`, request, { defaultErrorMessage: '셀 액션 실패' });\n }\n /**\n * Send chat message (non-streaming)\n * Uses global rate limit handling with key rotation\n */\n async sendMessage(request) {\n console.log('[ApiService] sendMessage request');\n return this.fetchWithKeyRotation(`${this.baseUrl}/chat/message`, request, { defaultErrorMessage: '메시지 전송 실패' });\n }\n /**\n * Send chat message with streaming response\n *\n * NOTE (Financial Security Compliance):\n * - API key rotation is handled by frontend (not server)\n * - Server receives ONLY ONE key per request\n * - On 429 rate limit, frontend rotates key and retries with next key\n */\n /**\n * 단순 Chat용 스트리밍 - /chat/stream 사용\n * 원래 main 브랜치의 구현 복원 - LangChain 에이전트 없이 단순 Q&A\n */\n async sendChatStream(request, onChunk, onMetadata) {\n const MAX_RETRIES = 10;\n let currentConfig = request.llmConfig;\n let lastError = null;\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n const requestToSend = currentConfig\n ? { ...request, llmConfig: buildSingleKeyConfig(currentConfig) }\n : request;\n try {\n await this.sendChatStreamInternal(requestToSend, onChunk, onMetadata);\n resetKeyRotation();\n return;\n }\n catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n lastError = error instanceof Error ? error : new Error(errorMsg);\n if (isRateLimitError(errorMsg) && request.llmConfig) {\n console.log(`[ApiService] Chat rate limit on attempt ${attempt + 1}, trying next key...`);\n const rotatedConfig = handleRateLimitError(request.llmConfig);\n if (rotatedConfig) {\n currentConfig = request.llmConfig;\n continue;\n }\n else {\n throw new Error('모든 API 키가 Rate Limit 상태입니다. 잠시 후 다시 시도해주세요.');\n }\n }\n throw error;\n }\n }\n throw lastError || new Error('Maximum retry attempts exceeded');\n }\n /**\n * 단순 Chat 스트리밍 내부 구현 - /chat/stream 엔드포인트 사용\n */\n async sendChatStreamInternal(request, onChunk, onMetadata) {\n const response = await fetch(`${this.baseUrl}/chat/stream`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify(request)\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to send chat message: ${error}`);\n }\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error('Response body is not readable');\n }\n const decoder = new TextDecoder();\n let buffer = '';\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done)\n break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n try {\n const data = JSON.parse(line.slice(6));\n if (data.error) {\n throw new Error(data.error);\n }\n if (data.content) {\n onChunk(data.content);\n }\n if (data.done && data.conversationId && onMetadata) {\n onMetadata({ conversationId: data.conversationId });\n }\n }\n catch (e) {\n if (!(e instanceof SyntaxError)) {\n throw e;\n }\n }\n }\n }\n }\n }\n finally {\n reader.releaseLock();\n }\n }\n /**\n * Agent V2용 스트리밍 - /agent/langchain/stream 사용\n * LangChain Deep Agent (HITL, Todo, 도구 실행 등)\n */\n async sendAgentV2Stream(request, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall, onComplete, // Callback to capture thread_id for context persistence\n threadId // Optional thread_id to continue existing conversation\n ) {\n // Maximum retry attempts (should match number of keys)\n const MAX_RETRIES = 10;\n let currentConfig = request.llmConfig;\n let lastError = null;\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n // Build request with single key for this attempt\n const requestToSend = currentConfig\n ? { ...request, llmConfig: buildSingleKeyConfig(currentConfig) }\n : request;\n try {\n await this.sendAgentV2StreamInternal(requestToSend, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall, onComplete, threadId);\n // Success - reset key rotation state\n resetKeyRotation();\n return;\n }\n catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n lastError = error instanceof Error ? error : new Error(errorMsg);\n // Check if rate limit error and we have config to rotate\n if (isRateLimitError(errorMsg) && request.llmConfig) {\n console.log(`[ApiService] Rate limit on attempt ${attempt + 1}, trying next key...`);\n // Try to rotate to next key using ORIGINAL config (with all keys)\n const rotatedConfig = handleRateLimitError(request.llmConfig);\n if (rotatedConfig) {\n // Update currentConfig with the rotated key for next attempt\n // Note: rotatedConfig already has single key, but we need full config for next rotation\n currentConfig = request.llmConfig;\n continue; // Try next key\n }\n else {\n // All keys exhausted\n throw new Error('모든 API 키가 Rate Limit 상태입니다. 잠시 후 다시 시도해주세요.');\n }\n }\n // Not a rate limit error, throw immediately\n throw error;\n }\n }\n // Should not reach here, but just in case\n throw lastError || new Error('Maximum retry attempts exceeded');\n }\n /**\n * Build request with single API key for server\n * (Key rotation is managed by frontend for financial security compliance)\n */\n buildRequestWithSingleKey(request) {\n if (!request.llmConfig) {\n return request;\n }\n // Build config with single key (server only uses apiKey field)\n const singleKeyConfig = buildSingleKeyConfig(request.llmConfig);\n return {\n ...request,\n llmConfig: singleKeyConfig\n };\n }\n /**\n * 기존 sendMessageStream 유지 (하위 호환성)\n * 내부적으로 sendAgentV2Stream 호출\n * @deprecated sendChatStream 또는 sendAgentV2Stream을 직접 사용하세요\n */\n async sendMessageStream(request, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall, onComplete, threadId) {\n return this.sendAgentV2Stream(request, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall, onComplete, threadId);\n }\n /**\n * Agent V2 스트리밍 내부 구현 - /agent/langchain/stream 사용\n * (기존 sendMessageStreamInternal에서 이름 변경)\n */\n async sendAgentV2StreamInternal(request, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall, onComplete, threadId) {\n // Convert IChatRequest to LangChain AgentRequest format\n // Frontend's context has limited fields, map what's available\n const resourceContext = await this.getResourceUsageSnapshot();\n const requestConfig = resourceContext && request.llmConfig\n ? { ...request.llmConfig, resourceContext }\n : request.llmConfig;\n const langchainRequest = {\n request: request.message,\n threadId: threadId,\n notebookContext: request.context ? {\n notebook_path: request.context.notebookPath,\n cell_count: 0,\n imported_libraries: [],\n defined_variables: [],\n recent_cells: request.context.selectedCells?.map(cell => ({ source: cell })) || []\n } : undefined,\n llmConfig: requestConfig,\n workspaceRoot: '.'\n };\n // Debug: log request size\n const requestBody = JSON.stringify(langchainRequest);\n console.log('[ApiService] Sending langchain request:', {\n messageLength: request.message.length,\n bodyLength: requestBody.length,\n threadId: threadId,\n });\n // Use LangChain streaming endpoint\n const response = await fetch(`${this.baseUrl}/agent/langchain/stream`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: requestBody\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to send message: ${error}`);\n }\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error('Response body is not readable');\n }\n const decoder = new TextDecoder();\n let buffer = '';\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done)\n break;\n buffer += decoder.decode(value, { stream: true });\n // Process complete SSE messages\n const lines = buffer.split('\\n');\n buffer = lines.pop() || ''; // Keep incomplete line in buffer\n let currentEventType = '';\n for (const line of lines) {\n // Handle SSE event type: \"event: type\"\n if (line.startsWith('event: ')) {\n currentEventType = line.slice(7).trim();\n continue;\n }\n // Handle SSE data: \"data: json\"\n if (line.startsWith('data: ')) {\n try {\n const data = JSON.parse(line.slice(6));\n // Handle based on event type\n if (currentEventType === 'todos' && data.todos && onTodos) {\n onTodos(data.todos);\n currentEventType = '';\n continue;\n }\n if (currentEventType === 'debug_clear' && onDebugClear) {\n onDebugClear();\n currentEventType = '';\n continue;\n }\n if (currentEventType === 'complete') {\n if (onDebugClear) {\n onDebugClear();\n }\n // Capture thread_id for context persistence across cycles\n console.log('[ApiService] Complete event received, data:', data);\n if (onComplete && data.thread_id) {\n console.log('[ApiService] Calling onComplete with threadId:', data.thread_id);\n onComplete({ threadId: data.thread_id });\n }\n else {\n console.log('[ApiService] onComplete not called - onComplete:', !!onComplete, 'thread_id:', data.thread_id);\n }\n return;\n }\n // Handle errors\n if (data.error) {\n if (onDebug) {\n onDebug(`오류: ${data.error}`);\n }\n throw new Error(data.error);\n }\n // Handle debug events (display in gray)\n if (data.status && onDebug) {\n onDebug(data.status);\n }\n // Handle interrupt events (Human-in-the-Loop)\n if (data.thread_id && data.action && onInterrupt) {\n onInterrupt({\n threadId: data.thread_id,\n action: data.action,\n args: data.args || {},\n description: data.description || ''\n });\n return; // Stop processing, wait for user decision\n }\n // Handle token events (streaming LLM response)\n if (data.content) {\n onChunk(data.content);\n }\n // Handle tool_call events - pass to handler for cell creation\n if (currentEventType === 'tool_call' && data.tool && onToolCall) {\n onToolCall({\n tool: data.tool,\n code: data.code,\n content: data.content,\n command: data.command,\n timeout: data.timeout\n });\n currentEventType = '';\n continue;\n }\n // Handle tool_result events - skip displaying raw output\n if (data.output) {\n // Don't add raw tool output to chat\n }\n // Handle completion\n if (data.final_answer) {\n onChunk(data.final_answer);\n }\n // Handle metadata\n if (data.conversationId && onMetadata) {\n onMetadata({\n conversationId: data.conversationId,\n messageId: data.messageId,\n provider: data.metadata?.provider,\n model: data.metadata?.model\n });\n }\n // Final metadata update\n if (data.done && data.metadata && onMetadata) {\n onMetadata({\n provider: data.metadata.provider,\n model: data.metadata.model\n });\n }\n currentEventType = '';\n }\n catch (e) {\n if (e instanceof SyntaxError) {\n console.warn('Failed to parse SSE data:', line);\n }\n else {\n throw e;\n }\n }\n }\n }\n }\n }\n finally {\n reader.releaseLock();\n }\n }\n /**\n * Resume interrupted agent execution with user decision\n */\n async resumeAgent(threadId, decision, args, feedback, llmConfig, onChunk, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall) {\n const resourceContext = await this.getResourceUsageSnapshot();\n const requestConfig = resourceContext && llmConfig\n ? { ...llmConfig, resourceContext }\n : llmConfig;\n const resumeRequest = {\n threadId,\n decisions: [{\n type: decision,\n args,\n feedback\n }],\n llmConfig: requestConfig,\n workspaceRoot: '.'\n };\n const response = await fetch(`${this.baseUrl}/agent/langchain/resume`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify(resumeRequest)\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to resume agent: ${error}`);\n }\n // Process SSE stream (same as sendMessageStream)\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error('Response body is not readable');\n }\n const decoder = new TextDecoder();\n let buffer = '';\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done)\n break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n let currentEventType = '';\n for (const line of lines) {\n // Handle SSE event type\n if (line.startsWith('event: ')) {\n currentEventType = line.slice(7).trim();\n continue;\n }\n if (line.startsWith('data: ')) {\n try {\n const data = JSON.parse(line.slice(6));\n if (data.error) {\n if (onDebug) {\n onDebug(`오류: ${data.error}`);\n }\n throw new Error(data.error);\n }\n // Handle todos event\n if (currentEventType === 'todos' && data.todos && onTodos) {\n onTodos(data.todos);\n currentEventType = '';\n continue;\n }\n // Handle debug_clear event\n if (currentEventType === 'debug_clear' && onDebugClear) {\n onDebugClear();\n currentEventType = '';\n continue;\n }\n if (currentEventType === 'complete') {\n if (onDebugClear) {\n onDebugClear();\n }\n return;\n }\n // Debug events\n if (data.status && onDebug) {\n onDebug(data.status);\n }\n // Another interrupt\n if (data.thread_id && data.action && onInterrupt) {\n onInterrupt({\n threadId: data.thread_id,\n action: data.action,\n args: data.args || {},\n description: data.description || ''\n });\n return;\n }\n // Content chunks\n if (data.content && onChunk) {\n onChunk(data.content);\n }\n // Tool call events during resume\n if (currentEventType === 'tool_call' && data.tool && onToolCall) {\n onToolCall({\n tool: data.tool,\n code: data.code,\n content: data.content,\n command: data.command,\n timeout: data.timeout\n });\n currentEventType = '';\n continue;\n }\n currentEventType = '';\n }\n catch (e) {\n if (!(e instanceof SyntaxError)) {\n throw e;\n }\n }\n }\n }\n }\n }\n finally {\n reader.releaseLock();\n }\n }\n async executeCommand(command, timeout, cwd, stdin) {\n const response = await fetch(`${this.baseUrl}/execute-command`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify({ command, timeout, cwd, stdin })\n });\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n const errorMessage = payload.error || 'Failed to execute command';\n throw new Error(errorMessage);\n }\n return payload;\n }\n async executeCommandStream(command, options) {\n const response = await fetch(`${this.baseUrl}/execute-command/stream`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify({\n command,\n timeout: options?.timeout,\n cwd: options?.cwd,\n stdin: options?.stdin\n })\n });\n if (!response.ok) {\n const payload = await response.json().catch(() => ({}));\n const errorMessage = payload.error || 'Failed to execute command';\n throw new Error(errorMessage);\n }\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error('Response body is not readable');\n }\n const decoder = new TextDecoder();\n let buffer = '';\n let result = null;\n let streamError = null;\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done)\n break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n let currentEventType = '';\n for (const line of lines) {\n if (line.startsWith('event: ')) {\n currentEventType = line.slice(7).trim();\n continue;\n }\n if (!line.startsWith('data: ')) {\n continue;\n }\n let data;\n try {\n data = JSON.parse(line.slice(6));\n }\n catch (e) {\n continue;\n }\n if (currentEventType === 'output') {\n if (typeof data.text === 'string' && options?.onOutput) {\n options.onOutput({\n stream: data.stream === 'stderr' ? 'stderr' : 'stdout',\n text: data.text\n });\n }\n currentEventType = '';\n continue;\n }\n if (currentEventType === 'error') {\n streamError = data.error || 'Command execution failed';\n currentEventType = '';\n continue;\n }\n if (currentEventType === 'result') {\n result = data;\n return result;\n }\n currentEventType = '';\n }\n }\n }\n finally {\n reader.releaseLock();\n }\n if (streamError) {\n throw new Error(streamError);\n }\n if (!result) {\n throw new Error('No command result received');\n }\n return result;\n }\n async readFile(path, options) {\n const response = await fetch(`${this.baseUrl}/read-file`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify({\n path,\n encoding: options?.encoding || 'utf-8',\n cwd: options?.cwd\n })\n });\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n const errorMessage = payload.error || 'Failed to read file';\n return { success: false, error: errorMessage };\n }\n return payload;\n }\n async writeFile(path, content, options) {\n const response = await fetch(`${this.baseUrl}/write-file`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify({\n path,\n content,\n encoding: options?.encoding,\n overwrite: options?.overwrite,\n cwd: options?.cwd\n })\n });\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n const errorMessage = payload.error || 'Failed to write file';\n throw new Error(errorMessage);\n }\n return payload;\n }\n /**\n * Search workspace for files matching pattern\n * Executed on Jupyter server using grep/ripgrep\n */\n async searchWorkspace(options) {\n const response = await fetch(`${this.baseUrl}/search-workspace`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify({\n pattern: options.pattern,\n file_types: options.file_types || ['*.py', '*.ipynb'],\n path: options.path || '.',\n max_results: options.max_results || 50,\n case_sensitive: options.case_sensitive || false\n })\n });\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n const errorMessage = payload.error || 'Failed to search workspace';\n throw new Error(errorMessage);\n }\n return payload;\n }\n /**\n * Search notebook cells for pattern\n * Executed on Jupyter server\n */\n async searchNotebookCells(options) {\n const response = await fetch(`${this.baseUrl}/search-notebook-cells`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify({\n pattern: options.pattern,\n notebook_path: options.notebook_path,\n cell_type: options.cell_type,\n max_results: options.max_results || 30,\n case_sensitive: options.case_sensitive || false\n })\n });\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n const errorMessage = payload.error || 'Failed to search notebook cells';\n throw new Error(errorMessage);\n }\n return payload;\n }\n /**\n * Check system resources and file sizes before data processing\n * Executed on Jupyter server\n */\n async checkResource(options) {\n const response = await fetch(`${this.baseUrl}/check-resource`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify({\n files: options.files || [],\n dataframes: options.dataframes || [],\n file_size_command: options.file_size_command || '',\n dataframe_check_code: options.dataframe_check_code || ''\n })\n });\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n if (response.status === 404) {\n return {\n success: false,\n files: [],\n dataframes: [],\n error: 'Resource check endpoint is not available on this Jupyter server.'\n };\n }\n const errorMessage = payload.error || 'Failed to check resources';\n throw new Error(errorMessage);\n }\n return payload;\n }\n /**\n * Save configuration\n */\n async saveConfig(config) {\n const response = await fetch(`${this.baseUrl}/config`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify(config)\n });\n if (!response.ok) {\n const error = await response.json().catch(() => ({ message: 'Failed to save configuration' }));\n throw new Error(error.message || 'Failed to save configuration');\n }\n }\n /**\n * Get current configuration\n */\n async getConfig() {\n const response = await fetch(`${this.baseUrl}/config`);\n if (!response.ok) {\n throw new Error('Failed to load configuration');\n }\n return response.json();\n }\n /**\n * Get health status\n */\n async getStatus() {\n const response = await fetch(`${this.baseUrl}/status`);\n if (!response.ok) {\n throw new Error('Failed to get status');\n }\n return response.json();\n }\n /**\n * Get available models\n */\n async getModels() {\n const response = await fetch(`${this.baseUrl}/models`);\n if (!response.ok) {\n throw new Error('Failed to load models');\n }\n return response.json();\n }\n // ═══════════════════════════════════════════════════════════════════════════\n // Auto-Agent API Methods (All use global rate limit handling)\n // ═══════════════════════════════════════════════════════════════════════════\n /**\n * Generate execution plan for auto-agent task\n */\n async generateExecutionPlan(request, onKeyRotation) {\n console.log('[ApiService] generateExecutionPlan request:', request);\n return this.fetchWithKeyRotation(`${this.baseUrl}/auto-agent/plan`, request, { onKeyRotation, defaultErrorMessage: '계획 생성 실패' });\n }\n /**\n * Refine step code after error (Self-Healing)\n */\n async refineStepCode(request, onKeyRotation) {\n console.log('[ApiService] refineStepCode request:', request);\n return this.fetchWithKeyRotation(`${this.baseUrl}/auto-agent/refine`, request, { onKeyRotation, defaultErrorMessage: '코드 수정 실패' });\n }\n /**\n * Adaptive Replanning - 에러 분석 후 계획 재수립\n */\n async replanExecution(request, onKeyRotation) {\n console.log('[ApiService] replanExecution request:', request);\n return this.fetchWithKeyRotation(`${this.baseUrl}/auto-agent/replan`, request, { onKeyRotation, defaultErrorMessage: '계획 재수립 실패' });\n }\n /**\n * Validate code before execution - 사전 코드 품질 검증 (Pyflakes/AST 기반)\n * Note: This is a local validation, no LLM call, but uses wrapper for consistency\n */\n async validateCode(request) {\n console.log('[ApiService] validateCode request:', request);\n return this.fetchWithKeyRotation(`${this.baseUrl}/auto-agent/validate`, request, { defaultErrorMessage: '코드 검증 실패' });\n }\n /**\n * Reflect on step execution - 실행 결과 분석 및 적응적 조정\n */\n async reflectOnExecution(request, onKeyRotation) {\n console.log('[ApiService] reflectOnExecution request:', request);\n return this.fetchWithKeyRotation(`${this.baseUrl}/auto-agent/reflect`, request, { onKeyRotation, defaultErrorMessage: 'Reflection 실패' });\n }\n /**\n * Verify state after step execution - 상태 검증 (Phase 1)\n * Note: This is a local verification, no LLM call, but uses wrapper for consistency\n */\n async verifyState(request) {\n console.log('[ApiService] verifyState request:', request);\n return this.fetchWithKeyRotation(`${this.baseUrl}/auto-agent/verify-state`, request, { defaultErrorMessage: '상태 검증 실패' });\n }\n /**\n * Stream execution plan generation with real-time updates\n */\n async generateExecutionPlanStream(request, onPlanUpdate, onReasoning) {\n const response = await fetch(`${this.baseUrl}/auto-agent/plan/stream`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify(request)\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to generate plan: ${error}`);\n }\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error('Response body is not readable');\n }\n const decoder = new TextDecoder();\n let buffer = '';\n let finalPlan = null;\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done)\n break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n try {\n const data = JSON.parse(line.slice(6));\n if (data.error) {\n throw new Error(data.error);\n }\n if (data.reasoning && onReasoning) {\n onReasoning(data.reasoning);\n }\n if (data.plan) {\n onPlanUpdate(data.plan);\n if (data.done) {\n finalPlan = data.plan;\n }\n }\n }\n catch (e) {\n if (!(e instanceof SyntaxError)) {\n throw e;\n }\n }\n }\n }\n }\n }\n finally {\n reader.releaseLock();\n }\n if (!finalPlan) {\n throw new Error('No plan received from server');\n }\n return finalPlan;\n }\n // ═══════════════════════════════════════════════════════════════════════════\n // File Action API Methods (Python 파일 에러 수정)\n // ═══════════════════════════════════════════════════════════════════════════\n /**\n * Python 파일 에러 수정/분석/설명 요청\n */\n async fileAction(request, onKeyRotation) {\n console.log('[ApiService] fileAction request:', request);\n return this.fetchWithKeyRotation(`${this.baseUrl}/file/action`, request, { onKeyRotation, defaultErrorMessage: '파일 액션 실패' });\n }\n // ═══════════════════════════════════════════════════════════════════════════\n // File Resolution API Methods (파일 경로 해결)\n // ═══════════════════════════════════════════════════════════════════════════\n /**\n * Resolve file path - 파일명 또는 패턴으로 경로 검색\n */\n async resolveFile(request) {\n console.log('[ApiService] resolveFile request:', request);\n const response = await fetch(`${this.baseUrl}/file/resolve`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify(request)\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to resolve file: ${error}`);\n }\n return response.json();\n }\n /**\n * Select file from multiple options - 사용자 선택 처리\n */\n async selectFile(request) {\n console.log('[ApiService] selectFile request:', request);\n const response = await fetch(`${this.baseUrl}/file/select`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify(request)\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to select file: ${error}`);\n }\n return response.json();\n }\n}\n","/**\n * CheckpointManager - 체크포인트 생성 및 롤백 관리\n *\n * 각 성공 스텝 후 체크포인트 저장, 실패 시 롤백 가능\n * - 순환 버퍼 (기본 최대 10개)\n * - 생성된 셀 삭제, 수정된 셀 복원\n */\nimport { DEFAULT_CHECKPOINT_CONFIG, } from '../types/auto-agent';\n/**\n * CheckpointManager 클래스\n * 체크포인트 생성/조회/롤백 관리\n */\nexport class CheckpointManager {\n constructor(notebook, config) {\n // 현재 실행 세션의 추적 정보\n this.createdCellIndices = new Set();\n this.modifiedCellIndices = new Set();\n this.variableNames = new Set();\n this.notebook = notebook;\n this.config = {\n ...DEFAULT_CHECKPOINT_CONFIG,\n ...config,\n };\n this.checkpoints = new Map();\n }\n /**\n * 설정 업데이트\n */\n updateConfig(config) {\n this.config = {\n ...this.config,\n ...config,\n };\n console.log('[CheckpointManager] Config updated:', this.config);\n }\n /**\n * 현재 설정 반환\n */\n getConfig() {\n return { ...this.config };\n }\n /**\n * 새 실행 세션 시작 시 초기화\n */\n startNewSession() {\n this.checkpoints.clear();\n this.createdCellIndices.clear();\n this.modifiedCellIndices.clear();\n this.variableNames.clear();\n console.log('[CheckpointManager] New session started');\n }\n /**\n * 체크포인트 생성\n * 스텝 성공 후 호출\n */\n createCheckpoint(stepNumber, stepDescription, plan, stepResult, newVariables = []) {\n // 스텝 결과에서 생성/수정된 셀 추적\n stepResult.toolResults.forEach((tr) => {\n if (tr.cellIndex !== undefined) {\n if (tr.wasModified) {\n this.modifiedCellIndices.add(tr.cellIndex);\n }\n else {\n this.createdCellIndices.add(tr.cellIndex);\n }\n }\n });\n // 새 변수들 추적\n newVariables.forEach(v => this.variableNames.add(v));\n // 셀 스냅샷 생성\n const cellSnapshots = this.captureCellSnapshots(stepResult);\n const checkpoint = {\n id: `cp-${stepNumber}-${Date.now()}`,\n stepNumber,\n timestamp: Date.now(),\n description: stepDescription,\n planSnapshot: { ...plan },\n cellSnapshots,\n variableNames: Array.from(this.variableNames),\n createdCellIndices: Array.from(this.createdCellIndices),\n modifiedCellIndices: Array.from(this.modifiedCellIndices),\n };\n // 순환 버퍼: 최대 개수 초과 시 가장 오래된 체크포인트 삭제\n if (this.checkpoints.size >= this.config.maxCheckpoints) {\n const oldestStep = Math.min(...this.checkpoints.keys());\n this.checkpoints.delete(oldestStep);\n console.log(`[CheckpointManager] Removed oldest checkpoint: step ${oldestStep}`);\n }\n this.checkpoints.set(stepNumber, checkpoint);\n console.log(`[CheckpointManager] Created checkpoint for step ${stepNumber}`);\n return checkpoint;\n }\n /**\n * 셀 스냅샷 캡처\n */\n captureCellSnapshots(stepResult) {\n const snapshots = [];\n const cells = this.notebook.content.model?.cells;\n if (!cells)\n return snapshots;\n stepResult.toolResults.forEach((tr) => {\n if (tr.cellIndex !== undefined && tr.cellIndex < cells.length) {\n const cell = cells.get(tr.cellIndex);\n const snapshot = {\n index: tr.cellIndex,\n type: cell.type,\n source: cell.sharedModel.getSource(),\n outputs: this.getCellOutputs(tr.cellIndex),\n wasCreated: !tr.wasModified,\n wasModified: tr.wasModified || false,\n previousSource: tr.previousContent,\n };\n snapshots.push(snapshot);\n }\n });\n return snapshots;\n }\n /**\n * 셀 출력 가져오기\n */\n getCellOutputs(cellIndex) {\n const cells = this.notebook.content.model?.cells;\n if (!cells || cellIndex >= cells.length)\n return [];\n const cell = cells.get(cellIndex);\n if (cell.type !== 'code')\n return [];\n const outputs = [];\n const codeCell = cell; // ICellModel doesn't expose outputs directly\n if (codeCell.outputs) {\n for (let i = 0; i < codeCell.outputs.length; i++) {\n outputs.push(codeCell.outputs.get(i)?.toJSON?.() || {});\n }\n }\n return outputs;\n }\n /**\n * 특정 체크포인트 조회\n */\n getCheckpoint(stepNumber) {\n return this.checkpoints.get(stepNumber);\n }\n /**\n * 모든 체크포인트 조회\n */\n getAllCheckpoints() {\n return Array.from(this.checkpoints.values())\n .sort((a, b) => a.stepNumber - b.stepNumber);\n }\n /**\n * 가장 최근 체크포인트 조회\n */\n getLatestCheckpoint() {\n if (this.checkpoints.size === 0)\n return undefined;\n const maxStep = Math.max(...this.checkpoints.keys());\n return this.checkpoints.get(maxStep);\n }\n /**\n * 특정 체크포인트로 롤백\n * - 해당 체크포인트 이후에 생성된 셀 삭제\n * - 해당 체크포인트 이후에 수정된 셀 복원\n */\n async rollbackTo(stepNumber) {\n const checkpoint = this.checkpoints.get(stepNumber);\n if (!checkpoint) {\n return {\n success: false,\n rolledBackTo: -1,\n deletedCells: [],\n restoredCells: [],\n error: `Checkpoint for step ${stepNumber} not found`,\n };\n }\n console.log(`[CheckpointManager] Rolling back to step ${stepNumber}`);\n const cells = this.notebook.content.model?.cells;\n if (!cells) {\n return {\n success: false,\n rolledBackTo: stepNumber,\n deletedCells: [],\n restoredCells: [],\n error: 'Notebook cells not accessible',\n };\n }\n const deletedCells = [];\n const restoredCells = [];\n try {\n // 1. 체크포인트 이후에 생성된 셀들 식별 및 삭제\n const checkpointCreatedSet = new Set(checkpoint.createdCellIndices);\n const cellsToDelete = Array.from(this.createdCellIndices)\n .filter(idx => !checkpointCreatedSet.has(idx))\n .sort((a, b) => b - a); // 뒤에서부터 삭제 (인덱스 변경 방지)\n for (const cellIndex of cellsToDelete) {\n if (cellIndex < cells.length) {\n this.notebook.content.model?.sharedModel.deleteCell(cellIndex);\n deletedCells.push(cellIndex);\n console.log(`[CheckpointManager] Deleted cell at index ${cellIndex}`);\n }\n }\n // 2. 체크포인트 이후에 수정된 셀들 복원\n const laterCheckpoints = Array.from(this.checkpoints.values())\n .filter(cp => cp.stepNumber > stepNumber);\n for (const laterCp of laterCheckpoints) {\n for (const snapshot of laterCp.cellSnapshots) {\n if (snapshot.wasModified && snapshot.previousSource !== undefined) {\n // 삭제로 인한 인덱스 조정 계산\n const deletedBefore = deletedCells.filter(d => d < snapshot.index).length;\n const adjustedIndex = snapshot.index - deletedBefore;\n if (adjustedIndex >= 0 && adjustedIndex < cells.length) {\n const cell = cells.get(adjustedIndex);\n cell.sharedModel.setSource(snapshot.previousSource);\n restoredCells.push(adjustedIndex);\n console.log(`[CheckpointManager] Restored cell at index ${adjustedIndex}`);\n }\n }\n }\n }\n // 3. 추적 상태 업데이트\n this.createdCellIndices = new Set(checkpoint.createdCellIndices);\n this.modifiedCellIndices = new Set(checkpoint.modifiedCellIndices);\n this.variableNames = new Set(checkpoint.variableNames);\n // 4. 롤백된 체크포인트 이후의 체크포인트들 삭제\n for (const [step, _] of this.checkpoints) {\n if (step > stepNumber) {\n this.checkpoints.delete(step);\n }\n }\n console.log(`[CheckpointManager] Rollback complete: deleted ${deletedCells.length} cells, ` +\n `restored ${restoredCells.length} cells`);\n return {\n success: true,\n rolledBackTo: stepNumber,\n deletedCells,\n restoredCells,\n };\n }\n catch (error) {\n console.error('[CheckpointManager] Rollback failed:', error);\n return {\n success: false,\n rolledBackTo: stepNumber,\n deletedCells,\n restoredCells,\n error: error.message || 'Unknown rollback error',\n };\n }\n }\n /**\n * 가장 최근 체크포인트로 롤백\n */\n async rollbackToLatest() {\n const latest = this.getLatestCheckpoint();\n if (!latest) {\n return {\n success: false,\n rolledBackTo: -1,\n deletedCells: [],\n restoredCells: [],\n error: 'No checkpoints available',\n };\n }\n return this.rollbackTo(latest.stepNumber);\n }\n /**\n * 특정 스텝 이전 체크포인트로 롤백\n */\n async rollbackBefore(stepNumber) {\n const checkpointSteps = Array.from(this.checkpoints.keys())\n .filter(step => step < stepNumber)\n .sort((a, b) => b - a);\n if (checkpointSteps.length === 0) {\n return {\n success: false,\n rolledBackTo: -1,\n deletedCells: [],\n restoredCells: [],\n error: `No checkpoint found before step ${stepNumber}`,\n };\n }\n return this.rollbackTo(checkpointSteps[0]);\n }\n /**\n * 모든 체크포인트 삭제\n */\n clearAllCheckpoints() {\n this.checkpoints.clear();\n console.log('[CheckpointManager] All checkpoints cleared');\n }\n /**\n * 관리자 상태 반환\n */\n getState() {\n const steps = Array.from(this.checkpoints.keys()).sort((a, b) => a - b);\n return {\n config: { ...this.config },\n checkpointCount: this.checkpoints.size,\n oldestCheckpoint: steps.length > 0 ? steps[0] : undefined,\n latestCheckpoint: steps.length > 0 ? steps[steps.length - 1] : undefined,\n };\n }\n /**\n * 상태 출력 (디버깅용)\n */\n printStatus() {\n const state = this.getState();\n console.log('[CheckpointManager] Status:');\n console.log(` - Max checkpoints: ${state.config.maxCheckpoints}`);\n console.log(` - Checkpoint count: ${state.checkpointCount}`);\n console.log(` - Oldest: step ${state.oldestCheckpoint ?? 'N/A'}`);\n console.log(` - Latest: step ${state.latestCheckpoint ?? 'N/A'}`);\n console.log(` - Created cells: ${Array.from(this.createdCellIndices).join(', ') || 'none'}`);\n console.log(` - Modified cells: ${Array.from(this.modifiedCellIndices).join(', ') || 'none'}`);\n }\n /**\n * 현재 추적 중인 생성된 셀 인덱스들 반환\n */\n getCreatedCellIndices() {\n return Array.from(this.createdCellIndices);\n }\n /**\n * 현재 추적 중인 수정된 셀 인덱스들 반환\n */\n getModifiedCellIndices() {\n return Array.from(this.modifiedCellIndices);\n }\n /**\n * 현재 추적 중인 변수 이름들 반환\n */\n getVariableNames() {\n return Array.from(this.variableNames);\n }\n}\nexport default CheckpointManager;\n","/**\n * ContextManager - 컨텍스트 윈도우 관리\n *\n * 토큰 예산 관리 및 적응형 컨텍스트 축소로 LLM 호출 신뢰성 향상\n * - 토큰 추정 (chars / 4)\n * - 우선순위 기반 셀 관리\n * - 예산 초과 시 자동 축소\n */\nimport { DEFAULT_CONTEXT_BUDGET, } from '../types/auto-agent';\n/**\n * ContextManager 클래스\n * 컨텍스트 윈도우 관리 및 토큰 예산 관리\n */\nexport class ContextManager {\n constructor(budget) {\n this.lastUsage = null;\n this.budget = {\n ...DEFAULT_CONTEXT_BUDGET,\n ...budget,\n };\n }\n /**\n * 예산 설정 업데이트\n */\n updateBudget(budget) {\n this.budget = {\n ...this.budget,\n ...budget,\n };\n console.log('[ContextManager] Budget updated:', this.budget);\n }\n /**\n * 현재 예산 설정 반환\n */\n getBudget() {\n return { ...this.budget };\n }\n /**\n * 텍스트의 토큰 수 추정\n * 근사값: 문자 수 / 4\n */\n estimateTokens(text) {\n if (!text)\n return 0;\n return Math.ceil(text.length / 4);\n }\n /**\n * 셀 컨텍스트의 토큰 수 추정\n */\n estimateCellTokens(cell) {\n let tokens = this.estimateTokens(cell.source);\n if (cell.output) {\n tokens += this.estimateTokens(cell.output);\n }\n return tokens;\n }\n /**\n * 노트북 컨텍스트의 전체 토큰 사용량 계산\n */\n calculateUsage(context) {\n const cellTokens = context.recentCells.reduce((sum, cell) => sum + this.estimateCellTokens(cell), 0);\n const variableTokens = this.estimateTokens(context.definedVariables.join(', '));\n const libraryTokens = this.estimateTokens(context.importedLibraries.join(', '));\n const totalTokens = cellTokens + variableTokens + libraryTokens;\n const availableTokens = this.budget.maxTokens - this.budget.reservedForResponse;\n const usagePercent = totalTokens / availableTokens;\n const usage = {\n totalTokens,\n cellTokens,\n variableTokens,\n libraryTokens,\n reservedTokens: this.budget.reservedForResponse,\n usagePercent,\n };\n this.lastUsage = usage;\n return usage;\n }\n /**\n * 마지막 토큰 사용량 반환\n */\n getLastUsage() {\n return this.lastUsage;\n }\n /**\n * 셀들에 우선순위 부여\n * - critical: currentCellIndex에 해당하는 셀\n * - high: 최근 3개 셀\n * - medium: 최근 5개 셀 (high 제외)\n * - low: 나머지 셀\n */\n prioritizeCells(cells, currentCellIndex) {\n if (cells.length === 0)\n return [];\n // 인덱스 기준 내림차순 정렬 (최근 셀이 앞으로)\n const sortedCells = [...cells].sort((a, b) => b.index - a.index);\n return sortedCells.map((cell, sortedIndex) => {\n let priority;\n // currentCellIndex가 지정된 경우 해당 셀은 critical\n if (currentCellIndex !== undefined && cell.index === currentCellIndex) {\n priority = 'critical';\n }\n // 최근 3개 셀은 high\n else if (sortedIndex < 3) {\n priority = 'high';\n }\n // 다음 5개 셀 (3-7)은 medium\n else if (sortedIndex < 8) {\n priority = 'medium';\n }\n // 나머지는 low\n else {\n priority = 'low';\n }\n return {\n ...cell,\n priority,\n tokenEstimate: this.estimateCellTokens(cell),\n };\n });\n }\n /**\n * 우선순위 레벨 숫자값 반환 (비교용)\n */\n getPriorityValue(priority) {\n switch (priority) {\n case 'critical':\n return 4;\n case 'high':\n return 3;\n case 'medium':\n return 2;\n case 'low':\n return 1;\n default:\n return 0;\n }\n }\n /**\n * 컨텍스트 축소 필요 여부 확인\n */\n isPruningRequired(context) {\n const usage = this.calculateUsage(context);\n const availableTokens = this.budget.maxTokens - this.budget.reservedForResponse;\n return usage.totalTokens > availableTokens;\n }\n /**\n * 경고 임계값 초과 여부 확인\n */\n isWarningThresholdExceeded(context) {\n const usage = this.calculateUsage(context);\n return usage.usagePercent >= this.budget.warningThreshold;\n }\n /**\n * 컨텍스트 축소\n * 우선순위가 낮은 셀부터 제거하거나 축소\n */\n pruneContext(context, targetTokens, currentCellIndex) {\n const target = targetTokens ?? (this.budget.maxTokens - this.budget.reservedForResponse);\n const prioritizedCells = this.prioritizeCells(context.recentCells, currentCellIndex);\n // 현재 사용량 계산\n const originalUsage = this.calculateUsage(context);\n const originalTokens = originalUsage.totalTokens;\n // 이미 예산 내에 있으면 그대로 반환\n if (originalTokens <= target) {\n return {\n context,\n result: {\n originalTokens,\n prunedTokens: originalTokens,\n removedCellCount: 0,\n truncatedCellCount: 0,\n preservedCells: context.recentCells.map(c => c.index),\n },\n };\n }\n console.log(`[ContextManager] Pruning required: ${originalTokens} → ${target} tokens`);\n // 우선순위 순으로 정렬 (낮은 우선순위가 먼저 제거됨)\n const sortedByPriority = [...prioritizedCells].sort((a, b) => this.getPriorityValue(a.priority) - this.getPriorityValue(b.priority));\n const preservedCells = [];\n let currentTokens = originalUsage.variableTokens + originalUsage.libraryTokens;\n let removedCount = 0;\n let truncatedCount = 0;\n // 우선순위 높은 것부터 유지 (역순으로 처리)\n for (let i = sortedByPriority.length - 1; i >= 0; i--) {\n const cell = sortedByPriority[i];\n const cellTokens = cell.tokenEstimate;\n // 예산 내에 들어가면 유지\n if (currentTokens + cellTokens <= target) {\n preservedCells.push(cell);\n currentTokens += cellTokens;\n }\n // critical 또는 high 우선순위는 축소해서라도 유지\n else if (cell.priority === 'critical' || cell.priority === 'high') {\n const remainingBudget = target - currentTokens;\n if (remainingBudget > 100) { // 최소 100 토큰 이상 남아있어야 축소\n const truncatedSource = this.truncateText(cell.source, remainingBudget * 4 // 토큰 → 문자 변환\n );\n const truncatedCell = {\n ...cell,\n source: truncatedSource,\n output: undefined,\n tokenEstimate: this.estimateTokens(truncatedSource),\n };\n preservedCells.push(truncatedCell);\n currentTokens += truncatedCell.tokenEstimate;\n truncatedCount++;\n console.log(`[ContextManager] Truncated ${cell.priority} cell ${cell.index}: ` +\n `${cellTokens} → ${truncatedCell.tokenEstimate} tokens`);\n }\n else {\n removedCount++;\n console.log(`[ContextManager] Removed ${cell.priority} cell ${cell.index} ` +\n `(insufficient budget: ${remainingBudget} tokens)`);\n }\n }\n // 그 외 우선순위는 제거\n else {\n removedCount++;\n console.log(`[ContextManager] Removed ${cell.priority} cell ${cell.index}`);\n }\n }\n // 원래 인덱스 순서로 복원\n const prunedCells = preservedCells\n .sort((a, b) => a.index - b.index)\n .map(({ priority, tokenEstimate, ...cellContext }) => cellContext);\n const prunedContext = {\n ...context,\n recentCells: prunedCells,\n };\n const result = {\n originalTokens,\n prunedTokens: currentTokens,\n removedCellCount: removedCount,\n truncatedCellCount: truncatedCount,\n preservedCells: prunedCells.map(c => c.index),\n };\n console.log(`[ContextManager] Pruning complete: ` +\n `${originalTokens} → ${currentTokens} tokens, ` +\n `removed ${removedCount}, truncated ${truncatedCount}`);\n return { context: prunedContext, result };\n }\n /**\n * 텍스트 축소 (마지막 부분 유지)\n */\n truncateText(text, maxChars) {\n if (text.length <= maxChars)\n return text;\n // 마지막 부분 유지 (최근 코드가 더 중요)\n const truncated = text.slice(-maxChars);\n // 줄 경계에서 자르기\n const firstNewline = truncated.indexOf('\\n');\n if (firstNewline > 0 && firstNewline < maxChars * 0.2) {\n return '...\\n' + truncated.slice(firstNewline + 1);\n }\n return '...' + truncated;\n }\n /**\n * 노트북 컨텍스트 추출 및 최적화\n * 예산을 초과하면 자동으로 축소\n */\n extractOptimizedContext(context, currentCellIndex) {\n // 경고 임계값 체크\n if (this.isWarningThresholdExceeded(context)) {\n const usage = this.getLastUsage();\n console.log(`[ContextManager] Warning: Token usage at ${(usage.usagePercent * 100).toFixed(1)}% ` +\n `(threshold: ${this.budget.warningThreshold * 100}%)`);\n }\n // 축소 필요 여부 확인\n if (this.isPruningRequired(context)) {\n const { context: prunedContext, result } = this.pruneContext(context, undefined, currentCellIndex);\n const usage = this.calculateUsage(prunedContext);\n return {\n context: prunedContext,\n usage,\n pruneResult: result,\n };\n }\n return {\n context,\n usage: this.calculateUsage(context),\n };\n }\n /**\n * 관리자 상태 반환\n */\n getState() {\n return {\n budget: { ...this.budget },\n currentUsage: this.lastUsage ?? {\n totalTokens: 0,\n cellTokens: 0,\n variableTokens: 0,\n libraryTokens: 0,\n reservedTokens: this.budget.reservedForResponse,\n usagePercent: 0,\n },\n isPruningRequired: false,\n lastPruneTimestamp: undefined,\n };\n }\n /**\n * 상태 출력 (디버깅용)\n */\n printStatus() {\n console.log('[ContextManager] Status:');\n console.log(` - Max tokens: ${this.budget.maxTokens}`);\n console.log(` - Reserved for response: ${this.budget.reservedForResponse}`);\n console.log(` - Warning threshold: ${this.budget.warningThreshold * 100}%`);\n if (this.lastUsage) {\n console.log(` - Current usage: ${this.lastUsage.totalTokens} tokens`);\n console.log(` - Cells: ${this.lastUsage.cellTokens}`);\n console.log(` - Variables: ${this.lastUsage.variableTokens}`);\n console.log(` - Libraries: ${this.lastUsage.libraryTokens}`);\n console.log(` - Usage percent: ${(this.lastUsage.usagePercent * 100).toFixed(1)}%`);\n }\n }\n}\nexport default ContextManager;\n","/**\n * State Verifier Service\n * Phase 1: 상태 검증 레이어 - 각 단계 실행 후 상태 검증으로 silent failure 감지\n */\nimport { CONFIDENCE_THRESHOLDS, } from '../types/auto-agent';\n// 기본 가중치 설정\nconst DEFAULT_WEIGHTS = {\n outputMatch: 0.3,\n variableCreation: 0.3,\n noExceptions: 0.25,\n executionComplete: 0.15,\n};\n/**\n * 상태 검증 서비스\n * - 스텝 실행 후 예상 상태와 실제 상태 비교\n * - 신뢰도 점수 계산\n * - 리플래닝 트리거 결정\n */\nexport class StateVerifier {\n constructor(apiService) {\n this.verificationHistory = [];\n this.apiService = apiService;\n }\n /**\n * 스텝 실행 결과 검증\n * @param context 검증 컨텍스트\n * @returns 검증 결과\n */\n async verifyStepState(context) {\n console.log('[StateVerifier] Verifying step state:', {\n stepNumber: context.stepNumber,\n executionStatus: context.executionResult.status,\n });\n const mismatches = [];\n const factors = {\n outputMatch: 1.0,\n variableCreation: 1.0,\n noExceptions: 1.0,\n executionComplete: 1.0,\n };\n // 1. 실행 완료 여부 확인\n if (context.executionResult.status === 'error') {\n factors.noExceptions = 0;\n factors.executionComplete = 0;\n mismatches.push({\n type: 'exception_occurred',\n severity: 'critical',\n description: `실행 중 예외 발생: ${context.executionResult.error?.ename || 'Unknown'}`,\n expected: '에러 없음',\n actual: context.executionResult.error?.evalue || 'Unknown error',\n suggestion: this.getSuggestionForError(context.executionResult.error?.ename),\n });\n }\n // 2. 변수 생성 검증 (expectation이 있는 경우)\n if (context.expectation?.expectedVariables) {\n const createdVariables = this.getNewVariables(context.previousVariables, context.currentVariables);\n const { score, variableMismatches } = this.verifyVariables(context.expectation.expectedVariables, createdVariables);\n factors.variableCreation = score;\n mismatches.push(...variableMismatches);\n }\n // 3. 출력 패턴 검증 (expectation이 있는 경우)\n if (context.expectation?.expectedOutputPatterns) {\n const { score, outputMismatches } = this.verifyOutputPatterns(context.expectation.expectedOutputPatterns, context.executionResult.stdout + context.executionResult.result);\n factors.outputMatch = score;\n mismatches.push(...outputMismatches);\n }\n // 4. Import 검증\n if (context.expectation?.expectedImports) {\n const importMismatches = this.verifyImports(context.expectation.expectedImports, context.executionResult);\n mismatches.push(...importMismatches);\n }\n // 5. 신뢰도 점수 계산\n const confidenceDetails = this.calculateConfidence(factors);\n // 6. 권장 사항 결정\n const recommendation = this.determineRecommendation(confidenceDetails.overall);\n const result = {\n isValid: mismatches.filter(m => m.severity === 'critical').length === 0,\n confidence: confidenceDetails.overall,\n confidenceDetails,\n mismatches,\n recommendation,\n timestamp: Date.now(),\n };\n // 이력 저장\n this.verificationHistory.push(result);\n console.log('[StateVerifier] Verification result:', {\n isValid: result.isValid,\n confidence: result.confidence,\n recommendation: result.recommendation,\n mismatchCount: result.mismatches.length,\n });\n return result;\n }\n /**\n * 백엔드 API를 통한 상세 검증 (복잡한 케이스)\n */\n async verifyWithBackend(stepNumber, executedCode, executionResult, expectation, previousVariables = [], currentVariables = []) {\n try {\n const request = {\n stepNumber,\n executedCode,\n executionOutput: executionResult.stdout + '\\n' + JSON.stringify(executionResult.result),\n executionStatus: executionResult.status,\n errorMessage: executionResult.error?.evalue,\n expectedVariables: expectation?.expectedVariables,\n expectedOutputPatterns: expectation?.expectedOutputPatterns,\n previousVariables,\n currentVariables,\n };\n const response = await this.apiService.verifyState(request);\n return response.verification;\n }\n catch (error) {\n console.error('[StateVerifier] Backend verification failed, using local verification:', error);\n // 폴백: 로컬 검증 수행\n return this.verifyStepState({\n stepNumber,\n executionResult,\n expectation,\n previousVariables,\n currentVariables,\n notebookContext: { cellCount: 0, recentCells: [], importedLibraries: [], definedVariables: [] },\n });\n }\n }\n /**\n * 신뢰도 점수 계산\n */\n calculateConfidence(factors, weights = DEFAULT_WEIGHTS) {\n const overall = factors.outputMatch * weights.outputMatch +\n factors.variableCreation * weights.variableCreation +\n factors.noExceptions * weights.noExceptions +\n factors.executionComplete * weights.executionComplete;\n return {\n overall: Math.max(0, Math.min(1, overall)),\n factors,\n weights,\n };\n }\n /**\n * 신뢰도에 따른 권장 사항 결정\n */\n determineRecommendation(confidence) {\n if (confidence >= CONFIDENCE_THRESHOLDS.PROCEED) {\n return 'proceed';\n }\n else if (confidence >= CONFIDENCE_THRESHOLDS.WARNING) {\n return 'warning';\n }\n else if (confidence >= CONFIDENCE_THRESHOLDS.REPLAN) {\n return 'replan';\n }\n else {\n return 'escalate';\n }\n }\n /**\n * 이전/이후 변수 목록 비교하여 새로 생성된 변수 추출\n */\n getNewVariables(previousVars, currentVars) {\n const previousSet = new Set(previousVars);\n return currentVars.filter(v => !previousSet.has(v));\n }\n /**\n * 변수 생성 검증\n */\n verifyVariables(expectedVars, createdVars) {\n const mismatches = [];\n const createdSet = new Set(createdVars);\n let matchCount = 0;\n for (const expected of expectedVars) {\n if (createdSet.has(expected)) {\n matchCount++;\n }\n else {\n mismatches.push({\n type: 'variable_missing',\n severity: 'major',\n description: `예상 변수 '${expected}'가 생성되지 않음`,\n expected,\n actual: '(없음)',\n suggestion: `변수 '${expected}'를 생성하는 코드가 올바르게 실행되었는지 확인하세요`,\n });\n }\n }\n const score = expectedVars.length > 0 ? matchCount / expectedVars.length : 1;\n return { score, variableMismatches: mismatches };\n }\n /**\n * 출력 패턴 검증\n */\n verifyOutputPatterns(patterns, output) {\n const mismatches = [];\n let matchCount = 0;\n for (const pattern of patterns) {\n try {\n const regex = new RegExp(pattern, 'i');\n if (regex.test(output)) {\n matchCount++;\n }\n else {\n mismatches.push({\n type: 'output_mismatch',\n severity: 'minor',\n description: `출력에서 예상 패턴을 찾을 수 없음`,\n expected: pattern,\n actual: output.substring(0, 100) + (output.length > 100 ? '...' : ''),\n });\n }\n }\n catch (e) {\n // 정규식 오류 시 문자열 포함 검사\n if (output.includes(pattern)) {\n matchCount++;\n }\n }\n }\n const score = patterns.length > 0 ? matchCount / patterns.length : 1;\n return { score, outputMismatches: mismatches };\n }\n /**\n * Import 검증\n */\n verifyImports(expectedImports, executionResult) {\n const mismatches = [];\n // 에러가 ModuleNotFoundError 또는 ImportError인 경우\n if (executionResult.error) {\n const errorName = executionResult.error.ename;\n const errorValue = executionResult.error.evalue;\n if (errorName === 'ModuleNotFoundError' || errorName === 'ImportError') {\n // 어떤 모듈이 실패했는지 추출\n const moduleMatch = errorValue.match(/No module named '([^']+)'/);\n const failedModule = moduleMatch ? moduleMatch[1] : 'unknown';\n mismatches.push({\n type: 'import_failed',\n severity: 'critical',\n description: `모듈 '${failedModule}' import 실패`,\n expected: `${failedModule} 모듈이 설치되어 있어야 함`,\n actual: errorValue,\n suggestion: `pip install ${failedModule} 또는 conda install ${failedModule}로 설치하세요`,\n });\n }\n }\n return mismatches;\n }\n /**\n * 에러 타입에 따른 복구 제안\n */\n getSuggestionForError(errorName) {\n const suggestions = {\n 'ModuleNotFoundError': '누락된 패키지를 설치하세요 (pip install)',\n 'NameError': '변수가 정의되었는지 확인하세요. 이전 셀을 먼저 실행해야 할 수 있습니다.',\n 'SyntaxError': '코드 문법을 확인하세요',\n 'TypeError': '함수 인자 타입을 확인하세요',\n 'ValueError': '입력 값의 범위나 형식을 확인하세요',\n 'KeyError': '딕셔너리 키가 존재하는지 확인하세요',\n 'IndexError': '리스트/배열 인덱스가 범위 내인지 확인하세요',\n 'FileNotFoundError': '파일 경로가 올바른지 확인하세요',\n 'AttributeError': '객체에 해당 속성/메서드가 있는지 확인하세요',\n };\n return suggestions[errorName || ''] || '에러 메시지를 확인하고 코드를 수정하세요';\n }\n /**\n * 최근 검증 이력 조회\n */\n getRecentHistory(count = 5) {\n return this.verificationHistory.slice(-count);\n }\n /**\n * 전체 신뢰도 트렌드 분석\n */\n analyzeConfidenceTrend() {\n if (this.verificationHistory.length < 2) {\n return {\n average: this.verificationHistory[0]?.confidence ?? 1,\n trend: 'stable',\n criticalCount: this.verificationHistory.filter(v => !v.isValid).length,\n };\n }\n const confidences = this.verificationHistory.map(v => v.confidence);\n const average = confidences.reduce((a, b) => a + b, 0) / confidences.length;\n // 최근 3개와 이전 3개 비교\n const recentAvg = confidences.slice(-3).reduce((a, b) => a + b, 0) / Math.min(3, confidences.length);\n const previousAvg = confidences.slice(0, -3).reduce((a, b) => a + b, 0) / Math.max(1, confidences.length - 3);\n let trend = 'stable';\n if (recentAvg > previousAvg + 0.1) {\n trend = 'improving';\n }\n else if (recentAvg < previousAvg - 0.1) {\n trend = 'declining';\n }\n return {\n average,\n trend,\n criticalCount: this.verificationHistory.filter(v => !v.isValid).length,\n };\n }\n /**\n * 검증 이력 초기화\n */\n clearHistory() {\n this.verificationHistory = [];\n }\n}\n","/**\n * Task Service - Handles notebook generation tasks and progress tracking\n */\nexport class TaskService {\n constructor(baseUrl = '/hdsp-agent') {\n this.baseUrl = baseUrl;\n this.eventSources = new Map();\n }\n /**\n * Get cookie value by name\n */\n getCookie(name) {\n const value = `; ${document.cookie}`;\n const parts = value.split(`; ${name}=`);\n if (parts.length === 2) {\n return parts.pop()?.split(';').shift() || '';\n }\n return '';\n }\n /**\n * Get CSRF token from cookie\n */\n getCsrfToken() {\n return this.getCookie('_xsrf');\n }\n /**\n * Get headers with CSRF token for POST requests\n */\n getHeaders() {\n return {\n 'Content-Type': 'application/json',\n 'X-XSRFToken': this.getCsrfToken()\n };\n }\n /**\n * Start a new notebook generation task\n */\n async generateNotebook(request) {\n const response = await fetch(`${this.baseUrl}/notebook/generate`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify(request)\n });\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ message: 'Failed to start notebook generation' }));\n throw new Error(error.message || 'Failed to start notebook generation');\n }\n return response.json();\n }\n /**\n * Get current task status\n */\n async getTaskStatus(taskId) {\n const response = await fetch(`${this.baseUrl}/task/${taskId}/status`);\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ message: 'Failed to get task status' }));\n throw new Error(error.message || 'Failed to get task status');\n }\n return response.json();\n }\n /**\n * Subscribe to task progress updates via Server-Sent Events\n */\n subscribeToTaskProgress(taskId, onProgress, onError, onComplete) {\n // Close existing connection if any\n this.unsubscribeFromTask(taskId);\n const eventSource = new EventSource(`${this.baseUrl}/task/${taskId}/stream`);\n eventSource.onmessage = (event) => {\n try {\n const data = JSON.parse(event.data);\n onProgress(data);\n // Close connection if task is done\n if (['completed', 'failed', 'cancelled'].includes(data.status)) {\n this.unsubscribeFromTask(taskId);\n if (onComplete) {\n onComplete();\n }\n }\n }\n catch (error) {\n console.error('Failed to parse SSE message:', error);\n if (onError) {\n onError(error);\n }\n }\n };\n eventSource.onerror = (event) => {\n console.error('SSE connection error:', event);\n this.unsubscribeFromTask(taskId);\n if (onError) {\n onError(new Error('Connection to server lost'));\n }\n };\n this.eventSources.set(taskId, eventSource);\n // Return unsubscribe function\n return () => this.unsubscribeFromTask(taskId);\n }\n /**\n * Unsubscribe from task progress updates\n */\n unsubscribeFromTask(taskId) {\n const eventSource = this.eventSources.get(taskId);\n if (eventSource) {\n eventSource.close();\n this.eventSources.delete(taskId);\n }\n }\n /**\n * Cancel a running task\n */\n async cancelTask(taskId) {\n const response = await fetch(`${this.baseUrl}/task/${taskId}/cancel`, {\n method: 'POST',\n headers: this.getHeaders()\n });\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ message: 'Failed to cancel task' }));\n throw new Error(error.message || 'Failed to cancel task');\n }\n // Close SSE connection\n this.unsubscribeFromTask(taskId);\n }\n /**\n * Clean up all active connections\n */\n dispose() {\n for (const [taskId, _] of this.eventSources) {\n this.unsubscribeFromTask(taskId);\n }\n }\n}\n","/**\n * ToolExecutor - HF Jupyter Agent 스타일의 Tool 실행기\n *\n * 3가지 도구 실행 및 결과 캡처:\n * - jupyter_cell: 코드 셀 생성/수정/실행\n * - markdown: 마크다운 셀 생성/수정\n * - final_answer: 작업 완료 신호\n */\nimport { NotebookActions } from '@jupyterlab/notebook';\nimport { ToolRegistry, BUILTIN_TOOL_DEFINITIONS, DANGEROUS_COMMAND_PATTERNS } from './ToolRegistry';\nexport class ToolExecutor {\n constructor(notebook, sessionContext, apiService) {\n this.autoScrollEnabled = true;\n this.apiService = null;\n /**\n * 마지막으로 생성된 셀 인덱스 추적 (순차 삽입용)\n */\n this.lastCreatedCellIndex = -1;\n // Generate unique instance ID for debugging\n this.instanceId = `ToolExecutor-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n // 디버깅: 생성자에 전달된 노트북 로그\n console.log(`[ToolExecutor ${this.instanceId}] Constructor - notebook path:`, notebook?.context?.path);\n console.log(`[ToolExecutor ${this.instanceId}] Constructor - notebook title:`, notebook?.title?.label);\n this.notebook = notebook;\n this.sessionContext = sessionContext;\n this.registry = ToolRegistry.getInstance();\n this.apiService = apiService || null;\n // 빌트인 도구들 등록\n this.registerBuiltinTools();\n }\n /**\n * Set ApiService instance (for file resolution)\n */\n setApiService(apiService) {\n this.apiService = apiService;\n }\n /**\n * Get current notebook directory\n */\n getNotebookDir() {\n const notebookPath = this.notebook.context.path;\n if (!notebookPath)\n return undefined;\n const pathParts = notebookPath.split('/');\n pathParts.pop(); // Remove filename\n return pathParts.join('/') || undefined;\n }\n /**\n * 노트북 모델이 준비될 때까지 대기\n * 사용자가 새로고침할 필요 없이 자동으로 모델을 기다림\n */\n async ensureModelReady() {\n console.log(`[ToolExecutor ${this.instanceId}] ensureModelReady - notebook:`, this.notebook?.context?.path);\n console.log(`[ToolExecutor ${this.instanceId}] ensureModelReady - this:`, this);\n console.log(`[ToolExecutor ${this.instanceId}] ensureModelReady - content:`, this.notebook?.content ? 'exists' : 'null');\n console.log(`[ToolExecutor ${this.instanceId}] ensureModelReady - model:`, this.notebook?.content?.model ? 'exists' : 'null');\n if (!this.notebook.content.model) {\n console.log('[ToolExecutor] Model not ready, waiting for context.ready...');\n // 노트북 컨텍스트가 준비될 때까지 대기\n await this.notebook.context.ready;\n console.log('[ToolExecutor] context.ready completed');\n }\n if (!this.notebook.content.model) {\n console.error('[ToolExecutor] Model still not available after context.ready!');\n console.error('[ToolExecutor] notebook:', this.notebook);\n console.error('[ToolExecutor] notebook.content:', this.notebook.content);\n throw new Error('Notebook model not available after waiting for context ready');\n }\n console.log('[ToolExecutor] Model ready!');\n }\n /**\n * 빌트인 도구들을 레지스트리에 등록\n */\n registerBuiltinTools() {\n // CRITICAL: Always re-register tools to update 'this' binding for current ToolExecutor instance\n // ToolRegistry is singleton, so we must overwrite executors to use the correct instance\n // jupyter_cell 도구 등록\n const jupyterCellDef = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'jupyter_cell');\n if (jupyterCellDef) {\n this.registry.register({\n ...jupyterCellDef,\n executor: async (params, context) => {\n return this.executeJupyterCell(params, context.stepNumber);\n },\n });\n }\n // markdown 도구 등록\n const markdownDef = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'markdown');\n if (markdownDef) {\n this.registry.register({\n ...markdownDef,\n executor: async (params, context) => {\n return this.executeMarkdown(params, context.stepNumber);\n },\n });\n }\n // final_answer 도구 등록\n const finalAnswerDef = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'final_answer');\n if (finalAnswerDef) {\n this.registry.register({\n ...finalAnswerDef,\n executor: async (params, _context) => {\n return this.executeFinalAnswer(params);\n },\n });\n }\n console.log(`[ToolExecutor ${this.instanceId}] Built-in tools registered (overwritten)`);\n // ─────────────────────────────────────────────────────────────────────────\n // 확장 도구들 등록\n // ─────────────────────────────────────────────────────────────────────────\n // read_file 도구 등록\n const readFileDef = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'read_file');\n if (readFileDef && !this.registry.hasTool('read_file')) {\n this.registry.register({\n ...readFileDef,\n executor: async (params, _context) => {\n return this.executeReadFile(params);\n },\n });\n }\n // write_file 도구 등록\n const writeFileDef = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'write_file');\n if (writeFileDef && !this.registry.hasTool('write_file')) {\n this.registry.register({\n ...writeFileDef,\n executor: async (params, _context) => {\n return this.executeWriteFile(params);\n },\n });\n }\n // list_files 도구 등록\n const listFilesDef = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'list_files');\n if (listFilesDef && !this.registry.hasTool('list_files')) {\n this.registry.register({\n ...listFilesDef,\n executor: async (params, _context) => {\n return this.executeListFiles(params);\n },\n });\n }\n // execute_command_tool 도구 등록 (조건부 승인)\n const executeCommandDef = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'execute_command_tool');\n if (executeCommandDef && !this.registry.hasTool('execute_command_tool')) {\n this.registry.register({\n ...executeCommandDef,\n executor: async (params, context) => {\n return this.executeCommand(params, context);\n },\n });\n }\n // search_files 도구 등록\n const searchFilesDef = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'search_files');\n if (searchFilesDef && !this.registry.hasTool('search_files')) {\n this.registry.register({\n ...searchFilesDef,\n executor: async (params, _context) => {\n return this.executeSearchFiles(params);\n },\n });\n }\n // ─────────────────────────────────────────────────────────────────────────\n // Phase 2 확장 도구들 등록\n // ─────────────────────────────────────────────────────────────────────────\n // install_package 도구 등록\n const installPackageDef = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'install_package');\n if (installPackageDef && !this.registry.hasTool('install_package')) {\n this.registry.register({\n ...installPackageDef,\n executor: async (params, _context) => {\n return this.executeInstallPackage(params);\n },\n });\n }\n // lint_file 도구 등록\n const lintFileDef = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'lint_file');\n if (lintFileDef && !this.registry.hasTool('lint_file')) {\n this.registry.register({\n ...lintFileDef,\n executor: async (params, _context) => {\n return this.executeLintFile(params);\n },\n });\n }\n // delete_cell 도구 등록\n const deleteCellDef = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'delete_cell');\n if (deleteCellDef && !this.registry.hasTool('delete_cell')) {\n this.registry.register({\n ...deleteCellDef,\n executor: async (params, _context) => {\n return this.executeDeleteCell(params);\n },\n });\n }\n // get_cell_output 도구 등록\n const getCellOutputDef = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'get_cell_output');\n if (getCellOutputDef && !this.registry.hasTool('get_cell_output')) {\n this.registry.register({\n ...getCellOutputDef,\n executor: async (params, _context) => {\n return this.executeGetCellOutput(params);\n },\n });\n }\n // create_notebook 도구 등록\n const createNotebookDef = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'create_notebook');\n if (createNotebookDef && !this.registry.hasTool('create_notebook')) {\n this.registry.register({\n ...createNotebookDef,\n executor: async (params, _context) => {\n return this.executeCreateNotebook(params);\n },\n });\n }\n // create_folder 도구 등록\n const createFolderDef = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'create_folder');\n if (createFolderDef && !this.registry.hasTool('create_folder')) {\n this.registry.register({\n ...createFolderDef,\n executor: async (params, _context) => {\n return this.executeCreateFolder(params);\n },\n });\n }\n // delete_file 도구 등록\n const deleteFileDef = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'delete_file');\n if (deleteFileDef && !this.registry.hasTool('delete_file')) {\n this.registry.register({\n ...deleteFileDef,\n executor: async (params, _context) => {\n return this.executeDeleteFile(params);\n },\n });\n }\n // ─────────────────────────────────────────────────────────────────────────\n // Phase 3 확장 도구들 등록 (Git/Test/Refactor)\n // ─────────────────────────────────────────────────────────────────────────\n // git_operations 도구 등록\n const gitOperationsDef = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'git_operations');\n if (gitOperationsDef && !this.registry.hasTool('git_operations')) {\n this.registry.register({\n ...gitOperationsDef,\n executor: async (params, context) => {\n return this.executeGitOperations(params, context);\n },\n });\n }\n // run_tests 도구 등록\n const runTestsDef = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'run_tests');\n if (runTestsDef && !this.registry.hasTool('run_tests')) {\n this.registry.register({\n ...runTestsDef,\n executor: async (params, _context) => {\n return this.executeRunTests(params);\n },\n });\n }\n // refactor_code 도구 등록\n const refactorCodeDef = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'refactor_code');\n if (refactorCodeDef && !this.registry.hasTool('refactor_code')) {\n this.registry.register({\n ...refactorCodeDef,\n executor: async (params, _context) => {\n return this.executeRefactorCode(params);\n },\n });\n }\n console.log('[ToolExecutor] Built-in tools registered');\n this.registry.printStatus();\n }\n /**\n * 승인 콜백 설정 (ApprovalDialog 연동용)\n */\n setApprovalCallback(callback) {\n this.registry.setApprovalCallback(callback);\n }\n /**\n * 승인 필요 여부 설정\n */\n setApprovalRequired(required) {\n this.registry.setApprovalRequired(required);\n }\n /**\n * 레지스트리 인스턴스 반환 (외부 도구 등록용)\n */\n getRegistry() {\n return this.registry;\n }\n /**\n * 커널이 idle 상태가 될 때까지 대기\n * @param timeout 최대 대기 시간 (ms)\n * @returns true if kernel became idle, false if timeout\n */\n async waitForKernelIdle(timeout = 10000) {\n const kernel = this.sessionContext.session?.kernel;\n if (!kernel) {\n console.warn('[ToolExecutor] No kernel available');\n return false;\n }\n const startTime = Date.now();\n const pollInterval = 100; // 100ms마다 체크\n return new Promise((resolve) => {\n const checkStatus = () => {\n const elapsed = Date.now() - startTime;\n const status = kernel.status;\n if (status === 'idle') {\n console.log('[ToolExecutor] Kernel is idle after', elapsed, 'ms');\n resolve(true);\n return;\n }\n if (elapsed >= timeout) {\n console.warn('[ToolExecutor] Kernel idle wait timeout after', timeout, 'ms, status:', status);\n resolve(false);\n return;\n }\n // 아직 idle이 아니면 다시 체크\n setTimeout(checkStatus, pollInterval);\n };\n checkStatus();\n });\n }\n /**\n * 자동 스크롤 설정\n */\n setAutoScroll(enabled) {\n this.autoScrollEnabled = enabled;\n }\n /**\n * 특정 셀로 스크롤 및 포커스\n */\n scrollToCell(cellIndex) {\n if (!this.autoScrollEnabled)\n return;\n const notebookContent = this.notebook.content;\n const cell = notebookContent.widgets[cellIndex];\n if (cell) {\n // 셀로 부드럽게 스크롤\n cell.node.scrollIntoView({\n behavior: 'smooth',\n block: 'center',\n });\n }\n }\n /**\n * Tool 실행 라우터 (레지스트리 기반)\n * @param call - 도구 호출 정보\n * @param stepNumber - 실행 계획의 단계 번호 (셀에 표시용)\n */\n async executeTool(call, stepNumber) {\n console.log('[ToolExecutor] executeTool called:', JSON.stringify(call, null, 2), 'stepNumber:', stepNumber);\n // 실행 컨텍스트 생성\n const context = {\n notebook: this.notebook,\n sessionContext: this.sessionContext,\n stepNumber,\n };\n // 레지스트리를 통해 도구 실행 (승인 게이트 포함)\n const result = await this.registry.executeTool(call.tool, call.parameters, context);\n console.log('[ToolExecutor] Tool result:', JSON.stringify(result, null, 2));\n return result;\n }\n /**\n * Step 번호 포맷팅 (스태킹 방지)\n * 기존 Step 주석이 있으면 교체, 없으면 추가\n */\n formatCodeWithStep(code, stepNumber) {\n if (stepNumber === undefined) {\n return code;\n }\n // 기존 Step 주석 제거 (스태킹 방지)\n // # [Step N] 또는 # [Step N.M] 패턴 매칭\n const stepPattern = /^# \\[Step \\d+(?:\\.\\d+)?\\]\\n/;\n const cleanCode = code.replace(stepPattern, '');\n // 새 Step 주석 추가\n return `# [Step ${stepNumber}]\\n${cleanCode}`;\n }\n /**\n * jupyter_cell 도구: 셀 생성/수정/실행\n * @param stepNumber - 실행 계획의 단계 번호 (셀에 주석으로 표시)\n */\n async executeJupyterCell(params, stepNumber) {\n console.log('[ToolExecutor] executeJupyterCell params:', params);\n const notebookContent = this.notebook.content;\n console.log('[ToolExecutor] notebook content available:', !!notebookContent);\n console.log('[ToolExecutor] notebook model available:', !!notebookContent?.model);\n let cellIndex;\n let wasModified = false;\n let operation = params.operation || 'CREATE';\n let previousContent;\n // Step 번호 포맷팅 (스태킹 방지)\n const codeWithStep = this.formatCodeWithStep(params.code, stepNumber);\n try {\n // 작업 유형에 따른 셀 처리\n if (params.cellIndex !== undefined && params.operation !== 'CREATE') {\n // MODIFY: 기존 셀 수정\n operation = 'MODIFY';\n cellIndex = params.cellIndex;\n // 수정 전 원본 내용 저장 (UI/실행취소용)\n const existingCell = notebookContent.widgets[cellIndex];\n if (existingCell?.model?.sharedModel) {\n previousContent = existingCell.model.sharedModel.getSource();\n }\n console.log('[ToolExecutor] MODIFY: Updating cell at index:', cellIndex);\n this.updateCellContent(cellIndex, codeWithStep);\n wasModified = true;\n }\n else if (params.insertAfter !== undefined) {\n // INSERT_AFTER: 특정 셀 뒤에 삽입\n operation = 'INSERT_AFTER';\n console.log('[ToolExecutor] INSERT_AFTER: Inserting after cell:', params.insertAfter);\n cellIndex = await this.insertCellAfter(codeWithStep, params.insertAfter);\n }\n else if (params.insertBefore !== undefined) {\n // INSERT_BEFORE: 특정 셀 앞에 삽입\n operation = 'INSERT_BEFORE';\n console.log('[ToolExecutor] INSERT_BEFORE: Inserting before cell:', params.insertBefore);\n cellIndex = await this.insertCellBefore(codeWithStep, params.insertBefore);\n }\n else {\n // CREATE: 기본 동작 - 노트북 끝에 생성\n operation = 'CREATE';\n console.log('[ToolExecutor] CREATE: Creating new cell at end');\n cellIndex = await this.createCodeCell(codeWithStep);\n }\n console.log('[ToolExecutor] Cell operation completed:', operation, 'at index:', cellIndex);\n // 셀 생성/수정 후 해당 셀로 스크롤 (실행 전)\n this.scrollToCell(cellIndex);\n // 셀 실행 및 결과 캡처\n console.log('[ToolExecutor] Executing cell at index:', cellIndex);\n const result = await this.executeCellAndCapture(cellIndex);\n console.log('[ToolExecutor] Cell execution result:', result.status);\n return {\n success: result.status === 'ok',\n output: result.result || result.stdout,\n error: result.error?.evalue,\n errorName: result.error?.ename,\n traceback: result.error?.traceback,\n cellIndex,\n wasModified,\n operation,\n previousContent,\n };\n }\n catch (error) {\n console.error('[ToolExecutor] executeJupyterCell error:', error);\n return {\n success: false,\n error: error.message || 'Failed to execute jupyter_cell',\n cellIndex: cellIndex,\n wasModified,\n operation,\n previousContent,\n };\n }\n }\n /**\n * markdown 도구: 마크다운 셀 생성/수정\n */\n async executeMarkdown(params, stepNumber) {\n try {\n let cellIndex;\n let wasModified = false;\n // stepNumber가 있으면 마크다운 맨 앞에 표시 추가\n let contentWithStep = params.content;\n if (stepNumber !== undefined) {\n contentWithStep = `**[Step ${stepNumber}]**\\n\\n${params.content}`;\n }\n if (params.cellIndex !== undefined) {\n cellIndex = params.cellIndex;\n this.updateCellContent(cellIndex, contentWithStep);\n wasModified = true;\n }\n else {\n cellIndex = await this.createMarkdownCell(contentWithStep);\n }\n // 마크다운 셀도 생성 후 스크롤\n this.scrollToCell(cellIndex);\n return {\n success: true,\n cellIndex,\n wasModified,\n };\n }\n catch (error) {\n return {\n success: false,\n error: error.message || 'Failed to execute markdown',\n };\n }\n }\n /**\n * final_answer 도구: 작업 완료 신호\n */\n async executeFinalAnswer(params) {\n return {\n success: true,\n output: params.answer,\n };\n }\n // ═══════════════════════════════════════════════════════════════════════════\n // 확장 도구 실행기 (파일/터미널 작업)\n // ═══════════════════════════════════════════════════════════════════════════\n /**\n * Path Traversal 방지 검사\n * 상대 경로만 허용, 절대 경로 및 .. 차단\n */\n validatePath(path) {\n // 절대 경로 차단\n if (path.startsWith('/') || path.startsWith('\\\\') || /^[A-Za-z]:/.test(path)) {\n return { valid: false, error: 'Absolute paths are not allowed' };\n }\n // Path traversal 차단\n if (path.includes('..')) {\n return { valid: false, error: 'Path traversal (..) is not allowed' };\n }\n return { valid: true };\n }\n /**\n * 위험 명령 여부 확인\n */\n isDangerousCommand(command) {\n return DANGEROUS_COMMAND_PATTERNS.some(pattern => pattern.test(command));\n }\n summarizeOutput(output, maxLines = 2) {\n const lines = output.split(/\\r?\\n/).filter(line => line.length > 0);\n const text = lines.slice(0, maxLines).join('\\n');\n return { text, truncated: lines.length > maxLines };\n }\n /**\n * read_file 도구: 파일 읽기\n */\n async executeReadFile(params) {\n console.log('[ToolExecutor] executeReadFile:', params);\n // 경로 검증\n const pathCheck = this.validatePath(params.path);\n if (!pathCheck.valid) {\n return { success: false, error: pathCheck.error };\n }\n const encoding = params.encoding || 'utf-8';\n const maxLines = params.maxLines || 1000;\n // Python 코드로 파일 읽기 (커널에서 실행)\n const pythonCode = `\nimport json\ntry:\n with open(${JSON.stringify(params.path)}, 'r', encoding=${JSON.stringify(encoding)}) as f:\n lines = f.readlines()[:${maxLines}]\n content = ''.join(lines)\n result = {'success': True, 'content': content, 'lineCount': len(lines), 'truncated': len(lines) >= ${maxLines}}\nexcept FileNotFoundError:\n result = {'success': False, 'error': f'File not found: ${params.path}'}\nexcept PermissionError:\n result = {'success': False, 'error': f'Permission denied: ${params.path}'}\nexcept Exception as e:\n result = {'success': False, 'error': str(e)}\nprint(json.dumps(result))\n`.trim();\n try {\n const execResult = await this.executeInKernel(pythonCode);\n if (execResult.status === 'ok' && execResult.stdout) {\n const parsed = JSON.parse(execResult.stdout.trim());\n if (parsed.success) {\n return {\n success: true,\n output: parsed.content,\n };\n }\n else {\n return { success: false, error: parsed.error };\n }\n }\n return { success: false, error: execResult.error?.evalue || 'Read failed' };\n }\n catch (error) {\n return { success: false, error: error.message };\n }\n }\n /**\n * write_file 도구: 파일 쓰기\n */\n async executeWriteFile(params) {\n console.log('[ToolExecutor] executeWriteFile:', params.path);\n // 경로 검증\n const pathCheck = this.validatePath(params.path);\n if (!pathCheck.valid) {\n return { success: false, error: pathCheck.error };\n }\n const overwrite = params.overwrite ?? false;\n const mode = overwrite ? 'w' : 'x'; // 'x'는 exclusive creation\n // Python 코드로 파일 쓰기 (커널에서 실행)\n const pythonCode = `\nimport json\nimport os\ntry:\n mode = ${JSON.stringify(mode)}\n path = ${JSON.stringify(params.path)}\n content = ${JSON.stringify(params.content)}\n\n # 디렉토리가 없으면 생성\n dir_path = os.path.dirname(path)\n if dir_path:\n os.makedirs(dir_path, exist_ok=True)\n\n with open(path, mode, encoding='utf-8') as f:\n f.write(content)\n result = {'success': True, 'path': path, 'size': len(content)}\nexcept FileExistsError:\n result = {'success': False, 'error': f'File already exists: {path}. Set overwrite=True to overwrite.'}\nexcept PermissionError:\n result = {'success': False, 'error': f'Permission denied: {path}'}\nexcept Exception as e:\n result = {'success': False, 'error': str(e)}\nprint(json.dumps(result))\n`.trim();\n try {\n const execResult = await this.executeInKernel(pythonCode);\n if (execResult.status === 'ok' && execResult.stdout) {\n const parsed = JSON.parse(execResult.stdout.trim());\n if (parsed.success) {\n return {\n success: true,\n output: `Written ${parsed.size} bytes to ${parsed.path}`,\n };\n }\n else {\n return { success: false, error: parsed.error };\n }\n }\n return { success: false, error: execResult.error?.evalue || 'Write failed' };\n }\n catch (error) {\n return { success: false, error: error.message };\n }\n }\n /**\n * Check if pattern is an exact path (contains path separator)\n */\n isExactPath(pattern) {\n return pattern.includes('/') || pattern.includes('\\\\');\n }\n /**\n * Check file existence for exact paths\n */\n async checkFileExists(filePath, basePath = '.') {\n const fullPath = filePath.startsWith('/') ? filePath : `${basePath}/${filePath}`;\n const pythonCode = `\nimport json\nimport os\ntry:\n path = ${JSON.stringify(fullPath)}\n if os.path.exists(path):\n is_dir = os.path.isdir(path)\n size = 0 if is_dir else os.path.getsize(path)\n result = {\n 'success': True,\n 'path': path,\n 'isDir': is_dir,\n 'size': size\n }\n else:\n result = {'success': False, 'error': f'File not found: {path}'}\nexcept Exception as e:\n result = {'success': False, 'error': str(e)}\nprint(json.dumps(result))\n`.trim();\n try {\n const execResult = await this.executeInKernel(pythonCode);\n if (execResult.status === 'ok' && execResult.stdout) {\n const parsed = JSON.parse(execResult.stdout.trim());\n if (parsed.success) {\n const icon = parsed.isDir ? '📁' : '📄';\n const sizeInfo = parsed.isDir ? '' : ` (${parsed.size} bytes)`;\n return {\n success: true,\n output: `${icon} ${parsed.path}${sizeInfo}`,\n metadata: { resolvedPath: parsed.path }\n };\n }\n return { success: false, error: parsed.error };\n }\n return { success: false, error: 'Failed to check file existence' };\n }\n catch (error) {\n return { success: false, error: error.message };\n }\n }\n /**\n * Resolve file using file_resolver API\n */\n async resolveWithFileResolver(pattern, params) {\n console.log('[ToolExecutor] Resolving files locally via kernel...');\n const notebookDirRelative = this.getNotebookDir() || '.';\n const recursive = params.recursive ?? false;\n // Python code to search for files in multiple paths\n const pythonCode = `\nimport json\nimport os\nimport glob as glob_module\ntry:\n pattern = ${JSON.stringify(pattern)}\n notebook_dir_relative = ${JSON.stringify(notebookDirRelative)}\n recursive = ${recursive ? 'True' : 'False'}\n\n # For simple filename patterns (no path separators), always search recursively\n # This allows finding files in subdirectories\n is_filename_pattern = '/' not in pattern and '\\\\\\\\' not in pattern\n if is_filename_pattern:\n recursive = True\n\n # Get current working directory (may be server root or notebook dir)\n cwd = os.getcwd()\n\n # Get Jupyter server root (absolute path)\n server_root = os.getenv('JUPYTER_SERVER_ROOT') or \\\n os.getenv('JUPYTERHUB_ROOT_DIR')\n\n # Get notebook directory (absolute path)\n if notebook_dir_relative and notebook_dir_relative != '.':\n # If we have server_root, use it\n if server_root:\n notebook_dir = os.path.join(server_root, notebook_dir_relative)\n if not os.path.exists(notebook_dir):\n notebook_dir = cwd\n else:\n # No server_root env var - derive from cwd\n # If cwd ends with notebook_dir_relative, it's already the notebook dir\n if cwd.endswith(notebook_dir_relative):\n notebook_dir = cwd\n server_root = os.path.dirname(cwd)\n else:\n # cwd is probably server_root\n notebook_dir = os.path.join(cwd, notebook_dir_relative)\n server_root = cwd\n else:\n # notebook_dir_relative is '.'\n notebook_dir = cwd\n server_root = os.path.dirname(cwd) if os.path.dirname(cwd) != cwd else cwd\n\n # Search paths:\n # 1. notebook_dir (현재 노트북 디렉토리) - 좁은 범위 우선\n # 2. server_root (JUPYTER_SERVER_ROOT, 프로젝트 루트) - 전체 프로젝트\n search_paths = [notebook_dir]\n\n # Add server_root if different from notebook_dir\n if server_root != notebook_dir and os.path.exists(server_root):\n search_paths.append(server_root)\n elif notebook_dir != cwd and cwd != server_root and os.path.exists(cwd):\n search_paths.append(cwd)\n\n matches = []\n seen_paths = set() # Track unique absolute paths\n debug_info = {\n 'notebook_dir': notebook_dir,\n 'server_root': server_root,\n 'search_paths': search_paths,\n 'searches': []\n }\n\n for search_path in search_paths:\n # Construct glob pattern\n if recursive and '**' not in pattern:\n search_pattern = os.path.join(search_path, '**', pattern)\n else:\n search_pattern = os.path.join(search_path, pattern)\n\n # Find files (limit depth to avoid searching too deep)\n all_files = glob_module.glob(search_pattern, recursive=recursive)\n\n # For recursive searches, apply depth limit based on search path\n if recursive and '**' in search_pattern:\n # For notebook_dir: limit to 3 levels (focused search)\n # For server_root: limit to 5 levels (broader search)\n if search_path == notebook_dir:\n max_depth = 3\n else:\n max_depth = 5\n\n files = [f for f in all_files if f.count(os.sep) - search_path.count(os.sep) <= max_depth]\n else:\n files = all_files\n\n debug_info['searches'].append({\n 'search_path': search_path,\n 'search_pattern': search_pattern,\n 'found_count': len(files),\n 'files': files\n })\n\n for file_path in files:\n abs_path = os.path.abspath(file_path)\n # Avoid duplicates\n if abs_path not in seen_paths:\n seen_paths.add(abs_path)\n matches.append({\n 'path': abs_path,\n 'relative': file_path,\n 'dir': os.path.dirname(file_path) or '.'\n })\n\n result = {\n 'success': True,\n 'matches': matches,\n 'count': len(matches),\n 'debug': debug_info\n }\nexcept Exception as e:\n result = {'success': False, 'error': str(e)}\nprint(json.dumps(result))\n`.trim();\n try {\n const execResult = await this.executeInKernel(pythonCode);\n if (execResult.status === 'ok' && execResult.stdout) {\n const parsed = JSON.parse(execResult.stdout.trim());\n // Debug logging\n if (parsed.debug) {\n console.log('[ToolExecutor] File search debug info:', parsed.debug);\n }\n if (!parsed.success) {\n return { success: false, error: parsed.error };\n }\n const matches = parsed.matches;\n if (matches.length === 0) {\n return { success: false, error: `'${pattern}' 파일을 찾을 수 없습니다.` };\n }\n if (matches.length === 1) {\n // Single file found\n return {\n success: true,\n output: `📄 ${matches[0].relative}`,\n metadata: { resolvedPath: matches[0].path }\n };\n }\n // Multiple files found - need user selection\n return {\n success: false,\n error: 'FILE_SELECTION_REQUIRED',\n metadata: {\n type: 'file_selection',\n pattern: pattern,\n options: matches,\n message: `'${pattern}' 패턴과 일치하는 파일이 ${matches.length}개 발견되었습니다.\\n\\n${matches.map((m, i) => `${i + 1}. ${m.relative}`).join('\\n')}\\n\\n번호를 선택해주세요 (1-${matches.length})`\n }\n };\n }\n return { success: false, error: execResult.error?.evalue || 'File resolution failed' };\n }\n catch (error) {\n return { success: false, error: error.message };\n }\n }\n /**\n * list_files 도구: 디렉토리 목록 조회 (MECE 구조)\n */\n async executeListFiles(params) {\n console.log('[ToolExecutor] executeListFiles:', params);\n // 경로 검증\n const pathCheck = this.validatePath(params.path);\n if (!pathCheck.valid) {\n return { success: false, error: pathCheck.error };\n }\n const pattern = params.pattern || '*';\n // CASE 1: 정확한 경로 (사용자가 직접 명시)\n // 예: \"./titanic.csv\", \"data/train.csv\"\n if (this.isExactPath(pattern)) {\n return await this.checkFileExists(pattern, params.path);\n }\n // CASE 2: 파일명/패턴 → file_resolver 시도\n // 예: \"titanic.csv\", \"*titanic*\", \"*.csv\"\n if (this.apiService) {\n try {\n return await this.resolveWithFileResolver(pattern, params);\n }\n catch (error) {\n console.warn('[ToolExecutor] file_resolver failed, falling back to glob:', error);\n // Fallback to glob search\n }\n }\n // CASE 3: Fallback - 기존 glob 검색 (apiService 없거나 실패 시)\n return await this.executeListFilesWithGlob(pattern, params);\n }\n /**\n * Glob-based file listing (fallback)\n */\n async executeListFilesWithGlob(pattern, params) {\n const recursive = params.recursive ?? false;\n const isFilenameOnly = !pattern.includes('/') && !pattern.includes('\\\\') && pattern !== '*';\n const effectiveRecursive = recursive || isFilenameOnly;\n // Python 코드로 파일 목록 조회\n const pythonCode = `\nimport json\nimport os\nimport glob as glob_module\ntry:\n path = ${JSON.stringify(params.path)}\n pattern = ${JSON.stringify(pattern)}\n recursive = ${effectiveRecursive ? 'True' : 'False'}\n\n if recursive:\n search_pattern = os.path.join(path, '**', pattern)\n files = glob_module.glob(search_pattern, recursive=True)\n else:\n search_pattern = os.path.join(path, pattern)\n files = glob_module.glob(search_pattern)\n\n # 결과를 상대 경로로 변환\n result_files = []\n for f in files[:500]: # 최대 500개\n stat = os.stat(f)\n result_files.append({\n 'path': f,\n 'isDir': os.path.isdir(f),\n 'size': stat.st_size if not os.path.isdir(f) else 0\n })\n\n result = {'success': True, 'files': result_files, 'count': len(result_files)}\nexcept FileNotFoundError:\n result = {'success': False, 'error': f'Directory not found: {path}'}\nexcept PermissionError:\n result = {'success': False, 'error': f'Permission denied: {path}'}\nexcept Exception as e:\n result = {'success': False, 'error': str(e)}\nprint(json.dumps(result))\n`.trim();\n try {\n const execResult = await this.executeInKernel(pythonCode);\n if (execResult.status === 'ok' && execResult.stdout) {\n const parsed = JSON.parse(execResult.stdout.trim());\n if (parsed.success) {\n // 파일 목록을 보기 좋게 포맷팅\n const formatted = parsed.files.map((f) => `${f.isDir ? '📁' : '📄'} ${f.path}${f.isDir ? '/' : ` (${f.size} bytes)`}`).join('\\n');\n return {\n success: true,\n output: formatted || '(empty directory)',\n };\n }\n else {\n return { success: false, error: parsed.error };\n }\n }\n return { success: false, error: execResult.error?.evalue || 'List failed' };\n }\n catch (error) {\n return { success: false, error: error.message };\n }\n }\n /**\n * execute_command_tool 도구: 셸 명령 실행 (조건부 승인)\n */\n async executeCommand(params, context) {\n console.log('[ToolExecutor] executeCommand:', params.command);\n const timeout = typeof params.timeout === 'number' ? params.timeout : 600000;\n // 위험 명령 검사 및 조건부 승인 요청\n if (this.isDangerousCommand(params.command)) {\n console.log('[ToolExecutor] Dangerous command detected, requesting approval');\n // 승인 요청\n const request = {\n id: `execute_command_tool-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n toolName: 'execute_command_tool',\n toolDefinition: this.registry.getTool('execute_command_tool'),\n parameters: params,\n stepNumber: context.stepNumber,\n description: `🔴 위험 명령 실행 요청:\\n\\n\\`${params.command}\\`\\n\\n이 명령은 시스템에 영향을 줄 수 있습니다.`,\n timestamp: Date.now(),\n };\n const approvalCallback = this.registry.approvalCallback;\n if (approvalCallback) {\n const approvalResult = await approvalCallback(request);\n if (!approvalResult.approved) {\n return {\n success: false,\n error: `Command execution denied: ${approvalResult.reason || 'User rejected dangerous command'}`,\n };\n }\n }\n }\n if (!this.apiService) {\n return { success: false, error: 'ApiService not available for execute_command_tool' };\n }\n try {\n const result = await this.apiService.executeCommandStream(params.command, { timeout });\n const stdout = typeof result.stdout === 'string' ? result.stdout : '';\n const stderr = typeof result.stderr === 'string' ? result.stderr : '';\n const combined = [stdout, stderr].filter(Boolean).join('\\n');\n const summary = this.summarizeOutput(combined, 2);\n const output = summary.text || '(no output)';\n if (result.success) {\n return {\n success: true,\n output,\n };\n }\n const errorText = summary.text || result.error || stderr || `Command failed with code ${result.returncode}`;\n return {\n success: false,\n error: errorText,\n };\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'Command execution failed';\n const summary = this.summarizeOutput(String(message), 2);\n return { success: false, error: summary.text || 'Command execution failed' };\n }\n }\n /**\n * search_files 도구: 파일 내용 검색\n */\n async executeSearchFiles(params) {\n console.log('[ToolExecutor] executeSearchFiles:', params);\n const searchPath = params.path || '.';\n const maxResults = params.maxResults || 100;\n // 경로 검증\n const pathCheck = this.validatePath(searchPath);\n if (!pathCheck.valid) {\n return { success: false, error: pathCheck.error };\n }\n // Python으로 grep 스타일 검색\n const pythonCode = `\nimport json\nimport os\nimport re\ntry:\n pattern = ${JSON.stringify(params.pattern)}\n search_path = ${JSON.stringify(searchPath)}\n max_results = ${maxResults}\n\n regex = re.compile(pattern, re.IGNORECASE)\n matches = []\n\n for root, dirs, files in os.walk(search_path):\n # 숨김 디렉토리 및 일반적인 제외 대상 스킵\n dirs[:] = [d for d in dirs if not d.startswith('.') and d not in ['node_modules', '__pycache__', '.git', 'venv', '.venv']]\n\n for filename in files:\n if len(matches) >= max_results:\n break\n\n # 바이너리 파일 스킵\n if filename.endswith(('.pyc', '.pyo', '.so', '.dll', '.exe', '.bin', '.png', '.jpg', '.gif', '.pdf', '.zip')):\n continue\n\n filepath = os.path.join(root, filename)\n try:\n with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:\n for line_num, line in enumerate(f, 1):\n if regex.search(line):\n matches.append({\n 'file': filepath,\n 'line': line_num,\n 'content': line.strip()[:200] # 최대 200자\n })\n if len(matches) >= max_results:\n break\n except (IOError, OSError):\n continue\n\n if len(matches) >= max_results:\n break\n\n result = {'success': True, 'matches': matches, 'count': len(matches), 'truncated': len(matches) >= max_results}\nexcept re.error as e:\n result = {'success': False, 'error': f'Invalid regex pattern: {e}'}\nexcept Exception as e:\n result = {'success': False, 'error': str(e)}\nprint(json.dumps(result))\n`.trim();\n try {\n const execResult = await this.executeInKernel(pythonCode);\n if (execResult.status === 'ok' && execResult.stdout) {\n const parsed = JSON.parse(execResult.stdout.trim());\n if (parsed.success) {\n // 결과를 보기 좋게 포맷팅\n const formatted = parsed.matches.map((m) => `${m.file}:${m.line}: ${m.content}`).join('\\n');\n return {\n success: true,\n output: formatted || '(no matches found)',\n };\n }\n else {\n return { success: false, error: parsed.error };\n }\n }\n return { success: false, error: execResult.error?.evalue || 'Search failed' };\n }\n catch (error) {\n return { success: false, error: error.message };\n }\n }\n // ═══════════════════════════════════════════════════════════════════════════\n // Phase 2 확장 도구 실행기\n // ═══════════════════════════════════════════════════════════════════════════\n /**\n * install_package 도구: pip 패키지 설치\n */\n async executeInstallPackage(params) {\n console.log('[ToolExecutor] executeInstallPackage:', params);\n const packageName = params.package;\n const version = params.version;\n const extras = params.extras || [];\n const upgrade = params.upgrade ?? false;\n // 패키지 스펙 구성\n let packageSpec = packageName;\n if (extras.length > 0) {\n packageSpec += `[${extras.join(',')}]`;\n }\n if (version) {\n packageSpec += `==${version}`;\n }\n // pip install 명령 구성\n const pipArgs = ['install'];\n if (upgrade) {\n pipArgs.push('--upgrade');\n }\n pipArgs.push(packageSpec);\n // Python subprocess로 pip 실행\n const pythonCode = `\nimport json\nimport subprocess\nimport sys\ntry:\n pip_args = ${JSON.stringify(pipArgs)}\n result = subprocess.run(\n [sys.executable, '-m', 'pip'] + pip_args,\n capture_output=True,\n text=True,\n timeout=300 # 5분 타임아웃\n )\n\n output = {\n 'success': result.returncode == 0,\n 'stdout': result.stdout,\n 'stderr': result.stderr,\n 'returncode': result.returncode,\n 'package': ${JSON.stringify(packageSpec)}\n }\nexcept subprocess.TimeoutExpired:\n output = {'success': False, 'error': 'Package installation timed out after 5 minutes'}\nexcept Exception as e:\n output = {'success': False, 'error': str(e)}\nprint(json.dumps(output))\n`.trim();\n try {\n const execResult = await this.executeInKernel(pythonCode);\n if (execResult.status === 'ok' && execResult.stdout) {\n const parsed = JSON.parse(execResult.stdout.trim());\n if (parsed.success) {\n return {\n success: true,\n output: `Successfully installed ${parsed.package}\\n${parsed.stdout}`,\n };\n }\n else {\n return {\n success: false,\n error: parsed.error || parsed.stderr || `pip install failed with code ${parsed.returncode}`,\n };\n }\n }\n return { success: false, error: execResult.error?.evalue || 'Package installation failed' };\n }\n catch (error) {\n return { success: false, error: error.message };\n }\n }\n /**\n * lint_file 도구: Python 파일 린트 검사\n */\n async executeLintFile(params) {\n console.log('[ToolExecutor] executeLintFile:', params);\n // 경로 검증\n const pathCheck = this.validatePath(params.path);\n if (!pathCheck.valid) {\n return { success: false, error: pathCheck.error };\n }\n const fix = params.fix ?? false;\n const tool = params.tool || 'ruff';\n // 린트 도구별 명령 구성\n const pythonCode = `\nimport json\nimport subprocess\nimport shutil\ntry:\n path = ${JSON.stringify(params.path)}\n tool = ${JSON.stringify(tool)}\n fix = ${fix}\n\n # 도구 존재 여부 확인\n tool_path = shutil.which(tool)\n if not tool_path:\n # pip로 도구 검색 시도\n import sys\n result = subprocess.run(\n [sys.executable, '-m', tool, '--version'],\n capture_output=True, text=True\n )\n if result.returncode != 0:\n raise FileNotFoundError(f'{tool} is not installed. Run: pip install {tool}')\n tool_cmd = [sys.executable, '-m', tool]\n else:\n tool_cmd = [tool]\n\n # 린트 명령 구성\n if tool == 'ruff':\n args = tool_cmd + ['check', path]\n if fix:\n args.append('--fix')\n elif tool == 'pylint':\n args = tool_cmd + [path]\n elif tool == 'flake8':\n args = tool_cmd + [path]\n else:\n raise ValueError(f'Unsupported lint tool: {tool}')\n\n result = subprocess.run(args, capture_output=True, text=True, timeout=60)\n\n output = {\n 'success': result.returncode == 0,\n 'stdout': result.stdout,\n 'stderr': result.stderr,\n 'returncode': result.returncode,\n 'tool': tool,\n 'fixed': fix and result.returncode == 0\n }\nexcept FileNotFoundError as e:\n output = {'success': False, 'error': str(e)}\nexcept subprocess.TimeoutExpired:\n output = {'success': False, 'error': 'Lint check timed out after 60 seconds'}\nexcept Exception as e:\n output = {'success': False, 'error': str(e)}\nprint(json.dumps(output))\n`.trim();\n try {\n const execResult = await this.executeInKernel(pythonCode);\n if (execResult.status === 'ok' && execResult.stdout) {\n const parsed = JSON.parse(execResult.stdout.trim());\n if (parsed.success) {\n const status = parsed.fixed ? '✅ Fixed' : '✅ No issues';\n return {\n success: true,\n output: `${status} (${parsed.tool})\\n${parsed.stdout || '(no output)'}`,\n };\n }\n else {\n // 린트 이슈가 있어도 실행은 성공한 것\n if (parsed.returncode !== undefined && parsed.stdout) {\n return {\n success: true,\n output: `⚠️ Lint issues found (${parsed.tool}):\\n${parsed.stdout}${parsed.stderr ? '\\n' + parsed.stderr : ''}`,\n };\n }\n return { success: false, error: parsed.error || parsed.stderr };\n }\n }\n return { success: false, error: execResult.error?.evalue || 'Lint check failed' };\n }\n catch (error) {\n return { success: false, error: error.message };\n }\n }\n /**\n * delete_cell 도구: 노트북 셀 삭제\n */\n async executeDeleteCell(params) {\n console.log('[ToolExecutor] executeDeleteCell:', params);\n const { cellIndex } = params;\n const model = this.notebook.content.model;\n if (!model) {\n return { success: false, error: 'Notebook model not available' };\n }\n const cellCount = model.cells.length;\n if (cellIndex < 0 || cellIndex >= cellCount) {\n return {\n success: false,\n error: `Invalid cell index: ${cellIndex}. Valid range: 0-${cellCount - 1}`,\n };\n }\n // 삭제 전 셀 내용 저장 (로깅용)\n const cell = model.cells.get(cellIndex);\n const cellType = cell?.type || 'unknown';\n const cellSource = cell?.sharedModel.getSource().substring(0, 100);\n try {\n model.sharedModel.deleteCell(cellIndex);\n return {\n success: true,\n output: `Deleted ${cellType} cell at index ${cellIndex}${cellSource ? `: \"${cellSource}...\"` : ''}`,\n };\n }\n catch (error) {\n return { success: false, error: error.message };\n }\n }\n /**\n * get_cell_output 도구: 셀 출력 조회\n */\n async executeGetCellOutput(params) {\n console.log('[ToolExecutor] executeGetCellOutput:', params);\n const { cellIndex, outputType = 'text' } = params;\n const model = this.notebook.content.model;\n if (!model) {\n return { success: false, error: 'Notebook model not available' };\n }\n const cellCount = model.cells.length;\n if (cellIndex < 0 || cellIndex >= cellCount) {\n return {\n success: false,\n error: `Invalid cell index: ${cellIndex}. Valid range: 0-${cellCount - 1}`,\n };\n }\n const cell = this.notebook.content.widgets[cellIndex];\n if (!cell || cell.model?.type !== 'code') {\n return {\n success: false,\n error: `Cell at index ${cellIndex} is not a code cell`,\n };\n }\n const cellOutputs = cell.model?.outputs;\n if (!cellOutputs || cellOutputs.length === 0) {\n return {\n success: true,\n output: '(no output)',\n };\n }\n const outputs = [];\n for (let i = 0; i < cellOutputs.length; i++) {\n const output = cellOutputs.get(i);\n const outputData = output.toJSON?.() || output;\n if (outputType === 'all') {\n outputs.push(outputData);\n }\n else {\n // text 모드: 텍스트만 추출\n if (output.type === 'stream') {\n outputs.push(output.text || '');\n }\n else if (output.type === 'execute_result' || output.type === 'display_data') {\n const data = output.data;\n if (data?.['text/plain']) {\n outputs.push(data['text/plain']);\n }\n }\n else if (output.type === 'error') {\n outputs.push(`${outputData.ename}: ${outputData.evalue}`);\n }\n }\n }\n return {\n success: true,\n output: outputType === 'all' ? JSON.stringify(outputs, null, 2) : outputs.join('\\n'),\n };\n }\n /**\n * create_notebook 도구: 새 노트북 파일 생성\n */\n async executeCreateNotebook(params) {\n console.log('[ToolExecutor] executeCreateNotebook:', params);\n // 경로 검증\n const pathCheck = this.validatePath(params.path);\n if (!pathCheck.valid) {\n return { success: false, error: pathCheck.error };\n }\n // .ipynb 확장자 확인\n if (!params.path.endsWith('.ipynb')) {\n return { success: false, error: 'Notebook path must end with .ipynb' };\n }\n const cells = params.cells || [];\n const kernel = params.kernel || 'python3';\n // 노트북 JSON 구조 생성\n const pythonCode = `\nimport json\nimport os\ntry:\n path = ${JSON.stringify(params.path)}\n cells = ${JSON.stringify(cells)}\n kernel = ${JSON.stringify(kernel)}\n\n # 이미 존재하는지 확인\n if os.path.exists(path):\n raise FileExistsError(f'Notebook already exists: {path}')\n\n # 디렉토리 생성\n dir_path = os.path.dirname(path)\n if dir_path:\n os.makedirs(dir_path, exist_ok=True)\n\n # 노트북 구조 생성\n notebook = {\n 'nbformat': 4,\n 'nbformat_minor': 5,\n 'metadata': {\n 'kernelspec': {\n 'name': kernel,\n 'display_name': 'Python 3',\n 'language': 'python'\n },\n 'language_info': {\n 'name': 'python',\n 'version': '3.9'\n }\n },\n 'cells': []\n }\n\n # 셀 추가\n for i, cell in enumerate(cells):\n cell_type = cell.get('type', 'code')\n source = cell.get('source', '')\n notebook['cells'].append({\n 'cell_type': cell_type,\n 'source': source.split('\\\\n') if source else [],\n 'metadata': {},\n 'execution_count': None if cell_type == 'code' else None,\n 'outputs': [] if cell_type == 'code' else None\n })\n # Remove None values\n notebook['cells'][-1] = {k: v for k, v in notebook['cells'][-1].items() if v is not None}\n\n # 파일 저장\n with open(path, 'w', encoding='utf-8') as f:\n json.dump(notebook, f, indent=2)\n\n result = {'success': True, 'path': path, 'cellCount': len(cells)}\nexcept FileExistsError as e:\n result = {'success': False, 'error': str(e)}\nexcept Exception as e:\n result = {'success': False, 'error': str(e)}\nprint(json.dumps(result))\n`.trim();\n try {\n const execResult = await this.executeInKernel(pythonCode);\n if (execResult.status === 'ok' && execResult.stdout) {\n const parsed = JSON.parse(execResult.stdout.trim());\n if (parsed.success) {\n return {\n success: true,\n output: `Created notebook: ${parsed.path} with ${parsed.cellCount} cells`,\n };\n }\n else {\n return { success: false, error: parsed.error };\n }\n }\n return { success: false, error: execResult.error?.evalue || 'Failed to create notebook' };\n }\n catch (error) {\n return { success: false, error: error.message };\n }\n }\n /**\n * create_folder 도구: 디렉토리 생성\n */\n async executeCreateFolder(params) {\n console.log('[ToolExecutor] executeCreateFolder:', params);\n // 경로 검증\n const pathCheck = this.validatePath(params.path);\n if (!pathCheck.valid) {\n return { success: false, error: pathCheck.error };\n }\n const parents = params.parents ?? true;\n const pythonCode = `\nimport json\nimport os\ntry:\n path = ${JSON.stringify(params.path)}\n parents = ${parents}\n\n if os.path.exists(path):\n if os.path.isdir(path):\n result = {'success': True, 'path': path, 'existed': True}\n else:\n raise FileExistsError(f'Path exists but is not a directory: {path}')\n else:\n if parents:\n os.makedirs(path, exist_ok=True)\n else:\n os.mkdir(path)\n result = {'success': True, 'path': path, 'existed': False}\nexcept FileExistsError as e:\n result = {'success': False, 'error': str(e)}\nexcept FileNotFoundError:\n result = {'success': False, 'error': f'Parent directory does not exist: {os.path.dirname(path)}. Set parents=True to create.'}\nexcept PermissionError:\n result = {'success': False, 'error': f'Permission denied: {path}'}\nexcept Exception as e:\n result = {'success': False, 'error': str(e)}\nprint(json.dumps(result))\n`.trim();\n try {\n const execResult = await this.executeInKernel(pythonCode);\n if (execResult.status === 'ok' && execResult.stdout) {\n const parsed = JSON.parse(execResult.stdout.trim());\n if (parsed.success) {\n const status = parsed.existed ? 'already exists' : 'created';\n return {\n success: true,\n output: `Folder ${status}: ${parsed.path}`,\n };\n }\n else {\n return { success: false, error: parsed.error };\n }\n }\n return { success: false, error: execResult.error?.evalue || 'Failed to create folder' };\n }\n catch (error) {\n return { success: false, error: error.message };\n }\n }\n /**\n * delete_file 도구: 파일/폴더 삭제\n */\n async executeDeleteFile(params) {\n console.log('[ToolExecutor] executeDeleteFile:', params);\n // 경로 검증\n const pathCheck = this.validatePath(params.path);\n if (!pathCheck.valid) {\n return { success: false, error: pathCheck.error };\n }\n const recursive = params.recursive ?? false;\n const pythonCode = `\nimport json\nimport os\nimport shutil\ntry:\n path = ${JSON.stringify(params.path)}\n recursive = ${recursive}\n\n if not os.path.exists(path):\n raise FileNotFoundError(f'Path not found: {path}')\n\n if os.path.isdir(path):\n if recursive:\n shutil.rmtree(path)\n result = {'success': True, 'path': path, 'type': 'directory', 'recursive': True}\n else:\n # 빈 디렉토리만 삭제\n try:\n os.rmdir(path)\n result = {'success': True, 'path': path, 'type': 'directory', 'recursive': False}\n except OSError:\n raise OSError(f'Directory not empty: {path}. Set recursive=True to delete contents.')\n else:\n os.remove(path)\n result = {'success': True, 'path': path, 'type': 'file'}\n\nexcept FileNotFoundError as e:\n result = {'success': False, 'error': str(e)}\nexcept PermissionError:\n result = {'success': False, 'error': f'Permission denied: {path}'}\nexcept OSError as e:\n result = {'success': False, 'error': str(e)}\nexcept Exception as e:\n result = {'success': False, 'error': str(e)}\nprint(json.dumps(result))\n`.trim();\n try {\n const execResult = await this.executeInKernel(pythonCode);\n if (execResult.status === 'ok' && execResult.stdout) {\n const parsed = JSON.parse(execResult.stdout.trim());\n if (parsed.success) {\n const typeStr = parsed.type === 'directory'\n ? (parsed.recursive ? 'directory (recursively)' : 'empty directory')\n : 'file';\n return {\n success: true,\n output: `Deleted ${typeStr}: ${parsed.path}`,\n };\n }\n else {\n return { success: false, error: parsed.error };\n }\n }\n return { success: false, error: execResult.error?.evalue || 'Failed to delete' };\n }\n catch (error) {\n return { success: false, error: error.message };\n }\n }\n // ═══════════════════════════════════════════════════════════════════════════\n // Phase 3 확장 도구 실행기 (Git/Test/Refactor)\n // ═══════════════════════════════════════════════════════════════════════════\n /**\n * git_operations 도구: Git 버전 관리 작업\n */\n async executeGitOperations(params, context) {\n console.log('[ToolExecutor] executeGitOperations:', params);\n const { operation, files, message, branch, count = 10, all } = params;\n // 위험한 작업(push, commit)은 승인 요청\n const dangerousOps = ['push', 'commit'];\n if (dangerousOps.includes(operation)) {\n const request = {\n id: `git_operations-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n toolName: 'git_operations',\n toolDefinition: this.registry.getTool('git_operations'),\n parameters: params,\n stepNumber: context.stepNumber,\n description: `🔶 Git ${operation} 작업 요청:\\n\\n${operation === 'commit' ? `메시지: \"${message}\"` : `브랜치: ${branch || 'current'}`}`,\n timestamp: Date.now(),\n };\n const approvalCallback = this.registry.approvalCallback;\n if (approvalCallback && this.registry.isApprovalRequired()) {\n const approvalResult = await approvalCallback(request);\n if (!approvalResult.approved) {\n return {\n success: false,\n error: `Git ${operation} denied: ${approvalResult.reason || 'User rejected'}`,\n };\n }\n }\n }\n // Git 명령 구성\n let gitCommand = '';\n switch (operation) {\n case 'status':\n gitCommand = 'git status --short';\n break;\n case 'diff':\n gitCommand = files?.length ? `git diff ${files.join(' ')}` : 'git diff';\n break;\n case 'log':\n gitCommand = `git log --oneline -n ${count}`;\n break;\n case 'add':\n if (all) {\n gitCommand = 'git add --all';\n }\n else if (files?.length) {\n gitCommand = `git add ${files.join(' ')}`;\n }\n else {\n return { success: false, error: 'git add requires files or all=true' };\n }\n break;\n case 'commit':\n if (!message) {\n return { success: false, error: 'git commit requires a message' };\n }\n gitCommand = `git commit -m \"${message.replace(/\"/g, '\\\\\"')}\"`;\n break;\n case 'push':\n gitCommand = all ? 'git push --all' : 'git push';\n break;\n case 'pull':\n gitCommand = 'git pull';\n break;\n case 'branch':\n if (branch) {\n gitCommand = `git branch ${branch}`;\n }\n else {\n gitCommand = 'git branch --list';\n }\n break;\n case 'checkout':\n if (!branch) {\n return { success: false, error: 'git checkout requires a branch' };\n }\n gitCommand = `git checkout ${branch}`;\n break;\n case 'stash':\n gitCommand = 'git stash';\n break;\n default:\n return { success: false, error: `Unknown git operation: ${operation}` };\n }\n // Python subprocess로 git 실행\n const pythonCode = `\nimport json\nimport subprocess\ntry:\n command = ${JSON.stringify(gitCommand)}\n result = subprocess.run(\n command,\n shell=True,\n capture_output=True,\n text=True,\n timeout=60\n )\n\n output = {\n 'success': result.returncode == 0,\n 'stdout': result.stdout,\n 'stderr': result.stderr,\n 'returncode': result.returncode,\n 'operation': ${JSON.stringify(operation)}\n }\nexcept subprocess.TimeoutExpired:\n output = {'success': False, 'error': 'Git operation timed out after 60 seconds'}\nexcept Exception as e:\n output = {'success': False, 'error': str(e)}\nprint(json.dumps(output))\n`.trim();\n try {\n const execResult = await this.executeInKernel(pythonCode);\n if (execResult.status === 'ok' && execResult.stdout) {\n const parsed = JSON.parse(execResult.stdout.trim());\n if (parsed.success) {\n return {\n success: true,\n output: `git ${parsed.operation}:\\n${parsed.stdout || '(no output)'}`,\n };\n }\n else {\n return {\n success: false,\n error: parsed.error || parsed.stderr || `git ${operation} failed`,\n };\n }\n }\n return { success: false, error: execResult.error?.evalue || 'Git operation failed' };\n }\n catch (error) {\n return { success: false, error: error.message };\n }\n }\n /**\n * run_tests 도구: pytest/unittest 실행\n */\n async executeRunTests(params) {\n console.log('[ToolExecutor] executeRunTests:', params);\n const path = params.path || '.';\n const pattern = params.pattern;\n const verbose = params.verbose ?? true;\n const coverage = params.coverage ?? false;\n const framework = params.framework || 'pytest';\n // 경로 검증\n const pathCheck = this.validatePath(path);\n if (!pathCheck.valid) {\n return { success: false, error: pathCheck.error };\n }\n // 테스트 명령 구성\n const pythonCode = `\nimport json\nimport subprocess\nimport sys\ntry:\n framework = ${JSON.stringify(framework)}\n path = ${JSON.stringify(path)}\n pattern = ${JSON.stringify(pattern)}\n verbose = ${verbose}\n coverage = ${coverage}\n\n if framework == 'pytest':\n args = [sys.executable, '-m', 'pytest', path]\n if verbose:\n args.append('-v')\n if coverage:\n args.extend(['--cov', '--cov-report=term-missing'])\n if pattern:\n args.extend(['-k', pattern])\n else: # unittest\n args = [sys.executable, '-m', 'unittest', 'discover', '-s', path]\n if verbose:\n args.append('-v')\n if pattern:\n args.extend(['-p', pattern])\n\n result = subprocess.run(\n args,\n capture_output=True,\n text=True,\n timeout=300 # 5분 타임아웃\n )\n\n # 테스트 결과 파싱\n output_text = result.stdout + '\\\\n' + result.stderr\n\n # pytest 결과에서 통계 추출\n passed = failed = errors = skipped = 0\n import re\n if framework == 'pytest':\n match = re.search(r'(\\\\d+) passed', output_text)\n if match:\n passed = int(match.group(1))\n match = re.search(r'(\\\\d+) failed', output_text)\n if match:\n failed = int(match.group(1))\n match = re.search(r'(\\\\d+) error', output_text)\n if match:\n errors = int(match.group(1))\n match = re.search(r'(\\\\d+) skipped', output_text)\n if match:\n skipped = int(match.group(1))\n\n output = {\n 'success': result.returncode == 0,\n 'stdout': result.stdout,\n 'stderr': result.stderr,\n 'returncode': result.returncode,\n 'framework': framework,\n 'stats': {\n 'passed': passed,\n 'failed': failed,\n 'errors': errors,\n 'skipped': skipped\n }\n }\nexcept subprocess.TimeoutExpired:\n output = {'success': False, 'error': 'Test execution timed out after 5 minutes'}\nexcept Exception as e:\n output = {'success': False, 'error': str(e)}\nprint(json.dumps(output))\n`.trim();\n try {\n const execResult = await this.executeInKernel(pythonCode);\n if (execResult.status === 'ok' && execResult.stdout) {\n const parsed = JSON.parse(execResult.stdout.trim());\n const stats = parsed.stats || {};\n const summary = `✅ ${stats.passed || 0} passed, ❌ ${stats.failed || 0} failed, ⚠️ ${stats.errors || 0} errors, ⏭️ ${stats.skipped || 0} skipped`;\n if (parsed.success) {\n return {\n success: true,\n output: `${summary}\\n\\n${parsed.stdout}`,\n };\n }\n else {\n return {\n success: false,\n error: `Tests failed: ${summary}\\n\\n${parsed.stdout}\\n${parsed.stderr}`,\n };\n }\n }\n return { success: false, error: execResult.error?.evalue || 'Test execution failed' };\n }\n catch (error) {\n return { success: false, error: error.message };\n }\n }\n /**\n * refactor_code 도구: 코드 리팩토링\n * 간단한 텍스트 기반 리팩토링 (LSP 없이)\n */\n async executeRefactorCode(params) {\n console.log('[ToolExecutor] executeRefactorCode:', params);\n // 경로 검증\n const pathCheck = this.validatePath(params.path);\n if (!pathCheck.valid) {\n return { success: false, error: pathCheck.error };\n }\n const { operation, path, oldName, newName, lineStart, lineEnd } = params;\n // 작업별 검증\n if ((operation === 'rename_variable' || operation === 'rename_function') && (!oldName || !newName)) {\n return { success: false, error: `${operation} requires oldName and newName` };\n }\n if (operation === 'extract_function' && (!newName || lineStart === undefined || lineEnd === undefined)) {\n return { success: false, error: 'extract_function requires newName, lineStart, and lineEnd' };\n }\n const pythonCode = `\nimport json\nimport re\nimport os\ntry:\n path = ${JSON.stringify(path)}\n operation = ${JSON.stringify(operation)}\n old_name = ${JSON.stringify(oldName || '')}\n new_name = ${JSON.stringify(newName || '')}\n line_start = ${lineStart ?? 'None'}\n line_end = ${lineEnd ?? 'None'}\n\n # 파일 읽기\n with open(path, 'r', encoding='utf-8') as f:\n content = f.read()\n lines = content.split('\\\\n')\n\n original_content = content\n changes_made = 0\n\n if operation == 'rename_variable':\n # 변수명 리네임 (단어 경계 고려)\n pattern = r'\\\\b' + re.escape(old_name) + r'\\\\b'\n new_content, count = re.subn(pattern, new_name, content)\n content = new_content\n changes_made = count\n\n elif operation == 'rename_function':\n # 함수명 리네임 (def, 호출부 모두)\n pattern = r'\\\\b' + re.escape(old_name) + r'\\\\b'\n new_content, count = re.subn(pattern, new_name, content)\n content = new_content\n changes_made = count\n\n elif operation == 'extract_function':\n # 함수 추출 (지정된 줄 범위를 새 함수로)\n if line_start is not None and line_end is not None:\n extract_lines = lines[line_start-1:line_end]\n indent = len(extract_lines[0]) - len(extract_lines[0].lstrip())\n\n # 새 함수 생성\n func_def = ' ' * indent + f'def {new_name}():\\\\n'\n func_body = '\\\\n'.join(' ' + line.lstrip() if line.strip() else line for line in extract_lines)\n new_func = func_def + func_body + '\\\\n'\n\n # 원래 위치에 함수 호출로 대체\n call_line = ' ' * indent + f'{new_name}()\\\\n'\n\n # 파일 수정\n new_lines = lines[:line_start-1] + [call_line.rstrip()] + lines[line_end:]\n # 파일 끝에 새 함수 추가\n new_lines.append('')\n new_lines.append(new_func.rstrip())\n content = '\\\\n'.join(new_lines)\n changes_made = 1\n\n elif operation == 'inline_variable':\n # 변수 인라인 (간단한 구현)\n # 변수 정의를 찾아서 사용처에 값을 직접 대입\n pattern = rf'{re.escape(old_name)}\\\\s*=\\\\s*(.+)'\n match = re.search(pattern, content)\n if match:\n value = match.group(1).strip()\n # 정의 제거\n content = re.sub(pattern + r'\\\\n?', '', content, count=1)\n # 사용처 대체\n content, count = re.subn(r'\\\\b' + re.escape(old_name) + r'\\\\b', value, content)\n changes_made = count\n\n if changes_made > 0:\n # 파일 저장\n with open(path, 'w', encoding='utf-8') as f:\n f.write(content)\n\n result = {\n 'success': True,\n 'operation': operation,\n 'path': path,\n 'changes': changes_made,\n 'oldName': old_name,\n 'newName': new_name\n }\n else:\n result = {\n 'success': False,\n 'error': f'No changes made. Pattern \"{old_name}\" not found in {path}'\n }\n\nexcept FileNotFoundError:\n result = {'success': False, 'error': f'File not found: {path}'}\nexcept Exception as e:\n result = {'success': False, 'error': str(e)}\nprint(json.dumps(result))\n`.trim();\n try {\n const execResult = await this.executeInKernel(pythonCode);\n if (execResult.status === 'ok' && execResult.stdout) {\n const parsed = JSON.parse(execResult.stdout.trim());\n if (parsed.success) {\n let desc = '';\n if (parsed.operation === 'rename_variable' || parsed.operation === 'rename_function') {\n desc = `Renamed \"${parsed.oldName}\" → \"${parsed.newName}\"`;\n }\n else if (parsed.operation === 'extract_function') {\n desc = `Extracted function \"${parsed.newName}\"`;\n }\n else if (parsed.operation === 'inline_variable') {\n desc = `Inlined variable \"${parsed.oldName}\"`;\n }\n return {\n success: true,\n output: `${desc} (${parsed.changes} changes in ${parsed.path})`,\n };\n }\n else {\n return { success: false, error: parsed.error };\n }\n }\n return { success: false, error: execResult.error?.evalue || 'Refactoring failed' };\n }\n catch (error) {\n return { success: false, error: error.message };\n }\n }\n /**\n * 커널에서 임시 코드 실행 (결과 캡처용)\n * 셀을 생성하지 않고 직접 커널에서 실행\n */\n async executeInKernel(code) {\n const model = this.notebook.content.model;\n if (!model) {\n throw new Error('Notebook model is not available');\n }\n const startTime = Date.now();\n const tempCellIndex = model.cells.length;\n // 임시 코드 셀 생성\n model.sharedModel.insertCell(tempCellIndex, {\n cell_type: 'code',\n source: code,\n });\n try {\n // 실행 및 결과 캡처\n const result = await this.executeCellAndCapture(tempCellIndex);\n return result;\n }\n finally {\n // 임시 셀 삭제 (성공/실패 관계없이)\n model.sharedModel.deleteCell(tempCellIndex);\n }\n }\n /**\n * Jupyter kernel에서 변수 값들을 추출\n * @param varNames 추출할 변수명 배열\n * @returns 변수명 -> 값 매핑 객체\n */\n async getVariableValues(varNames) {\n if (varNames.length === 0) {\n return {};\n }\n try {\n // JSON으로 변수 값들을 추출하는 Python 코드 생성\n // DataFrame 등 복잡한 타입을 HTML table로 변환하는 헬퍼 함수 포함\n const code = `\nimport json\n\ndef _format_value(v):\n \"\"\"변수 값을 적절한 형태로 포맷팅\"\"\"\n try:\n # 1. DataFrame → HTML table (pandas, modin 등)\n if hasattr(v, 'to_html'):\n try:\n html = v.to_html(index=False, max_rows=100)\n return f\"<!--DFHTML-->{html}<!--/DFHTML-->\"\n except:\n pass\n\n # 2. Lazy DataFrame (dask) - 샘플만 변환\n if hasattr(v, 'compute'):\n try:\n sample = v.head(100).compute()\n if hasattr(sample, 'to_html'):\n html = sample.to_html(index=False)\n return f\"<!--DFHTML-->{html}<!--/DFHTML-->\"\n except:\n pass\n\n # 3. Spark DataFrame\n if hasattr(v, 'toPandas'):\n try:\n sample = v.limit(100).toPandas()\n if hasattr(sample, 'to_html'):\n html = sample.to_html(index=False)\n return f\"<!--DFHTML-->{html}<!--/DFHTML-->\"\n except:\n pass\n\n # 4. DataFrame with to_pandas conversion (polars, cudf, vaex 등)\n for method in ['to_pandas', 'to_pandas_df']:\n if hasattr(v, method):\n try:\n converted = getattr(v, method)()\n if hasattr(converted, 'to_html'):\n html = converted.to_html(index=False, max_rows=100)\n return f\"<!--DFHTML-->{html}<!--/DFHTML-->\"\n except:\n continue\n\n # 5. Series - to_string()\n if hasattr(v, 'to_string'):\n try:\n return v.to_string(max_rows=100)\n except:\n pass\n\n # 6. 기본 str()\n return str(v)\n except:\n return str(v)\n\n# 변수 값 추출\nresult = {}\n${varNames.map(v => `\nif '${v}' in locals() or '${v}' in globals():\n val = locals().get('${v}', globals().get('${v}'))\n result['${v}'] = _format_value(val)\nelse:\n result['${v}'] = None`).join('')}\n\nprint(json.dumps(result))\n`.trim();\n // 임시 셀 생성하여 실행\n const model = this.notebook.content.model;\n if (!model) {\n throw new Error('Notebook model is not available');\n }\n const tempCellIndex = model.cells.length;\n // 코드 셀 삽입\n model.sharedModel.insertCell(tempCellIndex, {\n cell_type: 'code',\n source: code,\n });\n // 실행 및 결과 캡처\n const result = await this.executeCellAndCapture(tempCellIndex);\n // 임시 셀 삭제\n model.sharedModel.deleteCell(tempCellIndex);\n // stdout에서 JSON 파싱\n if (result.stdout) {\n const variables = JSON.parse(result.stdout.trim());\n // null 값 제거\n const filtered = {};\n for (const [key, value] of Object.entries(variables)) {\n if (value !== null) {\n filtered[key] = value;\n }\n }\n return filtered;\n }\n return {};\n }\n catch (error) {\n console.error('[ToolExecutor] Failed to extract variable values:', error);\n return {};\n }\n }\n /**\n * 순차 실행 시작 시 호출 (마지막 셀 인덱스 초기화)\n */\n resetSequentialExecution() {\n const model = this.notebook.content.model;\n // 현재 노트북 맨 끝 셀 인덱스로 초기화\n this.lastCreatedCellIndex = model ? model.cells.length - 1 : -1;\n console.log('[ToolExecutor] Reset sequential execution, lastCreatedCellIndex:', this.lastCreatedCellIndex);\n }\n /**\n * 코드 셀 생성 (항상 순차적으로 맨 끝에 추가)\n */\n async createCodeCell(code, insertAfter) {\n await this.ensureModelReady();\n const notebookContent = this.notebook.content;\n const model = notebookContent.model;\n if (!model) {\n throw new Error('Notebook model not available');\n }\n // 노트북 맨 끝의 활성 셀이 빈 코드 셀이면 재사용 (첫 셀 생성 시에만)\n const activeIndex = notebookContent.activeCellIndex;\n const isAtEnd = activeIndex === model.cells.length - 1;\n if (activeIndex >= 0 && insertAfter === undefined && isAtEnd) {\n const activeCell = model.cells.get(activeIndex);\n if (activeCell && activeCell.type === 'code') {\n const source = activeCell.sharedModel.getSource().trim();\n if (source === '') {\n // 빈 셀 재사용\n activeCell.sharedModel.setSource(code);\n this.lastCreatedCellIndex = activeIndex;\n return activeIndex;\n }\n }\n }\n // ★ 순차 삽입: 항상 노트북 맨 끝에 추가 (중간 삽입 금지)\n // 이렇게 하면 셀이 항상 아래로만 추가됨\n const insertIndex = model.cells.length;\n // 새 코드 셀 생성\n model.sharedModel.insertCell(insertIndex, {\n cell_type: 'code',\n source: code,\n metadata: {},\n });\n // 마지막 생성 셀 인덱스 업데이트\n this.lastCreatedCellIndex = insertIndex;\n // 새 셀로 포커스 이동\n notebookContent.activeCellIndex = insertIndex;\n console.log('[ToolExecutor] Created cell at index:', insertIndex, '(always at end)');\n return insertIndex;\n }\n /**\n * 특정 셀 뒤에 새 코드 셀 삽입 (INSERT_AFTER)\n * @param code - 삽입할 코드\n * @param afterIndex - 이 셀 뒤에 삽입\n */\n async insertCellAfter(code, afterIndex) {\n await this.ensureModelReady();\n const model = this.notebook.content.model;\n if (!model)\n throw new Error('Notebook model not available');\n // 삽입 위치: afterIndex + 1 (afterIndex 바로 뒤)\n const insertIndex = Math.min(afterIndex + 1, model.cells.length);\n model.sharedModel.insertCell(insertIndex, {\n cell_type: 'code',\n source: code,\n metadata: { hdsp_inserted: true }, // Agent에 의해 삽입됨 표시\n });\n this.lastCreatedCellIndex = insertIndex;\n this.notebook.content.activeCellIndex = insertIndex;\n console.log('[ToolExecutor] INSERT_AFTER: Inserted cell after index:', afterIndex, 'at:', insertIndex);\n return insertIndex;\n }\n /**\n * 특정 셀 앞에 새 코드 셀 삽입 (INSERT_BEFORE)\n * @param code - 삽입할 코드\n * @param beforeIndex - 이 셀 앞에 삽입\n */\n async insertCellBefore(code, beforeIndex) {\n await this.ensureModelReady();\n const model = this.notebook.content.model;\n if (!model)\n throw new Error('Notebook model not available');\n // 삽입 위치: beforeIndex (beforeIndex 바로 앞)\n const insertIndex = Math.max(0, beforeIndex);\n model.sharedModel.insertCell(insertIndex, {\n cell_type: 'code',\n source: code,\n metadata: { hdsp_inserted: true }, // Agent에 의해 삽입됨 표시\n });\n this.lastCreatedCellIndex = insertIndex;\n this.notebook.content.activeCellIndex = insertIndex;\n console.log('[ToolExecutor] INSERT_BEFORE: Inserted cell before index:', beforeIndex, 'at:', insertIndex);\n return insertIndex;\n }\n /**\n * 마크다운 셀 생성 (항상 순차적으로 맨 끝에 추가)\n */\n async createMarkdownCell(content, insertAfter) {\n await this.ensureModelReady();\n const notebookContent = this.notebook.content;\n const model = notebookContent.model;\n if (!model) {\n throw new Error('Notebook model not available');\n }\n // ★ 순차 삽입: 항상 노트북 맨 끝에 추가 (중간 삽입 금지)\n const insertIndex = model.cells.length;\n // 새 마크다운 셀 생성\n model.sharedModel.insertCell(insertIndex, {\n cell_type: 'markdown',\n source: content,\n metadata: {},\n });\n // 마크다운 셀 렌더링\n const cell = notebookContent.widgets[insertIndex];\n if (cell && cell.rendered !== undefined) {\n cell.rendered = true;\n }\n // 마지막 생성 셀 인덱스 업데이트\n this.lastCreatedCellIndex = insertIndex;\n // 새 셀로 활성 셀 업데이트\n notebookContent.activeCellIndex = insertIndex;\n console.log('[ToolExecutor] Created markdown cell at index:', insertIndex, '(always at end)');\n return insertIndex;\n }\n /**\n * 셀 내용 업데이트\n */\n updateCellContent(cellIndex, content) {\n const notebookContent = this.notebook.content;\n const cell = notebookContent.widgets[cellIndex];\n if (!cell || !cell.model?.sharedModel) {\n throw new Error(`Cell at index ${cellIndex} not found or model not available`);\n }\n cell.model.sharedModel.setSource(content);\n }\n /**\n * 셀 실행 및 결과 캡처\n * NotebookActions.run()을 사용하여 정식으로 셀 실행 (execution_count 업데이트 포함)\n */\n async executeCellAndCapture(cellIndex) {\n const notebookContent = this.notebook.content;\n const cell = notebookContent.widgets[cellIndex];\n if (!cell) {\n throw new Error(`Cell at index ${cellIndex} not found`);\n }\n const startTime = Date.now();\n // 해당 셀 선택\n notebookContent.activeCellIndex = cellIndex;\n // NotebookActions.run()을 사용하여 정식 실행 (execution_count 업데이트됨)\n const runSuccess = await NotebookActions.run(notebookContent, this.sessionContext);\n console.log('[ToolExecutor] NotebookActions.run() success:', runSuccess);\n // 커널이 idle 상태가 될 때까지 대기 (출력이 완전히 업데이트되도록)\n // NotebookActions.run()이 false를 반환해도 커널은 아직 busy 상태일 수 있음\n const kernelIdled = await this.waitForKernelIdle(10000);\n console.log('[ToolExecutor] Kernel idle wait result:', kernelIdled);\n // 추가 안정화 대기 (출력 모델 동기화)\n await new Promise(resolve => setTimeout(resolve, 200));\n // 실행 완료 후 결과 캡처\n const executionTime = Date.now() - startTime;\n // 셀 출력 분석\n let stdout = '';\n let stderr = '';\n let result = null;\n let error = undefined;\n // cell.model과 outputs가 존재하는지 안전하게 체크\n const outputs = cell.model?.outputs;\n console.log('[ToolExecutor] After kernel idle - outputs count:', outputs?.length ?? 0, '| runSuccess:', runSuccess);\n if (outputs && outputs.length > 0) {\n for (let i = 0; i < outputs.length; i++) {\n const output = outputs.get(i);\n // 더 상세한 디버깅: 전체 output 구조 확인\n console.log(`[ToolExecutor] Output ${i} type:`, output.type);\n try {\n // toJSON이 있으면 전체 구조 확인\n const outputJson = output.toJSON?.() || output;\n console.log(`[ToolExecutor] Output ${i} full structure:`, JSON.stringify(outputJson, null, 2));\n }\n catch (e) {\n console.log(`[ToolExecutor] Output ${i} raw:`, output);\n }\n if (output.type === 'stream') {\n // CRITICAL: toJSON()으로 실제 데이터 추출 (직접 프로퍼티 접근은 undefined 반환 가능)\n const streamOutput = output.toJSON?.() || output;\n if (streamOutput.name === 'stdout') {\n stdout += streamOutput.text || '';\n }\n else if (streamOutput.name === 'stderr') {\n stderr += streamOutput.text || '';\n }\n }\n else if (output.type === 'execute_result' || output.type === 'display_data') {\n const data = output.data;\n if (!result) {\n result = data;\n }\n }\n else if (output.type === 'error') {\n // CRITICAL: output 모델 객체는 toJSON()으로 실제 데이터를 추출해야 함\n // 직접 프로퍼티 접근(output.ename)은 undefined를 반환할 수 있음\n const errorData = output.toJSON?.() || output;\n console.log('[ToolExecutor] Error output detected:', JSON.stringify(errorData));\n // 실제로 에러 내용이 있는 경우에만 에러로 처리\n if (errorData.ename || errorData.evalue) {\n error = {\n ename: errorData.ename,\n evalue: errorData.evalue,\n traceback: errorData.traceback || [],\n };\n console.log('[ToolExecutor] Error captured:', error.ename, '-', error.evalue);\n }\n }\n }\n }\n // NotebookActions.run()이 false를 반환했거나 error output이 있으면 실패\n // runSuccess가 false면 에러 output이 없어도 실패로 처리\n const status = (error || !runSuccess) ? 'error' : 'ok';\n // 디버깅: 실패 감지 상세 로그\n console.log('[ToolExecutor] Final status:', status);\n console.log('[ToolExecutor] - runSuccess:', runSuccess);\n console.log('[ToolExecutor] - error detected:', !!error);\n if (error) {\n console.log('[ToolExecutor] - error.ename:', error.ename);\n console.log('[ToolExecutor] - error.evalue:', error.evalue);\n }\n // runSuccess가 false인데 error가 없으면 stdout/stderr에서 에러 패턴 추출 시도\n if (!runSuccess && !error) {\n console.warn('[ToolExecutor] NotebookActions.run() failed but no error output captured!');\n console.log('[ToolExecutor] Attempting to extract error from stdout/stderr...');\n // stdout에서 에러 패턴 검색 (Python traceback 형식)\n const combinedOutput = stdout + '\\n' + stderr;\n const extractedError = this.extractErrorFromOutput(combinedOutput);\n if (extractedError) {\n console.log('[ToolExecutor] Extracted error from output:', extractedError);\n error = extractedError;\n }\n else {\n error = {\n ename: 'ExecutionError',\n evalue: 'Cell execution failed (NotebookActions.run returned false)',\n traceback: [],\n };\n }\n }\n return {\n status,\n stdout,\n stderr,\n result,\n error,\n executionTime,\n cellIndex,\n };\n }\n /**\n * 현재 노트북의 셀 개수 반환\n */\n getCellCount() {\n return this.notebook.content.model?.cells.length || 0;\n }\n /**\n * 특정 셀의 내용 반환\n */\n getCellContent(cellIndex) {\n const cell = this.notebook.content.widgets[cellIndex];\n return cell?.model?.sharedModel?.getSource() || '';\n }\n /**\n * 특정 셀의 출력 반환\n */\n getCellOutput(cellIndex) {\n const cell = this.notebook.content.widgets[cellIndex];\n // cell, cell.model, cell.model.outputs 모두 안전하게 체크\n const cellOutputs = cell?.model?.outputs;\n if (!cell || !cellOutputs) {\n return '';\n }\n const outputs = [];\n for (let i = 0; i < cellOutputs.length; i++) {\n const output = cellOutputs.get(i);\n if (output.type === 'stream') {\n outputs.push(output.text || '');\n }\n else if (output.type === 'execute_result' || output.type === 'display_data') {\n const data = output.data;\n if (data?.['text/plain']) {\n outputs.push(data['text/plain']);\n }\n }\n else if (output.type === 'error') {\n const errorOutput = output;\n outputs.push(`${errorOutput.ename}: ${errorOutput.evalue}`);\n }\n }\n return outputs.join('\\n');\n }\n /**\n * 커널 인터럽트\n */\n async interruptKernel() {\n const kernel = this.sessionContext.session?.kernel;\n if (kernel) {\n await kernel.interrupt();\n }\n }\n /**\n * 출력 텍스트에서 Python 에러 패턴을 추출\n * error 타입 출력이 캡처되지 않았을 때 stdout/stderr에서 에러 추출 시도\n */\n extractErrorFromOutput(output) {\n if (!output)\n return undefined;\n // 에러 타입 패턴들 (Python 표준 에러들)\n const errorPatterns = [\n /^(ModuleNotFoundError):\\s*(.+)$/m,\n /^(ImportError):\\s*(.+)$/m,\n /^(SyntaxError):\\s*(.+)$/m,\n /^(TypeError):\\s*(.+)$/m,\n /^(ValueError):\\s*(.+)$/m,\n /^(KeyError):\\s*(.+)$/m,\n /^(IndexError):\\s*(.+)$/m,\n /^(AttributeError):\\s*(.+)$/m,\n /^(NameError):\\s*(.+)$/m,\n /^(FileNotFoundError):\\s*(.+)$/m,\n /^(ZeroDivisionError):\\s*(.+)$/m,\n /^(RuntimeError):\\s*(.+)$/m,\n /^(PermissionError):\\s*(.+)$/m,\n /^(OSError):\\s*(.+)$/m,\n /^(IOError):\\s*(.+)$/m,\n /^(ConnectionError):\\s*(.+)$/m,\n /^(TimeoutError):\\s*(.+)$/m,\n ];\n for (const pattern of errorPatterns) {\n const match = output.match(pattern);\n if (match) {\n return {\n ename: match[1],\n evalue: match[2].trim(),\n traceback: [], // traceback 추출은 복잡하므로 생략\n };\n }\n }\n // Traceback이 있으면 마지막 에러 라인 추출 시도\n if (output.includes('Traceback (most recent call last):')) {\n // 마지막 줄에서 에러 타입: 메시지 패턴 찾기\n const lines = output.split('\\n').filter(l => l.trim());\n for (let i = lines.length - 1; i >= 0; i--) {\n const line = lines[i].trim();\n const errorMatch = line.match(/^(\\w+Error):\\s*(.+)$/);\n if (errorMatch) {\n return {\n ename: errorMatch[1],\n evalue: errorMatch[2].trim(),\n traceback: [],\n };\n }\n }\n }\n return undefined;\n }\n /**\n * 셀 삭제\n */\n deleteCell(cellIndex) {\n const model = this.notebook.content.model;\n if (model && cellIndex >= 0 && cellIndex < model.cells.length) {\n model.sharedModel.deleteCell(cellIndex);\n }\n }\n /**\n * 여러 셀 실행 (순차)\n */\n async executeMultipleCells(cellIndices) {\n const results = [];\n for (const index of cellIndices) {\n const result = await this.executeCellAndCapture(index);\n results.push(result);\n if (result.status === 'error') {\n break; // 에러 발생 시 중단\n }\n }\n return results;\n }\n}\nexport default ToolExecutor;\n","/**\n * ToolRegistry - 동적 도구 등록 및 관리\n *\n * 기존 switch문 기반 도구 라우팅을 Map 기반 레지스트리로 대체\n * - 동적 도구 등록/해제\n * - 위험 수준별 분류\n * - 카테고리별 조회\n */\n/**\n * 기본 승인 콜백 - 항상 승인 (개발/테스트용)\n */\nconst defaultApprovalCallback = async (request) => ({\n approved: true,\n requestId: request.id,\n timestamp: Date.now(),\n});\n/**\n * ToolRegistry 클래스\n * 싱글톤 패턴으로 구현 (전역 레지스트리)\n */\nexport class ToolRegistry {\n constructor() {\n this.tools = new Map();\n this.approvalCallback = defaultApprovalCallback;\n this.approvalRequired = true;\n }\n /**\n * 싱글톤 인스턴스 반환\n */\n static getInstance() {\n if (!ToolRegistry.instance) {\n ToolRegistry.instance = new ToolRegistry();\n }\n return ToolRegistry.instance;\n }\n /**\n * 테스트용 인스턴스 리셋\n */\n static resetInstance() {\n ToolRegistry.instance = new ToolRegistry();\n }\n /**\n * 도구 등록\n */\n register(definition) {\n if (this.tools.has(definition.name)) {\n console.warn(`[ToolRegistry] Tool '${definition.name}' already registered, overwriting`);\n }\n this.tools.set(definition.name, definition);\n console.log(`[ToolRegistry] Registered tool: ${definition.name} (${definition.riskLevel})`);\n }\n /**\n * 도구 해제\n */\n unregister(name) {\n const result = this.tools.delete(name);\n if (result) {\n console.log(`[ToolRegistry] Unregistered tool: ${name}`);\n }\n return result;\n }\n /**\n * 도구 조회\n */\n getTool(name) {\n return this.tools.get(name);\n }\n /**\n * 도구 존재 여부 확인\n */\n hasTool(name) {\n return this.tools.has(name);\n }\n /**\n * 등록된 모든 도구 반환\n */\n getAllTools() {\n return Array.from(this.tools.values());\n }\n /**\n * 카테고리별 도구 조회\n */\n getToolsByCategory(category) {\n return this.getAllTools().filter(tool => tool.category === category);\n }\n /**\n * 위험 수준별 도구 조회\n */\n getToolsByRiskLevel(riskLevel) {\n return this.getAllTools().filter(tool => tool.riskLevel === riskLevel);\n }\n /**\n * 승인이 필요한 도구들 조회\n */\n getToolsRequiringApproval() {\n return this.getAllTools().filter(tool => tool.requiresApproval);\n }\n /**\n * 등록된 도구 이름들 반환\n */\n getToolNames() {\n return Array.from(this.tools.keys());\n }\n /**\n * 승인 콜백 설정\n */\n setApprovalCallback(callback) {\n this.approvalCallback = callback;\n console.log('[ToolRegistry] Approval callback updated');\n }\n /**\n * 승인 필요 여부 설정\n */\n setApprovalRequired(required) {\n this.approvalRequired = required;\n console.log(`[ToolRegistry] Approval requirement set to: ${required}`);\n }\n /**\n * 승인 필요 여부 확인\n */\n isApprovalRequired() {\n return this.approvalRequired;\n }\n /**\n * 도구 실행 (승인 게이트 포함)\n */\n async executeTool(name, params, context) {\n const tool = this.getTool(name);\n if (!tool) {\n return {\n success: false,\n error: `Unknown tool: ${name}`,\n };\n }\n // 승인이 필요한 도구인 경우 승인 요청\n if (this.approvalRequired && tool.requiresApproval) {\n const request = {\n id: `${name}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n toolName: name,\n toolDefinition: tool,\n parameters: params,\n stepNumber: context.stepNumber,\n description: this.formatToolDescription(tool, params),\n timestamp: Date.now(),\n };\n console.log(`[ToolRegistry] Requesting approval for tool: ${name}`);\n const approvalResult = await this.approvalCallback(request);\n if (!approvalResult.approved) {\n console.log(`[ToolRegistry] Tool execution denied: ${name}, reason: ${approvalResult.reason}`);\n return {\n success: false,\n error: `Tool execution denied: ${approvalResult.reason || 'User rejected'}`,\n };\n }\n console.log(`[ToolRegistry] Tool approved: ${name}`);\n }\n // 도구 실행\n try {\n console.log(`[ToolRegistry] Executing tool: ${name}`);\n return await tool.executor(params, context);\n }\n catch (error) {\n console.error(`[ToolRegistry] Tool execution failed: ${name}`, error);\n return {\n success: false,\n error: error.message || `Failed to execute tool: ${name}`,\n };\n }\n }\n /**\n * 도구 설명 포맷팅 (승인 다이얼로그용)\n */\n formatToolDescription(tool, params) {\n const riskEmoji = {\n low: '🟢',\n medium: '🟡',\n high: '🟠',\n critical: '🔴',\n };\n let description = `${riskEmoji[tool.riskLevel]} ${tool.description}`;\n // 파라미터 요약 추가\n if (params) {\n const paramSummary = Object.entries(params)\n .map(([key, value]) => {\n const valueStr = typeof value === 'string'\n ? value.length > 50 ? value.substring(0, 50) + '...' : value\n : JSON.stringify(value);\n return `${key}: ${valueStr}`;\n })\n .join(', ');\n if (paramSummary) {\n description += `\\n파라미터: ${paramSummary}`;\n }\n }\n return description;\n }\n /**\n * 레지스트리 상태 출력 (디버깅용)\n */\n printStatus() {\n console.log('[ToolRegistry] Status:');\n console.log(` - Registered tools: ${this.tools.size}`);\n console.log(` - Approval required: ${this.approvalRequired}`);\n console.log(' - Tools:');\n for (const [name, tool] of this.tools) {\n console.log(` * ${name} (${tool.category}, ${tool.riskLevel}, approval: ${tool.requiresApproval})`);\n }\n }\n}\n/**\n * 기본 도구 정의들 (빌트인)\n * ToolExecutor의 메서드들을 executor로 연결하기 위해 나중에 초기화\n */\nexport const BUILTIN_TOOL_DEFINITIONS = [\n // ─────────────────────────────────────────────────────────────────────────\n // 기본 도구 (Cell 작업)\n // ─────────────────────────────────────────────────────────────────────────\n {\n name: 'jupyter_cell',\n description: 'Jupyter 코드 셀 생성/수정/실행',\n riskLevel: 'medium',\n requiresApproval: false,\n category: 'cell',\n },\n {\n name: 'markdown',\n description: 'Jupyter 마크다운 셀 생성/수정',\n riskLevel: 'low',\n requiresApproval: false,\n category: 'cell',\n },\n {\n name: 'final_answer',\n description: '작업 완료 및 최종 답변 반환',\n riskLevel: 'low',\n requiresApproval: false,\n category: 'answer',\n },\n // ─────────────────────────────────────────────────────────────────────────\n // 확장 도구 (파일 시스템)\n // ─────────────────────────────────────────────────────────────────────────\n {\n name: 'read_file',\n description: '파일 내용 읽기 (읽기 전용)',\n riskLevel: 'low',\n requiresApproval: false,\n category: 'file',\n },\n {\n name: 'write_file',\n description: '파일에 내용 쓰기 (덮어쓰기 가능)',\n riskLevel: 'high',\n requiresApproval: true,\n category: 'file',\n },\n {\n name: 'list_files',\n description: '디렉토리 내 파일/폴더 목록 조회',\n riskLevel: 'low',\n requiresApproval: false,\n category: 'file',\n },\n {\n name: 'search_files',\n description: '파일 내용 검색 (grep/ripgrep 스타일)',\n riskLevel: 'medium',\n requiresApproval: false,\n category: 'file',\n },\n // ─────────────────────────────────────────────────────────────────────────\n // 확장 도구 (시스템)\n // ─────────────────────────────────────────────────────────────────────────\n {\n name: 'execute_command_tool',\n description: '셸 명령 실행 (위험 명령만 승인 필요)',\n riskLevel: 'critical',\n requiresApproval: false,\n category: 'system',\n },\n // ─────────────────────────────────────────────────────────────────────────\n // 확장 도구 Phase 2 (패키지/린트/셀/노트북/폴더/삭제)\n // ─────────────────────────────────────────────────────────────────────────\n {\n name: 'install_package',\n description: 'pip 패키지 설치 (버전 지정 가능)',\n riskLevel: 'high',\n requiresApproval: true,\n category: 'system',\n },\n {\n name: 'lint_file',\n description: 'Python 파일 린트 검사 및 자동 수정 (ruff/pylint/flake8)',\n riskLevel: 'medium',\n requiresApproval: false,\n category: 'file',\n },\n {\n name: 'delete_cell',\n description: 'Jupyter 노트북 셀 삭제',\n riskLevel: 'medium',\n requiresApproval: true,\n category: 'cell',\n },\n {\n name: 'get_cell_output',\n description: '특정 셀의 실행 출력 조회',\n riskLevel: 'low',\n requiresApproval: false,\n category: 'cell',\n },\n {\n name: 'create_notebook',\n description: '새 Jupyter 노트북 파일 생성',\n riskLevel: 'medium',\n requiresApproval: false,\n category: 'file',\n },\n {\n name: 'create_folder',\n description: '새 폴더(디렉토리) 생성',\n riskLevel: 'low',\n requiresApproval: false,\n category: 'file',\n },\n {\n name: 'delete_file',\n description: '파일 또는 폴더 삭제',\n riskLevel: 'critical',\n requiresApproval: true,\n category: 'file',\n },\n // ─────────────────────────────────────────────────────────────────────────\n // 확장 도구 Phase 3 (Git/Test/Refactor)\n // ─────────────────────────────────────────────────────────────────────────\n {\n name: 'git_operations',\n description: 'Git 버전 관리 작업 (status, diff, commit, push, pull 등)',\n riskLevel: 'high',\n requiresApproval: false,\n category: 'system',\n },\n {\n name: 'run_tests',\n description: '테스트 실행 (pytest/unittest) 및 결과 파싱',\n riskLevel: 'medium',\n requiresApproval: false,\n category: 'system',\n },\n {\n name: 'refactor_code',\n description: '코드 리팩토링 (변수/함수 리네임, 함수 추출)',\n riskLevel: 'high',\n requiresApproval: true,\n category: 'file',\n },\n];\n/**\n * 위험한 셸 명령 패턴들\n * execute_command_tool에서 이 패턴에 매칭되는 명령은 사용자 승인 필요\n */\nexport const DANGEROUS_COMMAND_PATTERNS = [\n // 파일 삭제/제거\n /\\brm\\s+(-[rRfF]+\\s+)?/i,\n /\\brmdir\\b/i,\n // 권한 상승/변경\n /\\bsudo\\b/i,\n /\\bsu\\b/i,\n /\\bchmod\\s+7[0-7][0-7]/i,\n /\\bchown\\b/i,\n // 시스템 파괴\n />\\s*\\/dev\\//i,\n /\\bmkfs\\b/i,\n /\\bdd\\b/i,\n // 원격 코드 실행\n /\\bcurl\\s+.*\\|\\s*(ba)?sh/i,\n /\\bwget\\s+.*\\|\\s*(ba)?sh/i,\n /\\beval\\s+/i,\n // 네트워크 위험\n /\\bnc\\s+-[el]/i,\n /\\biptables\\b/i, // iptables 조작\n];\n/**\n * 위험 수준 우선순위 (비교용)\n */\nexport const RISK_LEVEL_PRIORITY = {\n low: 0,\n medium: 1,\n high: 2,\n critical: 3,\n};\n/**\n * 위험 수준 비교\n */\nexport function compareRiskLevels(a, b) {\n return RISK_LEVEL_PRIORITY[a] - RISK_LEVEL_PRIORITY[b];\n}\n/**\n * 특정 위험 수준 이상인지 확인\n */\nexport function isRiskLevelAtLeast(level, threshold) {\n return RISK_LEVEL_PRIORITY[level] >= RISK_LEVEL_PRIORITY[threshold];\n}\nexport default ToolRegistry;\n","/**\n * Auto-Agent Type Definitions\n * HuggingFace Jupyter Agent 패턴 기반 Tool Calling 구조\n */\n// 속도 프리셋 (ms 단위 지연) - 전반적으로 느리게 조정\nexport const EXECUTION_SPEED_DELAYS = {\n 'instant': 0,\n 'fast': 800,\n 'normal': 1500,\n 'slow': 3000,\n 'step-by-step': -1, // -1은 수동 진행 의미\n};\nexport const DEFAULT_AUTO_AGENT_CONFIG = {\n maxRetriesPerStep: 3,\n executionTimeout: 30000,\n enableSafetyCheck: true,\n showDetailedProgress: true,\n executionSpeed: 'normal',\n stepDelay: 1500,\n autoScrollToCell: true,\n highlightCurrentCell: true,\n};\n// ═══════════════════════════════════════════════════════════════════════════\n// State Verification Types (Phase 1: 상태 검증 레이어)\n// ═══════════════════════════════════════════════════════════════════════════\n/**\n * 신뢰도 임계값 상수\n * - PROCEED (0.8): 계속 진행\n * - WARNING (0.6): 경고 로그, 진행\n * - REPLAN (0.4): 리플래닝 트리거\n * - ESCALATE (0.2): 사용자 개입 필요\n */\nexport const CONFIDENCE_THRESHOLDS = {\n PROCEED: 0.8,\n WARNING: 0.6,\n REPLAN: 0.4,\n ESCALATE: 0.2,\n};\n/**\n * 기본 컨텍스트 예산 설정\n */\nexport const DEFAULT_CONTEXT_BUDGET = {\n maxTokens: 8000,\n warningThreshold: 0.75,\n reservedForResponse: 2000,\n};\n/**\n * 기본 체크포인트 설정\n */\nexport const DEFAULT_CHECKPOINT_CONFIG = {\n maxCheckpoints: 10,\n autoSave: true,\n saveToNotebookMetadata: false,\n};\n","/**\n * Core type definitions for Jupyter Agent extension\n */\nexport var CellAction;\n(function (CellAction) {\n CellAction[\"EXPLAIN\"] = \"explain\";\n CellAction[\"FIX\"] = \"fix\";\n CellAction[\"CUSTOM_PROMPT\"] = \"custom_prompt\";\n})(CellAction || (CellAction = {}));\nexport var AgentEvent;\n(function (AgentEvent) {\n AgentEvent[\"CELL_ACTION\"] = \"hdsp-agent:cell-action\";\n AgentEvent[\"CONFIG_CHANGED\"] = \"hdsp-agent:config-changed\";\n AgentEvent[\"MESSAGE_SENT\"] = \"hdsp-agent:message-sent\";\n AgentEvent[\"MESSAGE_RECEIVED\"] = \"hdsp-agent:message-received\";\n})(AgentEvent || (AgentEvent = {}));\n// ═══════════════════════════════════════════════════════════════════════════\n// File Action Types (Python 파일 에러 수정용)\n// ═══════════════════════════════════════════════════════════════════════════\nexport var FileAction;\n(function (FileAction) {\n FileAction[\"FIX\"] = \"fix\";\n FileAction[\"EXPLAIN\"] = \"explain\";\n FileAction[\"CUSTOM\"] = \"custom\";\n})(FileAction || (FileAction = {}));\n","/**\n * SafetyChecker - 코드 안전 검사기\n *\n * 위험한 코드 패턴을 사전에 검출하여 실행 전 경고\n * NBI (Notebook Intelligence) 패턴 참조\n */\nconst DANGEROUS_PATTERNS = [\n // 시스템 명령 관련\n {\n pattern: /rm\\s+-rf\\s+[\\/~]/,\n description: '재귀 삭제 명령 (rm -rf)',\n severity: 'critical',\n category: 'system',\n },\n {\n pattern: /os\\.system\\s*\\(/,\n description: '시스템 명령 실행 (os.system)',\n severity: 'critical',\n category: 'system',\n },\n {\n pattern: /subprocess\\.(run|call|Popen|check_output)\\s*\\(/,\n description: '서브프로세스 실행 (subprocess)',\n severity: 'warning',\n category: 'system',\n },\n {\n pattern: /os\\.exec\\w*\\s*\\(/,\n description: 'OS exec 명령 실행',\n severity: 'critical',\n category: 'system',\n },\n // 동적 코드 실행\n {\n pattern: /\\beval\\s*\\(/,\n description: '동적 코드 실행 (eval)',\n severity: 'critical',\n category: 'security',\n },\n {\n pattern: /\\bexec\\s*\\(/,\n description: '동적 코드 실행 (exec)',\n severity: 'critical',\n category: 'security',\n },\n {\n pattern: /__import__\\s*\\(/,\n description: '동적 모듈 임포트 (__import__)',\n severity: 'warning',\n category: 'security',\n },\n {\n pattern: /compile\\s*\\([^)]*exec/,\n description: '동적 코드 컴파일 (compile)',\n severity: 'critical',\n category: 'security',\n },\n // 파일 시스템 관련\n {\n pattern: /open\\s*\\([^)]*,\\s*['\"]w/,\n description: '파일 쓰기 모드 열기',\n severity: 'warning',\n category: 'file',\n },\n {\n pattern: /shutil\\.(rmtree|move|copy)/,\n description: '파일/디렉토리 조작 (shutil)',\n severity: 'warning',\n category: 'file',\n },\n {\n pattern: /os\\.(remove|unlink|rmdir|makedirs|rename)/,\n description: '파일 시스템 변경 (os)',\n severity: 'warning',\n category: 'file',\n },\n {\n pattern: /pathlib\\.Path[^)]*\\.(unlink|rmdir|write)/,\n description: '파일 시스템 변경 (pathlib)',\n severity: 'warning',\n category: 'file',\n },\n // 네트워크 관련\n {\n pattern: /requests\\.(get|post|put|delete|patch)\\s*\\([^)]*\\)/,\n description: 'HTTP 요청 (requests)',\n severity: 'info',\n category: 'network',\n },\n {\n pattern: /urllib\\.(request|urlopen)/,\n description: 'URL 요청 (urllib)',\n severity: 'info',\n category: 'network',\n },\n {\n pattern: /socket\\./,\n description: '소켓 통신 (socket)',\n severity: 'warning',\n category: 'network',\n },\n // 무한 루프 패턴\n {\n pattern: /while\\s+True\\s*:/,\n description: '무한 루프 (while True)',\n severity: 'warning',\n category: 'loop',\n },\n {\n pattern: /while\\s+1\\s*:/,\n description: '무한 루프 (while 1)',\n severity: 'warning',\n category: 'loop',\n },\n {\n pattern: /for\\s+\\w+\\s+in\\s+iter\\s*\\(\\s*int\\s*,/,\n description: '무한 반복자 (iter(int, ...))',\n severity: 'warning',\n category: 'loop',\n },\n // 민감 정보 관련\n {\n pattern: /os\\.environ\\s*\\[/,\n description: '환경 변수 접근',\n severity: 'info',\n category: 'security',\n },\n {\n pattern: /(password|secret|api_key|token)\\s*=\\s*['\"][^'\"]+['\"]/i,\n description: '하드코딩된 민감 정보',\n severity: 'warning',\n category: 'security',\n },\n // 위험한 모듈\n {\n pattern: /import\\s+pickle|from\\s+pickle\\s+import/,\n description: 'Pickle 모듈 (역직렬화 취약점)',\n severity: 'info',\n category: 'security',\n },\n {\n pattern: /import\\s+ctypes|from\\s+ctypes\\s+import/,\n description: 'ctypes 모듈 (저수준 메모리 접근)',\n severity: 'warning',\n category: 'security',\n },\n];\nexport class SafetyChecker {\n constructor(config) {\n this.config = {\n enableSafetyCheck: true,\n blockDangerousPatterns: false,\n requireConfirmation: true,\n maxExecutionTime: 30,\n ...config,\n };\n }\n /**\n * 코드 안전성 검사\n */\n checkCodeSafety(code) {\n if (!this.config.enableSafetyCheck) {\n return { safe: true, warnings: [] };\n }\n const warnings = [];\n const blockedPatterns = [];\n for (const dangerous of DANGEROUS_PATTERNS) {\n if (dangerous.pattern.test(code)) {\n const message = `[${dangerous.severity.toUpperCase()}] ${dangerous.description}`;\n if (dangerous.severity === 'critical' && this.config.blockDangerousPatterns) {\n blockedPatterns.push(message);\n }\n else {\n warnings.push(message);\n }\n }\n }\n const safe = blockedPatterns.length === 0;\n return {\n safe,\n warnings,\n blockedPatterns: blockedPatterns.length > 0 ? blockedPatterns : undefined,\n };\n }\n /**\n * 여러 코드 블록 검사\n */\n checkMultipleCodeBlocks(codes) {\n const allWarnings = [];\n const allBlockedPatterns = [];\n for (let i = 0; i < codes.length; i++) {\n const result = this.checkCodeSafety(codes[i]);\n result.warnings.forEach((w) => allWarnings.push(`[Block ${i + 1}] ${w}`));\n if (result.blockedPatterns) {\n result.blockedPatterns.forEach((b) => allBlockedPatterns.push(`[Block ${i + 1}] ${b}`));\n }\n }\n return {\n safe: allBlockedPatterns.length === 0,\n warnings: allWarnings,\n blockedPatterns: allBlockedPatterns.length > 0 ? allBlockedPatterns : undefined,\n };\n }\n /**\n * 무한 루프 가능성 검사\n */\n checkInfiniteLoopRisk(code) {\n const loopPatterns = DANGEROUS_PATTERNS.filter((p) => p.category === 'loop');\n return loopPatterns.some((p) => p.pattern.test(code));\n }\n /**\n * 파일 시스템 변경 위험 검사\n */\n checkFileSystemRisk(code) {\n const filePatterns = DANGEROUS_PATTERNS.filter((p) => p.category === 'file');\n return filePatterns.some((p) => p.pattern.test(code));\n }\n /**\n * 네트워크 활동 검사\n */\n checkNetworkActivity(code) {\n const networkPatterns = DANGEROUS_PATTERNS.filter((p) => p.category === 'network');\n return networkPatterns.some((p) => p.pattern.test(code));\n }\n /**\n * 실행 시간 제한 값 반환\n */\n getMaxExecutionTime() {\n return this.config.maxExecutionTime * 1000; // ms로 변환\n }\n /**\n * 확인 필요 여부\n */\n requiresConfirmation(result) {\n if (!this.config.requireConfirmation) {\n return false;\n }\n return result.warnings.length > 0 || (result.blockedPatterns?.length || 0) > 0;\n }\n /**\n * 설정 업데이트\n */\n updateConfig(config) {\n this.config = { ...this.config, ...config };\n }\n /**\n * 현재 설정 반환\n */\n getConfig() {\n return { ...this.config };\n }\n /**\n * 사용자 친화적 경고 메시지 생성\n */\n formatWarningsForDisplay(result) {\n if (result.warnings.length === 0 && !result.blockedPatterns?.length) {\n return '';\n }\n const lines = [];\n if (result.blockedPatterns && result.blockedPatterns.length > 0) {\n lines.push('⛔ 차단된 패턴:');\n result.blockedPatterns.forEach((p) => lines.push(` • ${p}`));\n }\n if (result.warnings.length > 0) {\n lines.push('⚠️ 경고:');\n result.warnings.forEach((w) => lines.push(` • ${w}`));\n }\n return lines.join('\\n');\n }\n}\nexport default SafetyChecker;\n","/**\n * Markdown to HTML converter with syntax highlighting\n * Based on chrome_agent's formatMarkdownToHtml implementation\n */\n/**\n * Simple hash function for generating stable IDs from content\n * Uses djb2 algorithm for fast string hashing\n */\nfunction simpleHash(str) {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = ((hash << 5) + hash) + str.charCodeAt(i);\n hash = hash & hash; // Convert to 32-bit integer\n }\n // Convert to positive hex string\n return Math.abs(hash).toString(36);\n}\n/**\n * Escape HTML special characters\n */\nexport function escapeHtml(text) {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n}\n/**\n * Normalize indentation in code blocks\n */\nexport function normalizeIndentation(code) {\n const lines = code.split('\\n');\n // Find minimum indent from non-empty lines\n let minIndent = Infinity;\n const nonEmptyLines = [];\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line.trim().length > 0) {\n const match = line.match(/^(\\s*)/);\n const indent = match ? match[1].length : 0;\n minIndent = Math.min(minIndent, indent);\n nonEmptyLines.push(i);\n }\n }\n // If no indent or no non-empty lines, return original\n if (minIndent === Infinity || minIndent === 0) {\n return code;\n }\n // Remove minimum indent from all lines\n const normalized = lines.map(line => {\n if (line.trim().length === 0) {\n return '';\n }\n return line.substring(minIndent);\n });\n return normalized.join('\\n');\n}\nfunction extractNextItemsBlock(text) {\n if (!text.trim())\n return null;\n const fencedRegex = /```(\\w+)?\\n([\\s\\S]*?)```/g;\n let match;\n while ((match = fencedRegex.exec(text)) !== null) {\n const lang = (match[1] || '').toLowerCase();\n const content = match[2].trim();\n if (!content)\n continue;\n if (lang && lang !== 'json' && !content.includes('\"next_items\"')) {\n continue;\n }\n const items = parseNextItemsPayload(content);\n if (items) {\n const placeholder = `__NEXT_ITEMS_${Math.random().toString(36).slice(2, 11)}__`;\n const updated = text.slice(0, match.index) + placeholder + text.slice(match.index + match[0].length);\n return { items, placeholder, text: updated };\n }\n }\n const range = findNextItemsJsonRange(text);\n if (!range)\n return null;\n const candidate = text.slice(range.start, range.end + 1);\n const items = parseNextItemsPayload(candidate);\n if (!items)\n return null;\n let replacementStart = range.start;\n const lineStart = text.lastIndexOf('\\n', range.start - 1) + 1;\n const linePrefix = text.slice(lineStart, range.start).trim();\n const normalizedPrefix = linePrefix.replace(/[::]$/, '').toLowerCase();\n if (normalizedPrefix === 'json') {\n replacementStart = lineStart;\n }\n const placeholder = `__NEXT_ITEMS_${Math.random().toString(36).slice(2, 11)}__`;\n const updated = text.slice(0, replacementStart) + placeholder + text.slice(range.end + 1);\n return { items, placeholder, text: updated };\n}\nfunction findNextItemsJsonRange(text) {\n const key = '\"next_items\"';\n let searchIndex = text.indexOf(key);\n while (searchIndex !== -1) {\n const start = text.lastIndexOf('{', searchIndex);\n if (start === -1)\n return null;\n const end = findMatchingBrace(text, start);\n if (end !== -1) {\n return { start, end };\n }\n searchIndex = text.indexOf(key, searchIndex + key.length);\n }\n return null;\n}\nfunction findMatchingBrace(text, start) {\n let depth = 0;\n let inString = false;\n let escaped = false;\n for (let i = start; i < text.length; i++) {\n const char = text[i];\n if (inString) {\n if (escaped) {\n escaped = false;\n continue;\n }\n if (char === '\\\\') {\n escaped = true;\n continue;\n }\n if (char === '\"') {\n inString = false;\n }\n continue;\n }\n if (char === '\"') {\n inString = true;\n continue;\n }\n if (char === '{') {\n depth += 1;\n }\n else if (char === '}') {\n depth -= 1;\n if (depth === 0) {\n return i;\n }\n }\n }\n return -1;\n}\nfunction parseNextItemsPayload(payload) {\n if (!payload.startsWith('{') || !payload.endsWith('}'))\n return null;\n try {\n const parsed = JSON.parse(payload);\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))\n return null;\n const nextItemsRaw = parsed.next_items;\n if (!Array.isArray(nextItemsRaw))\n return null;\n const items = nextItemsRaw\n .map((item) => {\n if (!item || typeof item !== 'object')\n return null;\n const subject = typeof item.subject === 'string'\n ? item.subject.trim()\n : '';\n const description = typeof item.description === 'string'\n ? item.description.trim()\n : '';\n if (!subject && !description)\n return null;\n return { subject, description };\n })\n .filter((item) => Boolean(item));\n return items.length > 0 ? items : null;\n }\n catch {\n return null;\n }\n}\nfunction renderNextItemsList(items) {\n // Simple arrow icon\n const arrowSvg = `\n<svg class=\"jp-next-items-icon\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z\"/>\n</svg>`;\n const listItems = items.map((item) => {\n const subject = escapeHtml(item.subject);\n const description = escapeHtml(item.description);\n const subjectHtml = subject ? `<div class=\"jp-next-items-subject\">${subject}</div>` : '';\n const descriptionHtml = description ? `<div class=\"jp-next-items-description\">${description}</div>` : '';\n return `\n<li class=\"jp-next-items-item\" data-next-item=\"true\" role=\"button\" tabindex=\"0\">\n <div class=\"jp-next-items-text\">\n ${subjectHtml}\n ${descriptionHtml}\n </div>\n ${arrowSvg}\n</li>`;\n }).join('');\n return `\n<div class=\"jp-next-items\" data-next-items=\"true\">\n <div class=\"jp-next-items-header\">다음 단계 제안</div>\n <ul class=\"jp-next-items-list\" role=\"list\">\n ${listItems}\n </ul>\n</div>`;\n}\n/**\n * Highlight Python code with inline styles\n */\nexport function highlightPython(code) {\n let highlighted = code;\n // Color styles (matching chrome_agent)\n const styles = {\n COMMENT: 'color: #6a9955; font-style: italic;',\n STRING: 'color: #ce9178;',\n NUMBER: 'color: #b5cea8;',\n KEYWORD: 'color: #c586c0; font-weight: bold;',\n BUILTIN: 'color: #dcdcaa;',\n FUNCTION: 'color: #4fc1ff; font-weight: bold;',\n OPERATOR: 'color: #d4d4d4;',\n BRACKET: 'color: #d4d4d4;'\n };\n // Use placeholders to preserve order\n const placeholders = [];\n let placeholderIndex = 0;\n // Comments (process first)\n highlighted = highlighted.replace(/(#.*$)/gm, (match) => {\n const id = `__PH${placeholderIndex++}__`;\n placeholders.push({\n id,\n html: `<span style=\"${styles.COMMENT}\">${escapeHtml(match)}</span>`\n });\n return id;\n });\n // Triple-quoted strings\n highlighted = highlighted.replace(/(['\"]{3})([\\s\\S]*?)(\\1)/g, (match) => {\n const id = `__PH${placeholderIndex++}__`;\n placeholders.push({\n id,\n html: `<span style=\"${styles.STRING}\">${escapeHtml(match)}</span>`\n });\n return id;\n });\n // Regular strings\n highlighted = highlighted.replace(/(['\"])([^'\"]*?)(\\1)/g, (match) => {\n const id = `__PH${placeholderIndex++}__`;\n placeholders.push({\n id,\n html: `<span style=\"${styles.STRING}\">${escapeHtml(match)}</span>`\n });\n return id;\n });\n // Numbers\n highlighted = highlighted.replace(/\\b(\\d+\\.?\\d*)\\b/g, (match) => {\n const id = `__PH${placeholderIndex++}__`;\n placeholders.push({\n id,\n html: `<span style=\"${styles.NUMBER}\">${match}</span>`\n });\n return id;\n });\n // Python keywords\n const keywords = [\n 'def', 'class', 'if', 'elif', 'else', 'for', 'while', 'return', 'import',\n 'from', 'as', 'try', 'except', 'finally', 'with', 'lambda', 'yield',\n 'async', 'await', 'pass', 'break', 'continue', 'raise', 'assert', 'del',\n 'global', 'nonlocal', 'True', 'False', 'None', 'and', 'or', 'not', 'in', 'is'\n ];\n keywords.forEach(keyword => {\n const regex = new RegExp(`\\\\b${keyword}\\\\b`, 'g');\n highlighted = highlighted.replace(regex, (match) => {\n const id = `__PH${placeholderIndex++}__`;\n placeholders.push({\n id,\n html: `<span style=\"${styles.KEYWORD}\">${match}</span>`\n });\n return id;\n });\n });\n // Built-in functions\n const builtins = [\n 'print', 'len', 'range', 'str', 'int', 'float', 'list', 'dict', 'tuple',\n 'set', 'bool', 'type', 'isinstance', 'issubclass', 'hasattr', 'getattr',\n 'setattr', 'delattr', 'dir', 'vars', 'locals', 'globals', 'input', 'open',\n 'file', 'abs', 'all', 'any', 'bin', 'chr', 'ord', 'hex', 'oct', 'pow',\n 'round', 'sum', 'min', 'max', 'sorted', 'reversed', 'enumerate', 'zip',\n 'map', 'filter', 'reduce'\n ];\n builtins.forEach(builtin => {\n const regex = new RegExp(`\\\\b${builtin}\\\\b`, 'g');\n highlighted = highlighted.replace(regex, (match) => {\n const id = `__PH${placeholderIndex++}__`;\n placeholders.push({\n id,\n html: `<span style=\"${styles.BUILTIN}\">${match}</span>`\n });\n return id;\n });\n });\n // Function definitions - def keyword followed by function name\n highlighted = highlighted.replace(/(__PH\\d+__\\s+)([a-zA-Z_][a-zA-Z0-9_]*)/g, (match, defPart, funcName) => {\n const defPlaceholder = placeholders.find(p => p.id === defPart.trim());\n if (defPlaceholder && defPlaceholder.html.includes('def')) {\n const id = `__PH${placeholderIndex++}__`;\n placeholders.push({\n id,\n html: `<span style=\"${styles.FUNCTION}\">${funcName}</span>`\n });\n return defPart + id;\n }\n return match;\n });\n // Process remaining text - escape HTML and handle operators/brackets\n highlighted = highlighted.split(/(__PH\\d+__)/g).map(part => {\n if (part.match(/^__PH\\d+__$/)) {\n return part; // Keep placeholder as is\n }\n // Escape HTML\n part = escapeHtml(part);\n // Operators\n part = part.replace(/([+\\-*/%=<>!&|^~]+)/g, `<span style=\"${styles.OPERATOR}\">$1</span>`);\n // Brackets and delimiters\n part = part.replace(/([()[\\]{}])/g, `<span style=\"${styles.BRACKET}\">$1</span>`);\n return part;\n }).join('');\n // Replace placeholders with actual HTML\n placeholders.forEach(ph => {\n highlighted = highlighted.replace(ph.id, ph.html);\n });\n return highlighted;\n}\n/**\n * Highlight diff output with colors for additions/deletions\n */\nexport function highlightDiff(code) {\n const lines = code.split('\\n');\n const highlightedLines = lines.map(line => {\n const escapedLine = escapeHtml(line);\n // File headers (--- and +++)\n if (line.startsWith('---') || line.startsWith('+++')) {\n return `<span class=\"diff-header\">${escapedLine}</span>`;\n }\n // Hunk headers (@@ ... @@)\n if (line.startsWith('@@')) {\n return `<span class=\"diff-hunk\">${escapedLine}</span>`;\n }\n // Added lines\n if (line.startsWith('+')) {\n return `<span class=\"diff-add\">${escapedLine}</span>`;\n }\n // Removed lines\n if (line.startsWith('-')) {\n return `<span class=\"diff-del\">${escapedLine}</span>`;\n }\n // Context lines (unchanged)\n return `<span class=\"diff-context\">${escapedLine}</span>`;\n });\n return highlightedLines.join('\\n');\n}\n/**\n * Count additions and deletions in diff code\n */\nexport function countDiffLines(code) {\n const lines = code.split('\\n');\n let additions = 0;\n let deletions = 0;\n for (const line of lines) {\n // Skip file headers\n if (line.startsWith('+++') || line.startsWith('---'))\n continue;\n if (line.startsWith('+'))\n additions++;\n else if (line.startsWith('-'))\n deletions++;\n }\n return { additions, deletions };\n}\n/**\n * Highlight JavaScript code\n */\nexport function highlightJavaScript(code) {\n const escaped = escapeHtml(code);\n const lines = escaped.split('\\n');\n const keywords = [\n 'function', 'const', 'let', 'var', 'if', 'else', 'for', 'while',\n 'return', 'class', 'import', 'export', 'from', 'async', 'await',\n 'new', 'this', 'null', 'undefined', 'true', 'false', 'typeof'\n ];\n let inMultilineComment = false;\n const highlightedLines = lines.map(line => {\n // Check for multiline comment continuation\n if (inMultilineComment) {\n const endIndex = line.indexOf('*/');\n if (endIndex !== -1) {\n inMultilineComment = false;\n return `<span style=\"color: #6a9955; font-style: italic;\">${line.substring(0, endIndex + 2)}</span>` +\n highlightJSTokens(line.substring(endIndex + 2), keywords);\n }\n return `<span style=\"color: #6a9955; font-style: italic;\">${line}</span>`;\n }\n // Single-line comment\n const commentMatch = line.match(/^(\\s*)(\\/\\/.*)$/);\n if (commentMatch) {\n return commentMatch[1] + `<span style=\"color: #6a9955; font-style: italic;\">${commentMatch[2]}</span>`;\n }\n // Multiline comment start\n const multiCommentStart = line.indexOf('/*');\n if (multiCommentStart !== -1) {\n const multiCommentEnd = line.indexOf('*/', multiCommentStart);\n if (multiCommentEnd !== -1) {\n return highlightJSTokens(line.substring(0, multiCommentStart), keywords) +\n `<span style=\"color: #6a9955; font-style: italic;\">${line.substring(multiCommentStart, multiCommentEnd + 2)}</span>` +\n highlightJSTokens(line.substring(multiCommentEnd + 2), keywords);\n }\n else {\n inMultilineComment = true;\n return highlightJSTokens(line.substring(0, multiCommentStart), keywords) +\n `<span style=\"color: #6a9955; font-style: italic;\">${line.substring(multiCommentStart)}</span>`;\n }\n }\n // Comment in middle of line\n const commentIndex = line.indexOf('//');\n if (commentIndex !== -1) {\n return highlightJSTokens(line.substring(0, commentIndex), keywords) +\n `<span style=\"color: #6a9955; font-style: italic;\">${line.substring(commentIndex)}</span>`;\n }\n return highlightJSTokens(line, keywords);\n });\n return highlightedLines.join('\\n');\n}\n/**\n * Highlight JavaScript tokens (keywords, strings, numbers)\n */\nfunction highlightJSTokens(line, keywords) {\n const container = document.createElement('span');\n let i = 0;\n while (i < line.length) {\n // String check (template literal, double quote, single quote)\n if (line[i] === '`') {\n let j = i + 1;\n let escaped = false;\n while (j < line.length) {\n if (line[j] === '\\\\' && !escaped) {\n escaped = true;\n j++;\n continue;\n }\n if (line[j] === '`' && !escaped)\n break;\n escaped = false;\n j++;\n }\n if (j < line.length && line[j] === '`') {\n const span = document.createElement('span');\n span.style.color = '#ce9178';\n span.textContent = line.substring(i, j + 1);\n container.appendChild(span);\n i = j + 1;\n continue;\n }\n }\n if (line[i] === '\"') {\n let j = i + 1;\n let escaped = false;\n while (j < line.length) {\n if (line[j] === '\\\\' && !escaped) {\n escaped = true;\n j++;\n continue;\n }\n if (line[j] === '\"' && !escaped)\n break;\n escaped = false;\n j++;\n }\n if (j < line.length && line[j] === '\"') {\n const span = document.createElement('span');\n span.style.color = '#ce9178';\n span.textContent = line.substring(i, j + 1);\n container.appendChild(span);\n i = j + 1;\n continue;\n }\n // Unclosed string - treat rest as string\n const span = document.createElement('span');\n span.style.color = '#ce9178';\n span.textContent = line.substring(i);\n container.appendChild(span);\n break;\n }\n if (line[i] === '\\'') {\n let j = i + 1;\n let escaped = false;\n while (j < line.length) {\n if (line[j] === '\\\\' && !escaped) {\n escaped = true;\n j++;\n continue;\n }\n if (line[j] === '\\'' && !escaped)\n break;\n escaped = false;\n j++;\n }\n if (j < line.length && line[j] === '\\'') {\n const span = document.createElement('span');\n span.style.color = '#ce9178';\n span.textContent = line.substring(i, j + 1);\n container.appendChild(span);\n i = j + 1;\n continue;\n }\n // Unclosed string\n const span = document.createElement('span');\n span.style.color = '#ce9178';\n span.textContent = line.substring(i);\n container.appendChild(span);\n break;\n }\n // Number check\n if (/\\d/.test(line[i])) {\n let j = i;\n while (j < line.length && /[\\d.]/.test(line[j])) {\n j++;\n }\n const span = document.createElement('span');\n span.style.color = '#b5cea8';\n span.textContent = line.substring(i, j);\n container.appendChild(span);\n i = j;\n continue;\n }\n // Word check (keyword or identifier)\n if (/[a-zA-Z_$]/.test(line[i])) {\n let j = i;\n while (j < line.length && /[a-zA-Z0-9_$]/.test(line[j])) {\n j++;\n }\n const word = line.substring(i, j);\n if (keywords.includes(word)) {\n const span = document.createElement('span');\n span.style.color = '#569cd6';\n span.style.fontWeight = '500';\n span.textContent = word;\n container.appendChild(span);\n }\n else {\n const textNode = document.createTextNode(word);\n container.appendChild(textNode);\n }\n i = j;\n continue;\n }\n // Regular character\n const textNode = document.createTextNode(line[i]);\n container.appendChild(textNode);\n i++;\n }\n return container.innerHTML;\n}\n/**\n * Format inline markdown (bold, italic, inline code) within text\n */\nexport function formatInlineMarkdown(text) {\n let html = escapeHtml(text);\n // Inline code first (to protect from other transformations)\n html = html.replace(/`([^`]+)`/g, '<code class=\"inline-code\">$1</code>');\n // Bold text (**text**)\n html = html.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>');\n // Italic text (*text*)\n html = html.replace(/\\*([^*]+)\\*/g, '<em>$1</em>');\n return html;\n}\n/**\n * Parse markdown table to HTML\n */\nexport function parseMarkdownTable(tableText) {\n const lines = tableText.trim().split('\\n');\n if (lines.length < 2)\n return escapeHtml(tableText);\n // Check if it's a valid table (has header separator)\n const separatorIndex = lines.findIndex(line => /^\\|?\\s*[-:]+[-|\\s:]+\\s*\\|?$/.test(line));\n if (separatorIndex === -1 || separatorIndex === 0)\n return escapeHtml(tableText);\n const headerLines = lines.slice(0, separatorIndex);\n const separatorLine = lines[separatorIndex];\n const bodyLines = lines.slice(separatorIndex + 1);\n // Parse alignment from separator\n const alignments = [];\n const separatorCells = separatorLine.split('|').filter(cell => cell.trim());\n separatorCells.forEach(cell => {\n const trimmed = cell.trim();\n if (trimmed.startsWith(':') && trimmed.endsWith(':')) {\n alignments.push('center');\n }\n else if (trimmed.endsWith(':')) {\n alignments.push('right');\n }\n else {\n alignments.push('left');\n }\n });\n // Build HTML table with wrapper for horizontal scroll\n let html = '<div class=\"markdown-table-wrapper\"><table class=\"markdown-table\">';\n // Header\n html += '<thead>';\n headerLines.forEach(line => {\n html += '<tr>';\n const cells = line.split('|').filter((cell, idx, arr) => {\n // Filter out empty cells from leading/trailing |\n if (idx === 0 && cell.trim() === '')\n return false;\n if (idx === arr.length - 1 && cell.trim() === '')\n return false;\n return true;\n });\n cells.forEach((cell, idx) => {\n const align = alignments[idx] || 'left';\n html += `<th style=\"text-align: ${align};\">${formatInlineMarkdown(cell.trim())}</th>`;\n });\n html += '</tr>';\n });\n html += '</thead>';\n // Body\n if (bodyLines.length > 0) {\n html += '<tbody>';\n bodyLines.forEach(line => {\n if (!line.trim())\n return;\n html += '<tr>';\n const cells = line.split('|').filter((cell, idx, arr) => {\n if (idx === 0 && cell.trim() === '')\n return false;\n if (idx === arr.length - 1 && cell.trim() === '')\n return false;\n return true;\n });\n cells.forEach((cell, idx) => {\n const align = alignments[idx] || 'left';\n html += `<td style=\"text-align: ${align};\">${formatInlineMarkdown(cell.trim())}</td>`;\n });\n html += '</tr>';\n });\n html += '</tbody>';\n }\n html += '</table></div>';\n return html;\n}\n/**\n * Format markdown text to HTML with syntax highlighting\n */\nexport function formatMarkdownToHtml(text) {\n const nextItemsBlock = extractNextItemsBlock(text);\n // Decode HTML entities if present\n const textarea = document.createElement('textarea');\n textarea.innerHTML = nextItemsBlock ? nextItemsBlock.text : text;\n let html = textarea.value;\n // Step 0.5: Protect DataFrame HTML tables (must be before code blocks)\n const dataframeHtmlPlaceholders = [];\n html = html.replace(/<!--DFHTML-->([\\s\\S]*?)<!--\\/DFHTML-->/g, (match, tableHtml) => {\n const placeholder = '__DATAFRAME_HTML_' + Math.random().toString(36).substr(2, 9) + '__';\n dataframeHtmlPlaceholders.push({\n placeholder: placeholder,\n html: tableHtml\n });\n return placeholder;\n });\n // Step 1: Protect code blocks by replacing with placeholders\n const codeBlocks = [];\n const codeBlockPlaceholders = [];\n html = html.replace(/```(\\w+)?\\n([\\s\\S]*?)```/g, (match, language, code) => {\n const lang = (language || 'python').toLowerCase();\n const trimmedCode = normalizeIndentation(code.trim());\n // Use content-based hash for stable ID across re-renders\n const contentHash = simpleHash(trimmedCode + lang);\n const blockId = 'code-block-' + contentHash;\n const placeholder = '__CODE_BLOCK_' + blockId + '__';\n codeBlocks.push({\n id: blockId,\n code: trimmedCode,\n language: lang\n });\n // Create HTML for code block\n const isDiff = lang === 'diff';\n const highlightedCode = isDiff\n ? highlightDiff(trimmedCode)\n : lang === 'python' || lang === 'py'\n ? highlightPython(trimmedCode)\n : lang === 'javascript' || lang === 'js'\n ? highlightJavaScript(trimmedCode)\n : escapeHtml(trimmedCode);\n // For diff blocks, calculate and show line counts\n let diffLineCountHtml = '';\n if (isDiff) {\n const { additions, deletions } = countDiffLines(trimmedCode);\n diffLineCountHtml = '<span class=\"diff-line-counts\">' +\n '<span class=\"diff-additions\">+' + additions + '</span>' +\n '<span class=\"diff-deletions\">-' + deletions + '</span>' +\n '</span>';\n }\n const htmlBlock = '<div class=\"code-block-container' + (isDiff ? ' diff-block' : '') + '\" data-block-id=\"' + blockId + '\">' +\n '<div class=\"code-block-header\">' +\n '<span class=\"code-block-language\">' + escapeHtml(lang) + '</span>' +\n diffLineCountHtml +\n '<div class=\"code-block-actions\">' +\n (isDiff ? '' : '<button class=\"code-block-apply\" data-block-id=\"' + blockId + '\" title=\"셀에 적용\">셀에 적용</button>') +\n '<button class=\"code-block-copy\" data-block-id=\"' + blockId + '\" title=\"복사\">복사</button>' +\n '</div>' +\n '</div>' +\n '<pre class=\"code-block language-' + escapeHtml(lang) + '\"><code id=\"' + blockId + '\">' + highlightedCode + '</code></pre>' +\n '<button class=\"code-block-toggle\" data-block-id=\"' + blockId + '\" title=\"전체 보기\" aria-label=\"전체 보기\" aria-expanded=\"false\">' +\n '<span class=\"code-block-toggle-icon\" aria-hidden=\"true\">▾</span>' +\n '</button>' +\n '</div>';\n codeBlockPlaceholders.push({\n placeholder: placeholder,\n html: htmlBlock\n });\n return placeholder;\n });\n // Step 2: Protect inline code\n const inlineCodePlaceholders = [];\n html = html.replace(/`([^`]+)`/g, (match, code) => {\n const placeholder = '__INLINE_CODE_' + Math.random().toString(36).substr(2, 9) + '__';\n inlineCodePlaceholders.push({\n placeholder: placeholder,\n html: '<code class=\"inline-code\">' + escapeHtml(code) + '</code>'\n });\n return placeholder;\n });\n // Step 3: Parse and protect markdown tables\n const tablePlaceholders = [];\n // Improved table detection: look for lines with | separators and a separator row\n // Match pattern: header row(s), separator row (with ---), body rows\n // More flexible regex to handle various table formats\n const tableRegex = /(?:^|\\n)((?:\\|[^\\n]+\\|\\n?)+\\|[-:| ]+\\|(?:\\n\\|[^\\n]+\\|)*)/gm;\n html = html.replace(tableRegex, (match, tableBlock) => {\n const placeholder = '__TABLE_' + Math.random().toString(36).substr(2, 9) + '__';\n const tableHtml = parseMarkdownTable(tableBlock.trim());\n tablePlaceholders.push({\n placeholder: placeholder,\n html: tableHtml\n });\n return '\\n' + placeholder + '\\n';\n });\n // Alternative: tables without leading/trailing | (GFM style)\n // Pattern: \"header | header\\n---|---\\ndata | data\"\n const gfmTableRegex = /(?:^|\\n)((?:[^\\n|]+\\|[^\\n]+\\n)+[-:|\\s]+[-|]+\\n(?:[^\\n|]+\\|[^\\n]+\\n?)*)/gm;\n html = html.replace(gfmTableRegex, (match, tableBlock) => {\n // Skip if already processed (contains placeholder)\n if (tableBlock.includes('__TABLE_'))\n return match;\n const placeholder = '__TABLE_' + Math.random().toString(36).substr(2, 9) + '__';\n const tableHtml = parseMarkdownTable(tableBlock.trim());\n tablePlaceholders.push({\n placeholder: placeholder,\n html: tableHtml\n });\n return '\\n' + placeholder + '\\n';\n });\n // Third pattern: catch any remaining tables with | characters and --- separator\n const fallbackTableRegex = /(?:^|\\n)(\\|[^\\n]*\\|\\n\\|[-:| ]*\\|(?:\\n\\|[^\\n]*\\|)*)/gm;\n html = html.replace(fallbackTableRegex, (match, tableBlock) => {\n if (tableBlock.includes('__TABLE_'))\n return match;\n const placeholder = '__TABLE_' + Math.random().toString(36).substr(2, 9) + '__';\n const tableHtml = parseMarkdownTable(tableBlock.trim());\n tablePlaceholders.push({\n placeholder: placeholder,\n html: tableHtml\n });\n return '\\n' + placeholder + '\\n';\n });\n // Step 4: Escape HTML for non-placeholder text\n html = html.split(/(__(?:DATAFRAME_HTML|CODE_BLOCK|INLINE_CODE|TABLE|NEXT_ITEMS)_[a-z0-9-]+__)/gi)\n .map((part, index) => {\n // Odd indices are placeholders - keep as is\n if (index % 2 === 1)\n return part;\n // Even indices are regular text - escape HTML\n return escapeHtml(part);\n })\n .join('');\n // Step 5: Convert markdown to HTML\n // Headings (process from h6 to h1 to avoid conflicts)\n html = html.replace(/^###### (.*$)/gim, '<h6>$1</h6>');\n html = html.replace(/^##### (.*$)/gim, '<h5>$1</h5>');\n html = html.replace(/^#### (.*$)/gim, '<h4>$1</h4>');\n html = html.replace(/^### (.*$)/gim, '<h3>$1</h3>');\n html = html.replace(/^## (.*$)/gim, '<h2>$1</h2>');\n html = html.replace(/^# (.*$)/gim, '<h1>$1</h1>');\n // Horizontal rule (---)\n html = html.replace(/^---+$/gim, '<hr>');\n // Links [text](url)\n html = html.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href=\"$2\" target=\"_blank\" rel=\"noopener noreferrer\">$1</a>');\n // Lists - process BEFORE bold/italic to handle \"* item\" correctly\n // Unordered lists: - or * at start of line\n html = html.replace(/^[\\-\\*]\\s+(.*$)/gim, '<li>$1</li>');\n // Numbered lists: 1. 2. etc\n html = html.replace(/^\\d+\\.\\s+(.*$)/gim, '<li>$1</li>');\n // Bold text (must be before italic)\n html = html.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>');\n // Italic text - only match single * not at line start (to avoid conflict with lists)\n html = html.replace(/(?<!\\*)\\*([^*\\n]+)\\*(?!\\*)/g, '<em>$1</em>');\n // Line breaks\n html = html.replace(/\\n/g, '<br>');\n // Step 6: Restore table placeholders\n tablePlaceholders.forEach(item => {\n html = html.replace(item.placeholder, item.html);\n });\n // Step 6.5: Restore DataFrame HTML tables\n dataframeHtmlPlaceholders.forEach(item => {\n html = html.replace(item.placeholder, item.html);\n });\n // Step 7: Restore inline code placeholders\n inlineCodePlaceholders.forEach(item => {\n html = html.replace(item.placeholder, item.html);\n });\n // Step 8: Restore code block placeholders\n codeBlockPlaceholders.forEach(item => {\n html = html.replace(item.placeholder, item.html);\n });\n // Step 8.5: Restore next items list placeholders\n if (nextItemsBlock) {\n html = html.split(nextItemsBlock.placeholder).join(renderNextItemsList(nextItemsBlock.items));\n }\n return html;\n}\n","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M7.5 5.6 10 7 8.6 4.5 10 2 7.5 3.4 5 2l1.4 2.5L5 7zm12 9.8L17 14l1.4 2.5L17 19l2.5-1.4L22 19l-1.4-2.5L22 14zM22 2l-2.5 1.4L17 2l1.4 2.5L17 7l2.5-1.4L22 7l-1.4-2.5zm-7.63 5.29a.996.996 0 0 0-1.41 0L1.29 18.96c-.39.39-.39 1.02 0 1.41l2.34 2.34c.39.39 1.02.39 1.41 0L16.7 11.05c.39-.39.39-1.02 0-1.41zm-1.03 5.49-2.12-2.12 2.44-2.44 2.12 2.12z\"\n}), 'AutoFixHigh');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2m5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12z\"\n}), 'Cancel');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m-2 15-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8z\"\n}), 'CheckCircle');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"\n}), 'Close');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m1 15h-2v-2h2zm0-4h-2V7h2z\"\n}), 'Error');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"m12 8-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z\"\n}), 'ExpandLess');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M16.59 8.59 12 13.17 7.41 8.59 6 10l6 6 6-6z\"\n}), 'ExpandMore');"],"names":[],"ignoreList":[],"sourceRoot":""}