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.
- codebuff_agent-2.1.74.data/data/etc/jupyter/jupyter_server_config.d/codebuff_agent.json +7 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/package.json +157 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/104.2b8f20f9d1ac2c9db01b.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/150.39fa98117cc8ca590465.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/166.386ff9b2c37ac865fdee.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/216.25cf81131bf7274dabf4.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/353.581bd1cb00746399b62e.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/367.575f9621b63c9a641aa9.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/421.11dac223a2c2da1b09f5.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/432.8513b4c6cc1d1232d165.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/432.8513b4c6cc1d1232d165.js.LICENSE.txt +19 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/439.ba5b5ef70ec6de214b8c.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/439.ba5b5ef70ec6de214b8c.js.LICENSE.txt +8 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/481.cc2dec9fa869d66f1407.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/487.0796e931d6e651482c14.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/49.d326f554b6f9ccde6580.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/541.26897ea32845659e36be.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/579.e569d3edd8391fd8fbb3.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/58.eb7cc679f7535cc8a2f1.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/58.eb7cc679f7535cc8a2f1.js.LICENSE.txt +8 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/666.d9bfdb23f98489d0a188.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/677.d266c39dbed33bc388b3.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/677.d266c39dbed33bc388b3.js.LICENSE.txt +8 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/68.26b6f59c8454adb17f20.js +204 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/694.4ac443814260057e68f7.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/791.61affb8d3480d3d60e0d.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/848.4d2db09cfbb09374b299.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/848.4d2db09cfbb09374b299.js.LICENSE.txt +9 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/868.5cfe7e1a348ec5af1b8c.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/97.ea559a5b8a9a09f17c6b.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/remoteEntry.5292fc9c6df7e7920cc8.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/style.js +4 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/third-party-licenses.json +724 -0
- codebuff_agent-2.1.74.dist-info/METADATA +142 -0
- codebuff_agent-2.1.74.dist-info/RECORD +217 -0
- codebuff_agent-2.1.74.dist-info/WHEEL +4 -0
- codebuff_agent-2.1.74.dist-info/licenses/LICENSE +21 -0
- codebuff_core/__init__.py +153 -0
- codebuff_core/codebuff_config_defaults.json +64 -0
- codebuff_core/core/__init__.py +35 -0
- codebuff_core/core/code_validator.py +1238 -0
- codebuff_core/core/error_classifier.py +579 -0
- codebuff_core/core/exceptions.py +49 -0
- codebuff_core/data/tiktoken_cache/9b5ad71b2ce5302211f9c61530b329a4922fc6a4 +100256 -0
- codebuff_core/directory_policy.py +32 -0
- codebuff_core/factory.py +155 -0
- codebuff_core/interfaces.py +197 -0
- codebuff_core/knowledge/__init__.py +31 -0
- codebuff_core/knowledge/chunking.py +375 -0
- codebuff_core/knowledge/loader.py +339 -0
- codebuff_core/knowledge/watchdog_service.py +354 -0
- codebuff_core/legacy_paths.py +69 -0
- codebuff_core/llm/__init__.py +13 -0
- codebuff_core/llm/service.py +407 -0
- codebuff_core/managers/__init__.py +22 -0
- codebuff_core/managers/config_manager.py +690 -0
- codebuff_core/managers/remote_config.py +673 -0
- codebuff_core/managers/remote_skills.py +539 -0
- codebuff_core/managers/session_manager.py +255 -0
- codebuff_core/middleware/__init__.py +3 -0
- codebuff_core/middleware/error_recovery.py +394 -0
- codebuff_core/middleware/logging_utils.py +33 -0
- codebuff_core/models/__init__.py +111 -0
- codebuff_core/models/agent.py +306 -0
- codebuff_core/models/chat.py +57 -0
- codebuff_core/models/common.py +82 -0
- codebuff_core/models/rag.py +300 -0
- codebuff_core/platform_rules.py +127 -0
- codebuff_core/prompts/__init__.py +44 -0
- codebuff_core/prompts/auto_agent_prompts.py +1278 -0
- codebuff_core/services/__init__.py +30 -0
- codebuff_core/services/agent_service.py +345 -0
- codebuff_core/services/analytics_aggregate.py +837 -0
- codebuff_core/services/chat_service.py +898 -0
- codebuff_core/services/context_condenser.py +306 -0
- codebuff_core/services/pii_guardrail.py +393 -0
- codebuff_core/services/rag_chat_agent.py +2993 -0
- codebuff_core/services/rag_collections.py +2740 -0
- codebuff_core/services/rag_manager.py +890 -0
- codebuff_core/services/rag_service.py +145 -0
- codebuff_core/services/retriever.py +260 -0
- codebuff_core/services/vllm_embedding_service.py +290 -0
- codebuff_core/tests/__init__.py +1 -0
- codebuff_core/tests/conftest.py +81 -0
- codebuff_core/tests/test_error_recovery.py +647 -0
- codebuff_core/tests/test_factory.py +135 -0
- codebuff_core/tests/test_services.py +150 -0
- jupyter_ext/__init__.py +266 -0
- jupyter_ext/_version.py +4 -0
- jupyter_ext/agent/__init__.py +49 -0
- jupyter_ext/agent/agent_factory.py +2270 -0
- jupyter_ext/agent/ast_tools.py +451 -0
- jupyter_ext/agent/autofix_middleware.py +481 -0
- jupyter_ext/agent/background_procs.py +372 -0
- jupyter_ext/agent/code_history_middleware.py +211 -0
- jupyter_ext/agent/codex_autofix.py +160 -0
- jupyter_ext/agent/codex_bridge.py +3339 -0
- jupyter_ext/agent/codex_hooks.py +320 -0
- jupyter_ext/agent/codex_slash.py +685 -0
- jupyter_ext/agent/codex_threads.py +114 -0
- jupyter_ext/agent/content_injection.py +377 -0
- jupyter_ext/agent/continuation_middleware.py +548 -0
- jupyter_ext/agent/custom_skills.py +335 -0
- jupyter_ext/agent/file_diffs.py +99 -0
- jupyter_ext/agent/git_history.py +188 -0
- jupyter_ext/agent/guardrail_middleware.py +421 -0
- jupyter_ext/agent/json_repair_utils.py +54 -0
- jupyter_ext/agent/langchain_tools.py +2673 -0
- jupyter_ext/agent/mermaid_fixer.py +289 -0
- jupyter_ext/agent/mermaid_middleware.py +135 -0
- jupyter_ext/agent/middleware.py +359 -0
- jupyter_ext/agent/middleware_prompts.py +53 -0
- jupyter_ext/agent/observability.py +334 -0
- jupyter_ext/agent/patch_tool_calls.py +57 -0
- jupyter_ext/agent/prompts.py +112 -0
- jupyter_ext/agent/skill_middleware.py +257 -0
- jupyter_ext/agent/skills/airflow_dag_development.md +36 -0
- jupyter_ext/agent/skills/anomaly_detection.md +12 -0
- jupyter_ext/agent/skills/athena_sql.md +156 -0
- jupyter_ext/agent/skills/bdp_table_search.md +33 -0
- jupyter_ext/agent/skills/bigquery.md +65 -0
- jupyter_ext/agent/skills/bigquery_oracle.md +231 -0
- jupyter_ext/agent/skills/bigquery_tuning.md +307 -0
- jupyter_ext/agent/skills/classification_evaluation.md +11 -0
- jupyter_ext/agent/skills/clustering_analysis.md +12 -0
- jupyter_ext/agent/skills/comprehensive_eda.md +11 -0
- jupyter_ext/agent/skills/data_analysis.md +236 -0
- jupyter_ext/agent/skills/data_cleaning_pipeline.md +12 -0
- jupyter_ext/agent/skills/data_loading.md +158 -0
- jupyter_ext/agent/skills/data_quality_report.md +13 -0
- jupyter_ext/agent/skills/dimensionality_reduction.md +12 -0
- jupyter_ext/agent/skills/emr_script_development.md +17 -0
- jupyter_ext/agent/skills/experiment_tracking.md +13 -0
- jupyter_ext/agent/skills/feature_importance_analysis.md +10 -0
- jupyter_ext/agent/skills/hyperparameter_tuning.md +12 -0
- jupyter_ext/agent/skills/hypothesis_testing.md +12 -0
- jupyter_ext/agent/skills/imbalanced_data_handling.md +12 -0
- jupyter_ext/agent/skills/inference.md +392 -0
- jupyter_ext/agent/skills/lambda_function_development.md +19 -0
- jupyter_ext/agent/skills/model_comparison.md +11 -0
- jupyter_ext/agent/skills/model_explainability.md +12 -0
- jupyter_ext/agent/skills/model_training.md +318 -0
- jupyter_ext/agent/skills/pyspark.md +352 -0
- jupyter_ext/agent/skills/python_code_quality.md +35 -0
- jupyter_ext/agent/skills/regression_evaluation.md +12 -0
- jupyter_ext/agent/skills/spring_java_development.md +35 -0
- jupyter_ext/agent/skills/text_preprocessing.md +12 -0
- jupyter_ext/agent/skills/time_series_analysis.md +13 -0
- jupyter_ext/agent/skills/unit_testing.md +203 -0
- jupyter_ext/agent/todo_prompts.py +57 -0
- jupyter_ext/agent/tool_report.py +45 -0
- jupyter_ext/analytics_ingest.py +599 -0
- jupyter_ext/etc/jupyter/jupyter_server_config.d/codebuff_agent.json +7 -0
- jupyter_ext/handlers/__init__.py +8 -0
- jupyter_ext/handlers/analytics_handler.py +170 -0
- jupyter_ext/handlers/benchmark_handler.py +794 -0
- jupyter_ext/handlers/eda_benchmark_handler.py +1328 -0
- jupyter_ext/handlers/rag_admin_handler.py +689 -0
- jupyter_ext/handlers/websocket.py +497 -0
- jupyter_ext/http_handlers.py +8744 -0
- jupyter_ext/idle_monitor.py +346 -0
- jupyter_ext/labextension/package.json +157 -0
- jupyter_ext/labextension/static/104.2b8f20f9d1ac2c9db01b.js +1 -0
- jupyter_ext/labextension/static/150.39fa98117cc8ca590465.js +1 -0
- jupyter_ext/labextension/static/166.386ff9b2c37ac865fdee.js +1 -0
- jupyter_ext/labextension/static/216.25cf81131bf7274dabf4.js +1 -0
- jupyter_ext/labextension/static/353.581bd1cb00746399b62e.js +1 -0
- jupyter_ext/labextension/static/367.575f9621b63c9a641aa9.js +1 -0
- jupyter_ext/labextension/static/421.11dac223a2c2da1b09f5.js +1 -0
- jupyter_ext/labextension/static/432.8513b4c6cc1d1232d165.js +2 -0
- jupyter_ext/labextension/static/432.8513b4c6cc1d1232d165.js.LICENSE.txt +19 -0
- jupyter_ext/labextension/static/439.ba5b5ef70ec6de214b8c.js +2 -0
- jupyter_ext/labextension/static/439.ba5b5ef70ec6de214b8c.js.LICENSE.txt +8 -0
- jupyter_ext/labextension/static/481.cc2dec9fa869d66f1407.js +1 -0
- jupyter_ext/labextension/static/487.0796e931d6e651482c14.js +1 -0
- jupyter_ext/labextension/static/49.d326f554b6f9ccde6580.js +1 -0
- jupyter_ext/labextension/static/541.26897ea32845659e36be.js +1 -0
- jupyter_ext/labextension/static/579.e569d3edd8391fd8fbb3.js +1 -0
- jupyter_ext/labextension/static/58.eb7cc679f7535cc8a2f1.js +2 -0
- jupyter_ext/labextension/static/58.eb7cc679f7535cc8a2f1.js.LICENSE.txt +8 -0
- jupyter_ext/labextension/static/666.d9bfdb23f98489d0a188.js +1 -0
- jupyter_ext/labextension/static/677.d266c39dbed33bc388b3.js +2 -0
- jupyter_ext/labextension/static/677.d266c39dbed33bc388b3.js.LICENSE.txt +8 -0
- jupyter_ext/labextension/static/68.26b6f59c8454adb17f20.js +204 -0
- jupyter_ext/labextension/static/694.4ac443814260057e68f7.js +1 -0
- jupyter_ext/labextension/static/791.61affb8d3480d3d60e0d.js +1 -0
- jupyter_ext/labextension/static/848.4d2db09cfbb09374b299.js +2 -0
- jupyter_ext/labextension/static/848.4d2db09cfbb09374b299.js.LICENSE.txt +9 -0
- jupyter_ext/labextension/static/868.5cfe7e1a348ec5af1b8c.js +1 -0
- jupyter_ext/labextension/static/97.ea559a5b8a9a09f17c6b.js +1 -0
- jupyter_ext/labextension/static/remoteEntry.5292fc9c6df7e7920cc8.js +1 -0
- jupyter_ext/labextension/static/style.js +4 -0
- jupyter_ext/labextension/static/third-party-licenses.json +724 -0
- jupyter_ext/log_paths.py +113 -0
- jupyter_ext/resource_usage.py +519 -0
- jupyter_ext/secrets_bootstrap.py +116 -0
- jupyter_ext/team_access.py +105 -0
- jupyter_ext/tests/__init__.py +1 -0
- jupyter_ext/tests/test_context.py +234 -0
- jupyter_ext/tests/test_factory.py +563 -0
- jupyter_ext/tests/test_file_finder.py +139 -0
- jupyter_ext/tests/test_handlers.py +58 -0
- jupyter_ext/tests/test_mcp_selftest.py +204 -0
- jupyter_ext/tests/test_mermaid_fixer.py +272 -0
- jupyter_ext/tests/test_resource_tiers.py +298 -0
- jupyter_ext/tools/__init__.py +15 -0
- jupyter_ext/tools/base.py +90 -0
- jupyter_ext/tools/context.py +268 -0
- jupyter_ext/tools/dir_complete.py +86 -0
- jupyter_ext/tools/file_finder.py +303 -0
- jupyter_ext/tools/file_tools.py +244 -0
- jupyter_ext/tools/jupyter_tools.py +296 -0
- jupyter_ext/tools/registry.py +159 -0
- jupyter_ext/tools/search_tools.py +230 -0
- jupyter_ext/tools/security_tools.py +657 -0
- jupyter_ext/tools/shell_tools.py +109 -0
- 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
|
+
}
|