hdsp-jupyter-extension 2.0.2__py3-none-any.whl → 2.0.4__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 (47) hide show
  1. agent_server/langchain/agent.py +44 -37
  2. agent_server/routers/langchain_agent.py +44 -9
  3. hdsp_agent_core/models/common.py +8 -1
  4. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
  5. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +2 -2
  6. hdsp_jupyter_extension-2.0.2.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.634cf0ae0f3592d0882f.js → hdsp_jupyter_extension-2.0.4.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.4105453345c170c1d6e6.js +20 -2
  7. hdsp_jupyter_extension-2.0.4.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.4105453345c170c1d6e6.js.map +1 -0
  8. hdsp_jupyter_extension-2.0.2.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.1366019c413f1d68467f.js → hdsp_jupyter_extension-2.0.4.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.a223ea20056954479ae9.js +53 -5
  9. hdsp_jupyter_extension-2.0.4.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.a223ea20056954479ae9.js.map +1 -0
  10. hdsp_jupyter_extension-2.0.2.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.3379c4b222c042de2b01.js → hdsp_jupyter_extension-2.0.4.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.f9bcbb6529a2bf9261a2.js +3 -3
  11. hdsp_jupyter_extension-2.0.2.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.3379c4b222c042de2b01.js.map → hdsp_jupyter_extension-2.0.4.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.f9bcbb6529a2bf9261a2.js.map +1 -1
  12. {hdsp_jupyter_extension-2.0.2.dist-info → hdsp_jupyter_extension-2.0.4.dist-info}/METADATA +6 -1
  13. {hdsp_jupyter_extension-2.0.2.dist-info → hdsp_jupyter_extension-2.0.4.dist-info}/RECORD +43 -43
  14. jupyter_ext/_version.py +1 -1
  15. jupyter_ext/labextension/build_log.json +1 -1
  16. jupyter_ext/labextension/package.json +2 -2
  17. jupyter_ext/labextension/static/{frontend_styles_index_js.634cf0ae0f3592d0882f.js → frontend_styles_index_js.4105453345c170c1d6e6.js} +20 -2
  18. jupyter_ext/labextension/static/frontend_styles_index_js.4105453345c170c1d6e6.js.map +1 -0
  19. jupyter_ext/labextension/static/{lib_index_js.1366019c413f1d68467f.js → lib_index_js.a223ea20056954479ae9.js} +53 -5
  20. jupyter_ext/labextension/static/lib_index_js.a223ea20056954479ae9.js.map +1 -0
  21. jupyter_ext/labextension/static/{remoteEntry.3379c4b222c042de2b01.js → remoteEntry.f9bcbb6529a2bf9261a2.js} +3 -3
  22. jupyter_ext/labextension/static/{remoteEntry.3379c4b222c042de2b01.js.map → remoteEntry.f9bcbb6529a2bf9261a2.js.map} +1 -1
  23. hdsp_jupyter_extension-2.0.2.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.634cf0ae0f3592d0882f.js.map +0 -1
  24. hdsp_jupyter_extension-2.0.2.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.1366019c413f1d68467f.js.map +0 -1
  25. jupyter_ext/labextension/static/frontend_styles_index_js.634cf0ae0f3592d0882f.js.map +0 -1
  26. jupyter_ext/labextension/static/lib_index_js.1366019c413f1d68467f.js.map +0 -1
  27. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
  28. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
  29. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.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
  30. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.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
  31. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.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
  32. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.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
  33. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
  34. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.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
  35. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.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
  36. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +0 -0
  37. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +0 -0
  38. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +0 -0
  39. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +0 -0
  40. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +0 -0
  41. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.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
  42. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
  43. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +0 -0
  44. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +0 -0
  45. {hdsp_jupyter_extension-2.0.2.data → hdsp_jupyter_extension-2.0.4.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +0 -0
  46. {hdsp_jupyter_extension-2.0.2.dist-info → hdsp_jupyter_extension-2.0.4.dist-info}/WHEEL +0 -0
  47. {hdsp_jupyter_extension-2.0.2.dist-info → hdsp_jupyter_extension-2.0.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib_index_js.a223ea20056954479ae9.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,kCAAkC,6CAAM;AACxC,+BAA+B,6CAAM;AACrC,gCAAgC,6CAAM;AACtC,mCAAmC,6CAAM;AACzC;AACA;AACA;AACA;AACA,yCAAyC,+DAAa;AACtD;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,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,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,4BAA4B,yCAAyC;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;AACA;AACA;AACA;AACA;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,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;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,wBAAwB;AACxB;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,SAAS;AACT;AACA;AACA,gBAAgB,WAAW;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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,4BAA4B,qEAAY,MAAM,4EAAmB;AACjE;AACA;AACA;AACA,0BAA0B,uEAAc;AACxC;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,aAAa;AACb;AACA;AACA;AACA;AACA,wBAAwB;AACxB;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA,aAAa;AACb;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;AACA;AACA;AACA,6BAA6B,qBAAqB;AAClD;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,4BAA4B,0DAAmB,UAAU;AACzD;AACA,mEAAmE,SAAS,GAAG;AAC/E,yCAAyC,0DAAmB,UAAU,sCAAsC;AAC5G,4BAA4B,0DAAmB,WAAW,oCAAoC;AAC9F,4BAA4B,0DAAmB,WAAW,oCAAoC;AAC9F,wBAAwB,0DAAmB,UAAU,sCAAsC,kDAAkD,GAAG,sDAAsD,0DAAmB,UAAU,wCAAwC;AAC3Q,4BAA4B,0DAAmB,UAAU,6CAA6C;AACtG,4BAA4B,0DAAmB,UAAU,wCAAwC;AACjG,gCAAgC,0DAAmB,UAAU,6CAA6C;AAC1G;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,0DAAmB,UAAU,6CAA6C,kBAAkB,6BAA6B,QAAQ,6EAAoB,oBAAoB,QAAQ,EAAE,OAAO,gCAAgC,WAAW,UAAU;AAC3R;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,6CAA6C,0DAAmB,UAAU,qDAAqD,oEAAoE,GAAG;AACtM,YAAY,0DAAmB,UAAU,qCAAqC;AAC9E,gBAAgB,0DAAmB,UAAU,2DAA2D;AACxG,gBAAgB,0DAAmB,WAAW,kCAAkC;AAChF,kDAAkD,0DAAmB,WAAW,6DAA6D;AAC7I,6BAA6B,0DAAmB,UAAU,oCAAoC;AAC9F,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,8CAA8C,0DAAmB,CAAC,uDAAc;AAChF,4BAA4B,0DAAmB,UAAU,4CAA4C;AACrG,4BAA4B,0DAAmB,WAAW,4CAA4C,4BAA4B,0DAAmB,WAAW,4CAA4C;AAC5M,qBAAqB;AACrB,gBAAgB,0DAAmB,WAAW,6CAA6C;AAC3F;AACA;AACA;AACA,2BAA2B,0DAAmB,UAAU,oFAAoF,oEAAoE,GAAG;AACnN,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,QAAQ,0DAAmB,UAAU,uCAAuC;AAC5E,YAAY,0DAAmB,UAAU,qCAAqC;AAC9E,gBAAgB,0DAAmB,eAAe,6BAA6B,0DAA0D;AACzI;AACA,2FAA2F;AAC3F,gBAAgB,0DAAmB,aAAa,qKAAqK;AACrN,YAAY,0DAAmB,UAAU,gCAAgC;AACzE,gBAAgB,0DAAmB,UAAU,6CAA6C;AAC1F,oBAAoB,0DAAmB,aAAa,mCAAmC,4DAA4D,kCAAkC,0CAA0C,YAAY;AAC3O,wBAAwB,0DAAmB,UAAU,wGAAwG;AAC7J;AACA,wBAAwB,0DAAmB,WAAW,siBAAsiB;AAC5lB;AACA,wBAAwB,0DAAmB,WAAW,kNAAkN;AACxQ,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,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;;;;;;;;;;;;;;;;;AC17EA;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;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,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;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,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;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;;;;;;;;;;;;;;;;;;;;;;;;;AC3PA;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;AAC1E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,kEAAa;AACjB,IAAI,2EAAiB;AACrB,IAAI,qFAAsB;AAC1B,IAAI,mFAAqB;AACzB;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;ACrBvB;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;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;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;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;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;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;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;;;;;;;;;;;;;;;;;;AC5UA;AACA;AACA;AACyJ;AACzJ;AAC2D;AACpD;AACP;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;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,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,4EAA4E,cAAc;AAC1F,cAAc;AACd;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;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;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,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;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,iCAAiC;AACjC;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,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;;;;;;;;;;;;;;;;;ACxqBA;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;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,uCAAuC,WAAW,GAAG,wCAAwC;AAC7F;AACA;AACA;AACA;AACA,qDAAqD,eAAe;AACpE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4DAA4D,2DAA2D;AACvH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB;AAChB,oBAAoB;;AAEpB;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,sDAAsD,YAAY;AAChF;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4FAA4F,kBAAkB;AAC9G;AACA;AACA;AACA,qBAAqB;AACrB;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;;;;;;;;;;;;;;;;;;;;;AC78E5B;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;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;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,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;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;;;;;;;;;;;;;;;;;ACjmBA;;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/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 const interruptMessageIdRef = useRef(null);\n const approvalPendingRef = useRef(false);\n const pendingToolCallsRef = useRef([]);\n const handledToolCallKeysRef = useRef(new Set());\n const getActiveNotebookPanel = () => {\n const app = window.jupyterapp;\n if (app?.shell?.currentWidget) {\n const currentWidget = app.shell.currentWidget;\n if (currentWidget instanceof NotebookPanel) {\n return currentWidget;\n }\n if ('content' in currentWidget && currentWidget.content?.model) {\n return currentWidget;\n }\n }\n return notebookTracker?.currentWidget || null;\n };\n const insertCell = (notebook, cellType, source) => {\n const model = notebook.content?.model;\n if (!model?.sharedModel) {\n console.warn('[AgentPanel] Notebook model not ready for insert');\n return null;\n }\n const insertIndex = model.cells.length;\n model.sharedModel.insertCell(insertIndex, {\n cell_type: cellType,\n source\n });\n notebook.content.activeCellIndex = insertIndex;\n return insertIndex;\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 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 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, using regular chat');\n return;\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;\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 }\n catch (error) {\n console.error('[AgentPanel] File fix API error:', error);\n addErrorMessage('파일 수정 요청 실패: ' + error.message);\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 // 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 // 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 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 upsertInterruptMessage = (interrupt) => {\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: { interrupt }\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 = () => {\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 }\n }\n };\n }\n return msg;\n }));\n };\n const resumeFromInterrupt = async (interrupt, decision) => {\n const { threadId } = interrupt;\n setInterruptData(null);\n setDebugStatus(null);\n clearInterruptMessage();\n let resumeDecision = decision;\n let resumeArgs = undefined;\n if (decision === 'approve') {\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 else {\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 try {\n await apiService.resumeAgent(threadId, resumeDecision, resumeArgs, resumeDecision === 'reject' ? 'User rejected this action' : 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 if (nextInterrupt.action === 'jupyter_cell_tool' && nextInterrupt.args?.code) {\n if (isAutoApprovedCode(nextInterrupt.args.code)) {\n queueApprovalCell(nextInterrupt.args.code);\n void resumeFromInterrupt(nextInterrupt, 'approve');\n return;\n }\n queueApprovalCell(nextInterrupt.args.code);\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 // 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 // Python 에러가 감지되면 파일 수정 모드로 전환\n if (detectPythonError(currentInput)) {\n console.log('[AgentPanel] Agent mode: No notebook, but Python error detected - switching to file fix mode');\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 // Python 에러 수정 처리\n await handlePythonErrorFix(currentInput);\n return;\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 // Python 에러 감지 및 파일 수정 모드 (Chat 모드에서만)\n if (inputMode === 'chat' && detectPythonError(currentInput)) {\n console.log('[AgentPanel] Python error detected in message');\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 // Python 에러 수정 처리\n await handlePythonErrorFix(currentInput);\n return;\n }\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 if (!hasApiKey) {\n // Show error message and open settings\n const providerName = llmConfig?.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 // Use the display prompt (input) for the user message, or use a fallback if input is empty\n const displayContent = currentInput || (llmPrompt ? '셀 분석 요청' : '');\n const userMessage = {\n id: makeMessageId(),\n role: 'user',\n content: displayContent,\n timestamp: Date.now()\n };\n setMessages(prev => [...prev, userMessage]);\n // Only clear input if it was manually entered, keep it for auto-execution display\n if (currentInput) {\n setInput('');\n }\n setIsLoading(true);\n setIsStreaming(true);\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 let interrupted = false;\n try {\n // Use LLM prompt if available, otherwise use the display content\n const messageToSend = llmPrompt || displayContent;\n await apiService.sendMessageStream({\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 interrupted = true;\n approvalPendingRef.current = true;\n if (interrupt.action === 'jupyter_cell_tool' && interrupt.args?.code) {\n if (isAutoApprovedCode(interrupt.args.code)) {\n queueApprovalCell(interrupt.args.code);\n void resumeFromInterrupt(interrupt, 'approve');\n return;\n }\n queueApprovalCell(interrupt.args.code);\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 }\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 // Handle resume after user approval/rejection\n const handleResumeAgent = async (decision) => {\n if (!interruptData)\n return;\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)\n if (e.key === 'Tab' && e.shiftKey) {\n e.preventDefault();\n setInputMode(prev => prev === 'chat' ? 'agent' : 'chat');\n return;\n }\n // Cmd/Ctrl + . : 모드 전환 (대체 단축키)\n if (e.key === '.' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n setInputMode(prev => prev === 'chat' ? 'agent' : 'chat');\n }\n // Tab (without Shift): Agent 모드일 때 드롭다운 토글\n if (e.key === 'Tab' && !e.shiftKey && inputMode === 'agent') {\n e.preventDefault();\n setShowModeDropdown(prev => !prev);\n }\n };\n // 모드 토글 함수\n const toggleMode = () => {\n setInputMode(prev => prev === 'chat' ? 'agent' : '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 if (normalized.includes('tool')) {\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 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 : '메시지를 입력하거나 아래 버튼으로 Agent 모드를 선택하세요.'))) : (messages.map(msg => {\n if (isChatMessage(msg)) {\n // 일반 Chat 메시지\n const isAssistant = msg.role === 'assistant';\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}` },\n !isAssistant && (React.createElement(\"div\", { className: \"jp-agent-message-header\" },\n React.createElement(\"span\", { className: \"jp-agent-message-role\" }, msg.role === 'user' ? '사용자' : msg.role === 'system' ? '승인 요청' : 'Agent'),\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' : ''}` }, msg.role === 'system' && msg.metadata?.interrupt ? (React.createElement(\"div\", { className: \"jp-agent-interrupt-inline\" },\n 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 code = msg.metadata?.interrupt?.args?.code || msg.metadata?.interrupt?.args?.content || '';\n const lines = code.split('\\n');\n const preview = lines.slice(0, 8).join('\\n');\n const suffix = lines.length > 8 ? '\\n...' : '';\n const resolved = msg.metadata?.interrupt?.resolved;\n const actionHtml = resolved\n ? '<div class=\"jp-agent-interrupt-actions jp-agent-interrupt-actions--resolved\">승인됨</div>'\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 return (React.createElement(\"div\", { className: \"jp-RenderedHTMLCommon\", style: { padding: '0 4px' }, dangerouslySetInnerHTML: { __html: formatMarkdownToHtml(`\\n\\`\\`\\`python\\n${preview}${suffix}\\n\\`\\`\\``).replace('</div>', `${actionHtml}</div>`) }, 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 statusText && todos.length === 0 && (React.createElement(\"div\", { className: `jp-agent-message jp-agent-message-debug${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 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 return currentTodo ? (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))) : (React.createElement(\"span\", { className: \"jp-agent-todo-compact-current\" }, \"\\u2713 \\uBAA8\\uB4E0 \\uC791\\uC5C5 \\uC644\\uB8CC\"));\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 && (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 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 === 'agent' ? 'jp-agent-input--agent-mode' : ''}`, value: input, onChange: (e) => setInput(e.target.value), onKeyDown: handleKeyDown, placeholder: inputMode === 'agent'\n ? '노트북 작업을 입력하세요... (예: 데이터 시각화 해줘)'\n : '메시지를 입력하세요...', rows: 3, disabled: isLoading || isAgentRunning }),\n React.createElement(\"button\", { className: \"jp-agent-send-button\", onClick: handleSendMessage, disabled: !input.trim() || isLoading || isStreaming || isAgentRunning, title: \"\\uC804\\uC1A1 (Enter)\" }, isAgentRunning ? '실행 중...' : '전송')),\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 === 'agent' ? 'jp-agent-mode-toggle--active' : ''}`, onClick: toggleMode, title: `${inputMode === 'agent' ? 'Agent' : 'Chat'} 모드 (⇧Tab)` },\n React.createElement(\"svg\", { className: \"jp-agent-mode-icon\", viewBox: \"0 0 16 16\", fill: \"currentColor\", width: \"14\", height: \"14\" }, 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 // 채팅 아이콘 (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\" }))),\n React.createElement(\"span\", { className: \"jp-agent-mode-label\" }, inputMode === 'agent' ? 'Agent' : 'Chat'),\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(\"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 // 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 }\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 systemPrompt: systemPrompt && systemPrompt.trim() ? systemPrompt : undefined\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 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\" },\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';\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];\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 * 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.\n\n## ⚠️ CRITICAL RULE: NEVER produce an empty response\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. If ALL todos are completed → call final_answer_tool with a summary\n\nNEVER end your turn without calling a tool. NEVER produce an empty response.\n\n## Available Tools\n1. **jupyter_cell_tool**: Execute Python code in a new notebook cell\n2. **markdown_tool**: Add a markdown explanation cell\n3. **final_answer_tool**: Complete the task with a summary - REQUIRED when done\n4. **read_file_tool**: Read file contents\n5. **write_file_tool**: Write file contents\n6. **list_files_tool**: List directory contents\n7. **search_workspace_tool**: Search for patterns in workspace files\n8. **search_notebook_cells_tool**: Search for patterns in notebook cells\n9. **write_todos**: Create and update task list for complex multi-step tasks\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. If \\`!pip install\\` fails, use \\`!pip3 install\\` instead\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// ═══════════════════════════════════════════════════════════════════════════\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 }\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 };\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 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 // ═══════════════════════════════════════════════════════════════════════════\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 async sendMessageStream(request, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall) {\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.sendMessageStreamInternal(requestToSend, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall);\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 * Internal streaming implementation (without retry logic)\n * Uses LangChain agent endpoint for improved middleware support\n */\n async sendMessageStreamInternal(request, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall) {\n // Convert IChatRequest to LangChain AgentRequest format\n // Frontend's context has limited fields, map what's available\n const langchainRequest = {\n request: request.message,\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: request.llmConfig,\n workspaceRoot: '.'\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: JSON.stringify(langchainRequest)\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 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 });\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 resumeRequest = {\n threadId,\n decisions: [{\n type: decision,\n args,\n feedback\n }],\n llmConfig,\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 });\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 /**\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 도구 등록 (조건부 승인)\n const executeCommandDef = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'execute_command');\n if (executeCommandDef && !this.registry.hasTool('execute_command')) {\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 /**\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 도구: 셸 명령 실행 (조건부 승인)\n */\n async executeCommand(params, context) {\n console.log('[ToolExecutor] executeCommand:', params.command);\n const timeout = params.timeout || 30000;\n // 위험 명령 검사 및 조건부 승인 요청\n if (this.isDangerousCommand(params.command)) {\n console.log('[ToolExecutor] Dangerous command detected, requesting approval');\n // 승인 요청\n const request = {\n id: `execute_command-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n toolName: 'execute_command',\n toolDefinition: this.registry.getTool('execute_command'),\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 // Python subprocess로 명령 실행\n const pythonCode = `\nimport json\nimport subprocess\nimport sys\ntry:\n command = ${JSON.stringify(params.command)}\n timeout_sec = ${timeout / 1000}\n\n result = subprocess.run(\n command,\n shell=True,\n capture_output=True,\n text=True,\n timeout=timeout_sec\n )\n\n output = {\n 'success': result.returncode == 0,\n 'stdout': result.stdout,\n 'stderr': result.stderr,\n 'returncode': result.returncode\n }\nexcept subprocess.TimeoutExpired:\n output = {'success': False, 'error': f'Command timed out after {timeout_sec}s'}\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: parsed.stdout || '(no output)',\n };\n }\n else {\n return {\n success: false,\n error: parsed.error || parsed.stderr || `Command failed with code ${parsed.returncode}`,\n };\n }\n }\n return { success: false, error: execResult.error?.evalue || 'Command execution failed' };\n }\n catch (error) {\n return { success: false, error: error.message };\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',\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에서 이 패턴에 매칭되는 명령은 사용자 승인 필요\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}\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 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 // Decode HTML entities if present\n const textarea = document.createElement('textarea');\n textarea.innerHTML = 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 highlightedCode = lang === 'python' || lang === 'py'\n ? highlightPython(trimmedCode)\n : lang === 'javascript' || lang === 'js'\n ? highlightJavaScript(trimmedCode)\n : escapeHtml(trimmedCode);\n const htmlBlock = '<div class=\"code-block-container\" data-block-id=\"' + blockId + '\">' +\n '<div class=\"code-block-header\">' +\n '<span class=\"code-block-language\">' + escapeHtml(lang) + '</span>' +\n '<div class=\"code-block-actions\">' +\n '<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 '</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)_[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 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":""}