codebuff-agent 2.1.74__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 (217) hide show
  1. codebuff_agent-2.1.74.data/data/etc/jupyter/jupyter_server_config.d/codebuff_agent.json +7 -0
  2. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/package.json +157 -0
  3. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/104.2b8f20f9d1ac2c9db01b.js +1 -0
  4. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/150.39fa98117cc8ca590465.js +1 -0
  5. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/166.386ff9b2c37ac865fdee.js +1 -0
  6. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/216.25cf81131bf7274dabf4.js +1 -0
  7. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/353.581bd1cb00746399b62e.js +1 -0
  8. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/367.575f9621b63c9a641aa9.js +1 -0
  9. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/421.11dac223a2c2da1b09f5.js +1 -0
  10. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/432.8513b4c6cc1d1232d165.js +2 -0
  11. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/432.8513b4c6cc1d1232d165.js.LICENSE.txt +19 -0
  12. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/439.ba5b5ef70ec6de214b8c.js +2 -0
  13. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/439.ba5b5ef70ec6de214b8c.js.LICENSE.txt +8 -0
  14. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/481.cc2dec9fa869d66f1407.js +1 -0
  15. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/487.0796e931d6e651482c14.js +1 -0
  16. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/49.d326f554b6f9ccde6580.js +1 -0
  17. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/541.26897ea32845659e36be.js +1 -0
  18. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/579.e569d3edd8391fd8fbb3.js +1 -0
  19. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/58.eb7cc679f7535cc8a2f1.js +2 -0
  20. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/58.eb7cc679f7535cc8a2f1.js.LICENSE.txt +8 -0
  21. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/666.d9bfdb23f98489d0a188.js +1 -0
  22. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/677.d266c39dbed33bc388b3.js +2 -0
  23. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/677.d266c39dbed33bc388b3.js.LICENSE.txt +8 -0
  24. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/68.26b6f59c8454adb17f20.js +204 -0
  25. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/694.4ac443814260057e68f7.js +1 -0
  26. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/791.61affb8d3480d3d60e0d.js +1 -0
  27. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/848.4d2db09cfbb09374b299.js +2 -0
  28. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/848.4d2db09cfbb09374b299.js.LICENSE.txt +9 -0
  29. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/868.5cfe7e1a348ec5af1b8c.js +1 -0
  30. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/97.ea559a5b8a9a09f17c6b.js +1 -0
  31. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/remoteEntry.5292fc9c6df7e7920cc8.js +1 -0
  32. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/style.js +4 -0
  33. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/third-party-licenses.json +724 -0
  34. codebuff_agent-2.1.74.dist-info/METADATA +142 -0
  35. codebuff_agent-2.1.74.dist-info/RECORD +217 -0
  36. codebuff_agent-2.1.74.dist-info/WHEEL +4 -0
  37. codebuff_agent-2.1.74.dist-info/licenses/LICENSE +21 -0
  38. codebuff_core/__init__.py +153 -0
  39. codebuff_core/codebuff_config_defaults.json +64 -0
  40. codebuff_core/core/__init__.py +35 -0
  41. codebuff_core/core/code_validator.py +1238 -0
  42. codebuff_core/core/error_classifier.py +579 -0
  43. codebuff_core/core/exceptions.py +49 -0
  44. codebuff_core/data/tiktoken_cache/9b5ad71b2ce5302211f9c61530b329a4922fc6a4 +100256 -0
  45. codebuff_core/directory_policy.py +32 -0
  46. codebuff_core/factory.py +155 -0
  47. codebuff_core/interfaces.py +197 -0
  48. codebuff_core/knowledge/__init__.py +31 -0
  49. codebuff_core/knowledge/chunking.py +375 -0
  50. codebuff_core/knowledge/loader.py +339 -0
  51. codebuff_core/knowledge/watchdog_service.py +354 -0
  52. codebuff_core/legacy_paths.py +69 -0
  53. codebuff_core/llm/__init__.py +13 -0
  54. codebuff_core/llm/service.py +407 -0
  55. codebuff_core/managers/__init__.py +22 -0
  56. codebuff_core/managers/config_manager.py +690 -0
  57. codebuff_core/managers/remote_config.py +673 -0
  58. codebuff_core/managers/remote_skills.py +539 -0
  59. codebuff_core/managers/session_manager.py +255 -0
  60. codebuff_core/middleware/__init__.py +3 -0
  61. codebuff_core/middleware/error_recovery.py +394 -0
  62. codebuff_core/middleware/logging_utils.py +33 -0
  63. codebuff_core/models/__init__.py +111 -0
  64. codebuff_core/models/agent.py +306 -0
  65. codebuff_core/models/chat.py +57 -0
  66. codebuff_core/models/common.py +82 -0
  67. codebuff_core/models/rag.py +300 -0
  68. codebuff_core/platform_rules.py +127 -0
  69. codebuff_core/prompts/__init__.py +44 -0
  70. codebuff_core/prompts/auto_agent_prompts.py +1278 -0
  71. codebuff_core/services/__init__.py +30 -0
  72. codebuff_core/services/agent_service.py +345 -0
  73. codebuff_core/services/analytics_aggregate.py +837 -0
  74. codebuff_core/services/chat_service.py +898 -0
  75. codebuff_core/services/context_condenser.py +306 -0
  76. codebuff_core/services/pii_guardrail.py +393 -0
  77. codebuff_core/services/rag_chat_agent.py +2993 -0
  78. codebuff_core/services/rag_collections.py +2740 -0
  79. codebuff_core/services/rag_manager.py +890 -0
  80. codebuff_core/services/rag_service.py +145 -0
  81. codebuff_core/services/retriever.py +260 -0
  82. codebuff_core/services/vllm_embedding_service.py +290 -0
  83. codebuff_core/tests/__init__.py +1 -0
  84. codebuff_core/tests/conftest.py +81 -0
  85. codebuff_core/tests/test_error_recovery.py +647 -0
  86. codebuff_core/tests/test_factory.py +135 -0
  87. codebuff_core/tests/test_services.py +150 -0
  88. jupyter_ext/__init__.py +266 -0
  89. jupyter_ext/_version.py +4 -0
  90. jupyter_ext/agent/__init__.py +49 -0
  91. jupyter_ext/agent/agent_factory.py +2270 -0
  92. jupyter_ext/agent/ast_tools.py +451 -0
  93. jupyter_ext/agent/autofix_middleware.py +481 -0
  94. jupyter_ext/agent/background_procs.py +372 -0
  95. jupyter_ext/agent/code_history_middleware.py +211 -0
  96. jupyter_ext/agent/codex_autofix.py +160 -0
  97. jupyter_ext/agent/codex_bridge.py +3339 -0
  98. jupyter_ext/agent/codex_hooks.py +320 -0
  99. jupyter_ext/agent/codex_slash.py +685 -0
  100. jupyter_ext/agent/codex_threads.py +114 -0
  101. jupyter_ext/agent/content_injection.py +377 -0
  102. jupyter_ext/agent/continuation_middleware.py +548 -0
  103. jupyter_ext/agent/custom_skills.py +335 -0
  104. jupyter_ext/agent/file_diffs.py +99 -0
  105. jupyter_ext/agent/git_history.py +188 -0
  106. jupyter_ext/agent/guardrail_middleware.py +421 -0
  107. jupyter_ext/agent/json_repair_utils.py +54 -0
  108. jupyter_ext/agent/langchain_tools.py +2673 -0
  109. jupyter_ext/agent/mermaid_fixer.py +289 -0
  110. jupyter_ext/agent/mermaid_middleware.py +135 -0
  111. jupyter_ext/agent/middleware.py +359 -0
  112. jupyter_ext/agent/middleware_prompts.py +53 -0
  113. jupyter_ext/agent/observability.py +334 -0
  114. jupyter_ext/agent/patch_tool_calls.py +57 -0
  115. jupyter_ext/agent/prompts.py +112 -0
  116. jupyter_ext/agent/skill_middleware.py +257 -0
  117. jupyter_ext/agent/skills/airflow_dag_development.md +36 -0
  118. jupyter_ext/agent/skills/anomaly_detection.md +12 -0
  119. jupyter_ext/agent/skills/athena_sql.md +156 -0
  120. jupyter_ext/agent/skills/bdp_table_search.md +33 -0
  121. jupyter_ext/agent/skills/bigquery.md +65 -0
  122. jupyter_ext/agent/skills/bigquery_oracle.md +231 -0
  123. jupyter_ext/agent/skills/bigquery_tuning.md +307 -0
  124. jupyter_ext/agent/skills/classification_evaluation.md +11 -0
  125. jupyter_ext/agent/skills/clustering_analysis.md +12 -0
  126. jupyter_ext/agent/skills/comprehensive_eda.md +11 -0
  127. jupyter_ext/agent/skills/data_analysis.md +236 -0
  128. jupyter_ext/agent/skills/data_cleaning_pipeline.md +12 -0
  129. jupyter_ext/agent/skills/data_loading.md +158 -0
  130. jupyter_ext/agent/skills/data_quality_report.md +13 -0
  131. jupyter_ext/agent/skills/dimensionality_reduction.md +12 -0
  132. jupyter_ext/agent/skills/emr_script_development.md +17 -0
  133. jupyter_ext/agent/skills/experiment_tracking.md +13 -0
  134. jupyter_ext/agent/skills/feature_importance_analysis.md +10 -0
  135. jupyter_ext/agent/skills/hyperparameter_tuning.md +12 -0
  136. jupyter_ext/agent/skills/hypothesis_testing.md +12 -0
  137. jupyter_ext/agent/skills/imbalanced_data_handling.md +12 -0
  138. jupyter_ext/agent/skills/inference.md +392 -0
  139. jupyter_ext/agent/skills/lambda_function_development.md +19 -0
  140. jupyter_ext/agent/skills/model_comparison.md +11 -0
  141. jupyter_ext/agent/skills/model_explainability.md +12 -0
  142. jupyter_ext/agent/skills/model_training.md +318 -0
  143. jupyter_ext/agent/skills/pyspark.md +352 -0
  144. jupyter_ext/agent/skills/python_code_quality.md +35 -0
  145. jupyter_ext/agent/skills/regression_evaluation.md +12 -0
  146. jupyter_ext/agent/skills/spring_java_development.md +35 -0
  147. jupyter_ext/agent/skills/text_preprocessing.md +12 -0
  148. jupyter_ext/agent/skills/time_series_analysis.md +13 -0
  149. jupyter_ext/agent/skills/unit_testing.md +203 -0
  150. jupyter_ext/agent/todo_prompts.py +57 -0
  151. jupyter_ext/agent/tool_report.py +45 -0
  152. jupyter_ext/analytics_ingest.py +599 -0
  153. jupyter_ext/etc/jupyter/jupyter_server_config.d/codebuff_agent.json +7 -0
  154. jupyter_ext/handlers/__init__.py +8 -0
  155. jupyter_ext/handlers/analytics_handler.py +170 -0
  156. jupyter_ext/handlers/benchmark_handler.py +794 -0
  157. jupyter_ext/handlers/eda_benchmark_handler.py +1328 -0
  158. jupyter_ext/handlers/rag_admin_handler.py +689 -0
  159. jupyter_ext/handlers/websocket.py +497 -0
  160. jupyter_ext/http_handlers.py +8744 -0
  161. jupyter_ext/idle_monitor.py +346 -0
  162. jupyter_ext/labextension/package.json +157 -0
  163. jupyter_ext/labextension/static/104.2b8f20f9d1ac2c9db01b.js +1 -0
  164. jupyter_ext/labextension/static/150.39fa98117cc8ca590465.js +1 -0
  165. jupyter_ext/labextension/static/166.386ff9b2c37ac865fdee.js +1 -0
  166. jupyter_ext/labextension/static/216.25cf81131bf7274dabf4.js +1 -0
  167. jupyter_ext/labextension/static/353.581bd1cb00746399b62e.js +1 -0
  168. jupyter_ext/labextension/static/367.575f9621b63c9a641aa9.js +1 -0
  169. jupyter_ext/labextension/static/421.11dac223a2c2da1b09f5.js +1 -0
  170. jupyter_ext/labextension/static/432.8513b4c6cc1d1232d165.js +2 -0
  171. jupyter_ext/labextension/static/432.8513b4c6cc1d1232d165.js.LICENSE.txt +19 -0
  172. jupyter_ext/labextension/static/439.ba5b5ef70ec6de214b8c.js +2 -0
  173. jupyter_ext/labextension/static/439.ba5b5ef70ec6de214b8c.js.LICENSE.txt +8 -0
  174. jupyter_ext/labextension/static/481.cc2dec9fa869d66f1407.js +1 -0
  175. jupyter_ext/labextension/static/487.0796e931d6e651482c14.js +1 -0
  176. jupyter_ext/labextension/static/49.d326f554b6f9ccde6580.js +1 -0
  177. jupyter_ext/labextension/static/541.26897ea32845659e36be.js +1 -0
  178. jupyter_ext/labextension/static/579.e569d3edd8391fd8fbb3.js +1 -0
  179. jupyter_ext/labextension/static/58.eb7cc679f7535cc8a2f1.js +2 -0
  180. jupyter_ext/labextension/static/58.eb7cc679f7535cc8a2f1.js.LICENSE.txt +8 -0
  181. jupyter_ext/labextension/static/666.d9bfdb23f98489d0a188.js +1 -0
  182. jupyter_ext/labextension/static/677.d266c39dbed33bc388b3.js +2 -0
  183. jupyter_ext/labextension/static/677.d266c39dbed33bc388b3.js.LICENSE.txt +8 -0
  184. jupyter_ext/labextension/static/68.26b6f59c8454adb17f20.js +204 -0
  185. jupyter_ext/labextension/static/694.4ac443814260057e68f7.js +1 -0
  186. jupyter_ext/labextension/static/791.61affb8d3480d3d60e0d.js +1 -0
  187. jupyter_ext/labextension/static/848.4d2db09cfbb09374b299.js +2 -0
  188. jupyter_ext/labextension/static/848.4d2db09cfbb09374b299.js.LICENSE.txt +9 -0
  189. jupyter_ext/labextension/static/868.5cfe7e1a348ec5af1b8c.js +1 -0
  190. jupyter_ext/labextension/static/97.ea559a5b8a9a09f17c6b.js +1 -0
  191. jupyter_ext/labextension/static/remoteEntry.5292fc9c6df7e7920cc8.js +1 -0
  192. jupyter_ext/labextension/static/style.js +4 -0
  193. jupyter_ext/labextension/static/third-party-licenses.json +724 -0
  194. jupyter_ext/log_paths.py +113 -0
  195. jupyter_ext/resource_usage.py +519 -0
  196. jupyter_ext/secrets_bootstrap.py +116 -0
  197. jupyter_ext/team_access.py +105 -0
  198. jupyter_ext/tests/__init__.py +1 -0
  199. jupyter_ext/tests/test_context.py +234 -0
  200. jupyter_ext/tests/test_factory.py +563 -0
  201. jupyter_ext/tests/test_file_finder.py +139 -0
  202. jupyter_ext/tests/test_handlers.py +58 -0
  203. jupyter_ext/tests/test_mcp_selftest.py +204 -0
  204. jupyter_ext/tests/test_mermaid_fixer.py +272 -0
  205. jupyter_ext/tests/test_resource_tiers.py +298 -0
  206. jupyter_ext/tools/__init__.py +15 -0
  207. jupyter_ext/tools/base.py +90 -0
  208. jupyter_ext/tools/context.py +268 -0
  209. jupyter_ext/tools/dir_complete.py +86 -0
  210. jupyter_ext/tools/file_finder.py +303 -0
  211. jupyter_ext/tools/file_tools.py +244 -0
  212. jupyter_ext/tools/jupyter_tools.py +296 -0
  213. jupyter_ext/tools/registry.py +159 -0
  214. jupyter_ext/tools/search_tools.py +230 -0
  215. jupyter_ext/tools/security_tools.py +657 -0
  216. jupyter_ext/tools/shell_tools.py +109 -0
  217. jupyter_ext/tools/user_tools.py +132 -0
@@ -0,0 +1,346 @@
1
+ """
2
+ Server-side Idle Monitor.
3
+
4
+ 브라우저가 닫혀도 커널/터미널 활동을 감지하여 idle timeout을 서버에서 처리.
5
+ Jupyter Server의 커널/터미널 매니저를 통해 마지막 활동 시간을 추적한다.
6
+ """
7
+
8
+ import asyncio
9
+ import json
10
+ import logging
11
+ import os
12
+ import time
13
+ from typing import Optional
14
+
15
+ logger = logging.getLogger("ServerApp")
16
+
17
+ # 기본 idle timeout: 60분
18
+ DEFAULT_IDLE_TIMEOUT_SECONDS = 60 * 60
19
+
20
+ # 서버 체크 주기: 30초
21
+ CHECK_INTERVAL_SECONDS = 30
22
+
23
+ # SageMaker 메타데이터 경로
24
+ SAGEMAKER_METADATA_PATH = "/opt/ml/metadata/resource-metadata.json"
25
+
26
+
27
+ def _is_sagemaker() -> bool:
28
+ """SageMaker 환경인지 판별."""
29
+ return bool(os.environ.get("SAGEMAKER_SPACE_NAME"))
30
+
31
+
32
+ class ServerIdleMonitor:
33
+ """서버 사이드 유휴 상태 모니터."""
34
+
35
+ def __init__(self, server_app):
36
+ self.server_app = server_app
37
+ self.last_activity_time = time.time()
38
+ self._task: Optional[asyncio.Task] = None
39
+ self._shutdown_triggered = False
40
+ self._sagemaker = _is_sagemaker()
41
+
42
+ # idle timeout 설정 로드
43
+ self.idle_timeout_seconds = self._load_timeout()
44
+
45
+ # 환경변수 (shutdown API용)
46
+ self.hdsp_env = os.environ.get("HDSP_ENV")
47
+ self.project_id = os.environ.get("HDSP_PRJ_ID")
48
+ self.user_id = os.environ.get("JUPYTERHUB_USER")
49
+
50
+ def _load_timeout(self) -> int:
51
+ """config에서 idle timeout 로드. 최대 1시간."""
52
+ try:
53
+ from codebuff_core.managers.config_manager import get_config_manager
54
+
55
+ cm = get_config_manager()
56
+ config = cm.get_config()
57
+ timeout = config.get("idleTimeout")
58
+ if isinstance(timeout, (int, float)) and timeout > 0:
59
+ return min(int(timeout), DEFAULT_IDLE_TIMEOUT_SECONDS)
60
+ except Exception:
61
+ pass
62
+ return DEFAULT_IDLE_TIMEOUT_SECONDS
63
+
64
+ def start(self):
65
+ """모니터링 시작."""
66
+ if self.idle_timeout_seconds <= 0:
67
+ logger.info("[ServerIdleMonitor] Disabled (timeout=0)")
68
+ return
69
+
70
+ # SageMaker는 환경변수 없어도 시작 가능
71
+ if not self._sagemaker and not (
72
+ self.hdsp_env and self.project_id and self.user_id
73
+ ):
74
+ logger.info(
75
+ "[ServerIdleMonitor] Disabled (missing HDSP_ENV/HDSP_PRJ_ID/JUPYTERHUB_USER)"
76
+ )
77
+ return
78
+
79
+ self._task = asyncio.ensure_future(self._monitor_loop())
80
+ logger.info(
81
+ "[ServerIdleMonitor] Started (sagemaker=%s, timeout=%ds, interval=%ds)",
82
+ self._sagemaker,
83
+ self.idle_timeout_seconds,
84
+ CHECK_INTERVAL_SECONDS,
85
+ )
86
+
87
+ def stop(self):
88
+ """모니터링 중지."""
89
+ if self._task and not self._task.done():
90
+ self._task.cancel()
91
+ logger.info("[ServerIdleMonitor] Stopped")
92
+
93
+ def reset(self):
94
+ """활동 감지 시 타이머 리셋 (HTTP 요청 시 호출)."""
95
+ self.last_activity_time = time.time()
96
+
97
+ def get_idle_status(self) -> dict:
98
+ """현재 idle 상태 반환 (API 응답용)."""
99
+ kernel_last = self._check_kernel_activity()
100
+ terminal_last = self._check_terminal_activity()
101
+ latest = max(self.last_activity_time, kernel_last, terminal_last)
102
+ idle_seconds = time.time() - latest
103
+ return {
104
+ "idle": idle_seconds >= self.idle_timeout_seconds,
105
+ "idle_seconds": round(idle_seconds),
106
+ "timeout_seconds": self.idle_timeout_seconds,
107
+ }
108
+
109
+ def _check_kernel_activity(self) -> float:
110
+ """커널 매니저에서 마지막 활동 시간 조회. epoch 반환."""
111
+ last = 0.0
112
+ try:
113
+ km = self.server_app.kernel_manager
114
+ for kid in km.list_kernel_ids():
115
+ kernel = km.get_kernel(kid)
116
+ # last_activity는 datetime 객체
117
+ if hasattr(kernel, "last_activity") and kernel.last_activity:
118
+ ts = kernel.last_activity.timestamp()
119
+ if ts > last:
120
+ last = ts
121
+ # execution_state로 busy 체크
122
+ if (
123
+ hasattr(kernel, "execution_state")
124
+ and kernel.execution_state == "busy"
125
+ ):
126
+ return time.time() # busy면 지금 활동 중
127
+ except Exception as e:
128
+ logger.debug("[ServerIdleMonitor] Kernel check error: %s", e)
129
+ return last
130
+
131
+ def _check_terminal_activity(self) -> float:
132
+ """터미널 매니저에서 마지막 활동 시간 조회. 자식 프로세스도 감지."""
133
+ last = 0.0
134
+ try:
135
+ tm = self.server_app.web_app.settings.get("terminal_manager")
136
+ if tm is None:
137
+ return last
138
+
139
+ # terminado의 terminals dict에서 직접 순회
140
+ terminals = getattr(tm, "terminals", None)
141
+ if not terminals:
142
+ logger.info(
143
+ "[ServerIdleMonitor] No tm.terminals found (type=%s)",
144
+ type(tm).__name__,
145
+ )
146
+ return last
147
+
148
+ for term_name, term_obj in terminals.items():
149
+ # last_activity 확인 (REST model 대신 직접)
150
+ try:
151
+ term_model = tm.get(term_name)
152
+ if isinstance(term_model, dict):
153
+ la = term_model.get("last_activity")
154
+ if la and hasattr(la, "timestamp"):
155
+ ts = la.timestamp()
156
+ if ts > last:
157
+ last = ts
158
+ except Exception:
159
+ pass
160
+
161
+ # PTY 자식 프로세스 확인
162
+ if self._terminal_has_running_process(term_obj, term_name):
163
+ return time.time()
164
+
165
+ except Exception as e:
166
+ logger.warning("[ServerIdleMonitor] Terminal check error: %s", e)
167
+ return last
168
+
169
+ def _terminal_has_running_process(self, term, term_name: str = "") -> bool:
170
+ """터미널의 쉘에 자식 프로세스가 실행 중인지 확인."""
171
+ try:
172
+ import psutil
173
+
174
+ ptyproc = getattr(term, "ptyproc", None)
175
+ if ptyproc is None:
176
+ logger.info(
177
+ "[ServerIdleMonitor] Terminal '%s': no ptyproc attr", term_name
178
+ )
179
+ return False
180
+
181
+ pid = getattr(ptyproc, "pid", None)
182
+ if pid is None:
183
+ logger.info(
184
+ "[ServerIdleMonitor] Terminal '%s': ptyproc has no pid", term_name
185
+ )
186
+ return False
187
+
188
+ if not ptyproc.isalive():
189
+ return False
190
+
191
+ proc = psutil.Process(pid)
192
+ children = proc.children(recursive=True)
193
+ if children:
194
+ child_info = [(c.pid, c.name()) for c in children]
195
+ logger.info(
196
+ "[ServerIdleMonitor] Terminal '%s' (pid=%d) has %d child(ren): %s",
197
+ term_name,
198
+ pid,
199
+ len(children),
200
+ child_info,
201
+ )
202
+ return True
203
+ return False
204
+ except Exception as e:
205
+ logger.warning(
206
+ "[ServerIdleMonitor] Process check error for '%s': %s", term_name, e
207
+ )
208
+ return False
209
+
210
+ async def _monitor_loop(self):
211
+ """주기적으로 idle 상태를 체크."""
212
+ try:
213
+ while True:
214
+ await asyncio.sleep(CHECK_INTERVAL_SECONDS)
215
+
216
+ if self._shutdown_triggered:
217
+ break
218
+
219
+ # 커널/터미널에서 마지막 활동 시간 조회
220
+ now = time.time()
221
+ kernel_last = self._check_kernel_activity()
222
+ terminal_last = self._check_terminal_activity()
223
+
224
+ # HTTP 요청 활동, 커널 활동, 터미널 활동 중 가장 최근
225
+ latest_activity = max(
226
+ self.last_activity_time, kernel_last, terminal_last
227
+ )
228
+
229
+ idle_seconds = now - latest_activity
230
+
231
+ # 60초마다 상태 로깅
232
+ if int(idle_seconds) % 60 < CHECK_INTERVAL_SECONDS:
233
+ logger.info(
234
+ "[ServerIdleMonitor] idle=%.0fs/%.0fs, http=%.0fs ago, kernel=%.0fs ago, terminal=%.0fs ago",
235
+ idle_seconds,
236
+ self.idle_timeout_seconds,
237
+ now - self.last_activity_time,
238
+ now - kernel_last if kernel_last > 0 else -1,
239
+ now - terminal_last if terminal_last > 0 else -1,
240
+ )
241
+
242
+ if idle_seconds >= self.idle_timeout_seconds:
243
+ logger.info(
244
+ "[ServerIdleMonitor] Idle timeout reached (%.0fs idle). Triggering shutdown.",
245
+ idle_seconds,
246
+ )
247
+ self._shutdown_triggered = True
248
+ from .log_paths import write_idle_shutdown_log
249
+
250
+ write_idle_shutdown_log(
251
+ idle_seconds=idle_seconds,
252
+ timeout_setting=self.idle_timeout_seconds,
253
+ trigger_source="server",
254
+ )
255
+ await self._call_shutdown()
256
+ break
257
+
258
+ except asyncio.CancelledError:
259
+ pass
260
+ except Exception as e:
261
+ logger.error("[ServerIdleMonitor] Monitor loop error: %s", e, exc_info=True)
262
+
263
+ async def _call_shutdown(self):
264
+ """환경에 따라 적절한 shutdown 호출."""
265
+ if self._sagemaker:
266
+ await self._sagemaker_shutdown()
267
+ else:
268
+ await self._codebuff_shutdown()
269
+
270
+ async def _sagemaker_shutdown(self):
271
+ """SageMaker delete_app API로 노트북 종료."""
272
+ try:
273
+ import boto3
274
+
275
+ meta = self._load_sagemaker_metadata()
276
+ domain_id = meta.get("DomainId") or os.environ.get("DOMAIN_ID", "")
277
+ space_name = meta.get("SpaceName") or os.environ.get(
278
+ "SAGEMAKER_SPACE_NAME", ""
279
+ )
280
+ app_type = meta.get("AppType", "JupyterLab")
281
+ app_name = meta.get("AppName", "default")
282
+
283
+ if not domain_id or not space_name:
284
+ logger.error(
285
+ "[ServerIdleMonitor] SageMaker shutdown failed: missing DomainId=%s, SpaceName=%s",
286
+ domain_id,
287
+ space_name,
288
+ )
289
+ return
290
+
291
+ logger.info(
292
+ "[ServerIdleMonitor] SageMaker delete_app: domain=%s, space=%s, app_type=%s, app=%s",
293
+ domain_id,
294
+ space_name,
295
+ app_type,
296
+ app_name,
297
+ )
298
+
299
+ client = boto3.client("sagemaker", region_name="ap-northeast-2")
300
+ client.delete_app(
301
+ DomainId=domain_id,
302
+ SpaceName=space_name,
303
+ AppType=app_type,
304
+ AppName=app_name,
305
+ )
306
+ logger.info("[ServerIdleMonitor] SageMaker delete_app success")
307
+ except Exception as e:
308
+ logger.error("[ServerIdleMonitor] SageMaker shutdown error: %s", e)
309
+
310
+ def _load_sagemaker_metadata(self) -> dict:
311
+ """SageMaker 리소스 메타데이터 로드."""
312
+ try:
313
+ with open(SAGEMAKER_METADATA_PATH) as f:
314
+ return json.load(f)
315
+ except Exception:
316
+ return {}
317
+
318
+ async def _codebuff_shutdown(self):
319
+ """HDSP shutdown API 호출."""
320
+ import httpx
321
+
322
+ if self.hdsp_env == "dev":
323
+ api_base = "https://hdsp-api-dev.hyundaicard.com"
324
+ elif self.hdsp_env == "prod":
325
+ api_base = "https://hdsp-api.hyundaicard.com"
326
+ else:
327
+ logger.warning("[ServerIdleMonitor] Unknown HDSP_ENV: %s", self.hdsp_env)
328
+ return
329
+
330
+ api_url = f"{api_base}/k8s/jupyter/{self.project_id}/{self.user_id}"
331
+ logger.info("[ServerIdleMonitor] Calling shutdown API: DELETE %s", api_url)
332
+
333
+ try:
334
+ async with httpx.AsyncClient(timeout=30.0) as client:
335
+ response = await client.delete(api_url)
336
+
337
+ if response.status_code in (200, 204):
338
+ logger.info("[ServerIdleMonitor] Shutdown API success")
339
+ else:
340
+ logger.error(
341
+ "[ServerIdleMonitor] Shutdown API failed: %s %s",
342
+ response.status_code,
343
+ response.text,
344
+ )
345
+ except Exception as e:
346
+ logger.error("[ServerIdleMonitor] Shutdown API error: %s", e)
@@ -0,0 +1,157 @@
1
+ {
2
+ "name": "codebuff",
3
+ "version": "2.1.74",
4
+ "description": "Codebuff JupyterLab Extension - Thin client for Agent Server",
5
+ "keywords": [
6
+ "jupyter",
7
+ "jupyterlab",
8
+ "jupyterlab-extension",
9
+ "ai",
10
+ "llm",
11
+ "code-assistant"
12
+ ],
13
+ "homepage": "https://github.com/lks21c/codebuff",
14
+ "bugs": {
15
+ "url": "https://github.com/lks21c/codebuff/issues"
16
+ },
17
+ "license": "MIT",
18
+ "author": {
19
+ "name": "Your Name",
20
+ "email": "your.email@example.com"
21
+ },
22
+ "files": [
23
+ "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
24
+ "frontend/styles/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
25
+ "schema/*.json"
26
+ ],
27
+ "main": "lib/index.js",
28
+ "types": "lib/index.d.ts",
29
+ "style": "frontend/styles/index.css",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/lks21c/codebuff.git"
33
+ },
34
+ "scripts": {
35
+ "build": "jlpm build:lib && jlpm build:labextension:dev",
36
+ "build:prod": "jlpm clean && jlpm build:lib && jlpm build:labextension",
37
+ "build:labextension": "jupyter labextension build .",
38
+ "build:labextension:dev": "jupyter labextension build --development True .",
39
+ "prebuild:lib": "mkdir -p lib/styles/icons && cp -r frontend/styles/icons/* lib/styles/icons/",
40
+ "build:lib": "jlpm prebuild:lib && tsc",
41
+ "clean": "jlpm clean:all",
42
+ "clean:lib": "rimraf lib tsconfig.tsbuildinfo",
43
+ "clean:lintcache": "rimraf .eslintcache .stylelintcache",
44
+ "clean:labextension": "rimraf jupyter_ext/labextension",
45
+ "clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:lintcache",
46
+ "eslint": "jlpm eslint:check --fix",
47
+ "eslint:check": "eslint frontend --cache --ext .ts,.tsx",
48
+ "install:extension": "jlpm build",
49
+ "lint": "jlpm lint:check",
50
+ "lint:check": "jlpm stylelint:check && jlpm eslint:check",
51
+ "prettier": "jlpm prettier:base --write --list-different",
52
+ "prettier:base": "prettier \"frontend/**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"",
53
+ "prettier:check": "jlpm prettier:base --check",
54
+ "stylelint": "jlpm stylelint:check --fix",
55
+ "stylelint:check": "stylelint --cache \"frontend/styles/**/*.css\"",
56
+ "watch": "run-p watch:src watch:labextension",
57
+ "watch:src": "tsc -w",
58
+ "watch:labextension": "jupyter labextension watch .",
59
+ "test:ui": "playwright test",
60
+ "test:ui:headed": "playwright test --headed",
61
+ "test:ui:debug": "playwright test --debug",
62
+ "lint:fix": "jlpm stylelint && jlpm eslint"
63
+ },
64
+ "dependencies": {
65
+ "@codemirror/commands": "6.8.1",
66
+ "@codemirror/state": "^6.5.2",
67
+ "@emotion/react": "^11.11.0",
68
+ "@emotion/styled": "^11.11.0",
69
+ "@jupyter-widgets/base": "^6.0.0",
70
+ "@jupyterlab/application": "^4.0.0",
71
+ "@jupyterlab/cells": "^4.0.0",
72
+ "@jupyterlab/console": "^4.0.0",
73
+ "@jupyterlab/launcher": "^4.0.0",
74
+ "@jupyterlab/notebook": "^4.0.0",
75
+ "@jupyterlab/rendermime": "^4.0.0",
76
+ "@jupyterlab/services": "^7.0.0",
77
+ "@jupyterlab/terminal": "^4.0.0",
78
+ "@lezer/java": "^1.1.4",
79
+ "@lezer/javascript": "^1.5.5",
80
+ "@lezer/python": "^1.1.19",
81
+ "@lumino/widgets": "^2.0.0",
82
+ "@mui/icons-material": "^7.3.4",
83
+ "@mui/material": "^5.14.0",
84
+ "diff": "^9.0.0",
85
+ "react": "^18.2.0",
86
+ "react-dom": "^18.2.0",
87
+ "react-markdown": "^10.1.0",
88
+ "react-syntax-highlighter": "^16.1.1",
89
+ "remark-gfm": "^4.0.1",
90
+ "sql-formatter": "^15.7.3"
91
+ },
92
+ "devDependencies": {
93
+ "@axe-core/playwright": "^4.8.0",
94
+ "@codemirror/lang-java": "^6.0.2",
95
+ "@codemirror/lang-javascript": "^6.2.4",
96
+ "@codemirror/lang-markdown": "^6.5.0",
97
+ "@codemirror/lang-python": "6.2.0",
98
+ "@jupyterlab/builder": "^4.0.0",
99
+ "@jupyterlab/galata": "^5.0.0",
100
+ "@playwright/test": "^1.40.0",
101
+ "@types/react": "^18.2.0",
102
+ "@types/react-dom": "^18.2.0",
103
+ "@types/react-syntax-highlighter": "^15.5.13",
104
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
105
+ "@typescript-eslint/parser": "^6.0.0",
106
+ "eslint": "^8.45.0",
107
+ "eslint-config-prettier": "^8.8.0",
108
+ "eslint-plugin-prettier": "^5.0.0",
109
+ "eslint-plugin-react": "^7.33.0",
110
+ "eslint-plugin-react-hooks": "^4.6.2",
111
+ "npm-run-all": "^4.1.5",
112
+ "prettier": "^3.0.0",
113
+ "rimraf": "^5.0.1",
114
+ "stylelint": "^15.10.0",
115
+ "stylelint-config-prettier": "^9.0.5",
116
+ "stylelint-config-recommended": "^13.0.0",
117
+ "stylelint-config-standard": "^34.0.0",
118
+ "stylelint-prettier": "^4.0.0",
119
+ "typescript": "~5.1.6"
120
+ },
121
+ "sideEffects": true,
122
+ "styleModule": "frontend/styles/index.js",
123
+ "publishConfig": {
124
+ "access": "public"
125
+ },
126
+ "jupyterlab": {
127
+ "extension": true,
128
+ "outputDir": "jupyter_ext/labextension",
129
+ "sharedPackages": {
130
+ "@jupyterlab/application": {
131
+ "bundled": false,
132
+ "singleton": true
133
+ },
134
+ "@jupyterlab/cells": {
135
+ "bundled": false,
136
+ "singleton": true
137
+ },
138
+ "@jupyterlab/notebook": {
139
+ "bundled": false,
140
+ "singleton": true
141
+ },
142
+ "@jupyterlab/rendermime": {
143
+ "bundled": false,
144
+ "singleton": true
145
+ },
146
+ "@jupyter-widgets/base": {
147
+ "bundled": false,
148
+ "singleton": true
149
+ }
150
+ },
151
+ "_build": {
152
+ "load": "static/remoteEntry.5292fc9c6df7e7920cc8.js",
153
+ "extension": "./extension",
154
+ "style": "./style"
155
+ }
156
+ }
157
+ }