more-compute 0.3.3__py3-none-any.whl → 0.4.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: more-compute
3
- Version: 0.3.3
3
+ Version: 0.4.0
4
4
  Summary: An interactive notebook environment for local and GPU computing
5
5
  Home-page: https://github.com/DannyMang/MORECOMPUTE
6
6
  Author: MoreCompute Team
@@ -44,16 +44,12 @@ Dynamic: requires-python
44
44
  [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
45
45
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
46
46
 
47
- An interactive notebook environment, similar to Marimo and Google Colab, that runs locally. It works with standard `.ipynb` files, similar to Jupyter Lab but more awesome.
48
-
49
-
47
+ An interactive Python notebook environment, similar to Marimo and Google Colab, that runs locally.
50
48
 
51
49
 
52
50
  https://github.com/user-attachments/assets/8c7ec716-dade-4de2-ad37-71d328129c97
53
51
 
54
52
 
55
-
56
-
57
53
  ## Installation
58
54
 
59
55
  **Prerequisites:** [Node.js](https://nodejs.org/) >= 20.10.0 required for web interface
@@ -83,40 +79,46 @@ pip install more-compute
83
79
  ## Usage
84
80
 
85
81
  ```bash
86
- more-compute notebook.ipynb # Open existing notebook
87
- more-compute # Create and open new notebook
82
+ more-compute notebook.py # Open existing notebook
83
+ more-compute new # Create new notebook
88
84
  more-compute --debug # Show logs
89
85
  ```
90
86
 
91
- Opens automatically at http://localhost:8000
87
+ Opens automatically at http://localhost:3141
92
88
 
93
- ## Troubleshooting
89
+ ### Converting Between Formats
94
90
 
95
- **Command not found:**
96
- ```bash
97
- uv tool update-shell # Fixes PATH automatically
98
- ```
91
+ MoreCompute uses `.py` notebooks with `# %%` cell markers, but you can convert to/from `.ipynb`:
99
92
 
100
- **Manual PATH fix (macOS/Linux):**
93
+ **From .ipynb to .py:**
101
94
  ```bash
102
- echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
103
- source ~/.bashrc
104
- ```
95
+ # Auto-detect output name (notebook.ipynb -> notebook.py)
96
+ more-compute convert notebook.ipynb
105
97
 
106
- **Manual PATH fix (Windows):**
107
- ```powershell
108
- $pythonScripts = python -c "import site; print(site.USER_BASE)"
109
- $userPath = [Environment]::GetEnvironmentVariable("Path", "User")
110
- [Environment]::SetEnvironmentVariable("Path", "$userPath;$pythonScripts\Scripts", "User")
111
- # Restart PowerShell
98
+ # Or specify output
99
+ more-compute convert notebook.ipynb -o my_notebook.py
100
+
101
+ # Then open in MoreCompute
102
+ more-compute my_notebook.py
112
103
  ```
113
104
 
114
- **Port in use:**
105
+ The converter automatically extracts dependencies from `!pip install` commands and adds UV inline script metadata.
106
+
107
+ **From .py to .ipynb:**
115
108
  ```bash
116
- export MORECOMPUTE_PORT=8080 # macOS/Linux
117
- $env:MORECOMPUTE_PORT = "8080" # Windows
109
+ # Auto-detect output name (notebook.py -> notebook.ipynb)
110
+ more-compute convert notebook.py
111
+
112
+ # Or specify output
113
+ more-compute convert notebook.py -o colab_notebook.ipynb
118
114
  ```
119
115
 
116
+ This makes your notebooks compatible with Google Colab, Jupyter, and other tools that require `.ipynb` format.
117
+
118
+ ## Troubleshooting
119
+
120
+ will add things here as things progress...
121
+
120
122
  ## Development
121
123
 
122
124
  ```bash
@@ -125,7 +127,7 @@ cd MORECOMPUTE
125
127
  uv venv && source .venv/bin/activate
126
128
  uv pip install -e .
127
129
  cd frontend && npm install && cd ..
128
- python kernel_run.py notebook.ipynb
130
+ more-compute notebook.py
129
131
  ```
130
132
 
131
133
  ## License
@@ -1,11 +1,10 @@
1
- kernel_run.py,sha256=VlIQDKa2z9noDwAyRpUz49LCspjHntnlHugHIuP47dw,17936
2
- frontend/.DS_Store,sha256=uQeHnkKyuTF1AVax3NPqtN0uCH6XNXAxL9Nkb6Q9cGw,8196
1
+ kernel_run.py,sha256=tY8n3U4-mB3PYwVWDEOe5x0v67iXMOWpIrWNFqcm7JE,21540
3
2
  frontend/.gitignore,sha256=IH4mX_SQH5rZ-W2M4IUw4E-fxgCBVHKmbQpEYJbWVM0,480
4
3
  frontend/README.md,sha256=YLVf9995r3JZD5UkII5GZCvDK9wXXNrUE0loHA4vlY8,1450
5
4
  frontend/__init__.py,sha256=L5SAOdfDfKqlgEVCvYQQRDZBTlCxutZKSpJp4018IG4,100
6
5
  frontend/eslint.config.mjs,sha256=LBCCw4SomtiVMmlTSpYRXfkRs6Xs04R1YFfoyorYyT8,879
7
6
  frontend/next-env.d.ts,sha256=ha5a7nXwEZZ88tJcvDQvYtaTFOnZJff0qjRW_Cz_zKY,262
8
- frontend/next.config.mjs,sha256=n0o6cIIVIoOtI6JlvAK-HUFd2lg1pQPfUwlFS4O6TK0,346
7
+ frontend/next.config.mjs,sha256=hxSST3ls40dm0NL0F8J9yPkHXFQZNQ_JenO7qDxbYSQ,346
9
8
  frontend/next.config.ts,sha256=OL_rEfTIZxsB_B5R9JX2AxYXgC0Fc_XiDoRlOGEDEpk,368
10
9
  frontend/package-lock.json,sha256=pGL_J72WHNTz7iLx2MOwHwnDGfbKzN-r_qnnbzfGbEU,334369
11
10
  frontend/package.json,sha256=nUVMqlXGT5m431ArOzzs_geSfmq0AKfqUB40zIAeZ5s,1395
@@ -14,20 +13,20 @@ frontend/styling_README.md,sha256=jvoXKUzJv0rEIU40gz23Z6Xugy4To8P0N9szg_hKrqw,56
14
13
  frontend/tailwind.config.ts,sha256=eP9nVaAuyYo46vGQfCyWbo25_pr2hW830fs1Itcix9Q,620
15
14
  frontend/tsconfig.json,sha256=7SvBlRBYmuXAlAteRQTGwEE7ooWuNaPUrZ219dOo61E,598
16
15
  frontend/app/favicon.ico,sha256=K4rS0zRVqPc2_DqOv48L3qiEitTA20iigzvQ-c13WTI,25931
17
- frontend/app/globals.css,sha256=pjnnmkXxa0SoPYPtfBdqyHutSeUUqGvUsgSD4Zt7dcA,33884
18
- frontend/app/layout.tsx,sha256=VMkhvePu0hV0w-huEmTocTr7lZ6DF_7eidrhzCCMwzc,7604
16
+ frontend/app/globals.css,sha256=uNJlOuKwesEGBHdZauR2-peTvBR9BH_-5J05enUTdUQ,34048
17
+ frontend/app/layout.tsx,sha256=6xut2wC-CN1aPufPfjJqa2TlYKr8gD6F9KqUlINd0hg,9295
19
18
  frontend/app/page.tsx,sha256=p-DgDv8xDnwcRfDJY4rtfSQ2VdYwbnP3G-otWqDR1l0,256
20
- frontend/components/Notebook.tsx,sha256=JImlThoX4F363yShOqdYDjIIO3i1uqfCLRkDlzurinQ,21851
19
+ frontend/components/Notebook.tsx,sha256=izTlXRI7-JR51f_uSwqV8kcMeuhiVPq_wR30h_8jhhI,22517
21
20
  frontend/components/cell/AddCellButton.tsx,sha256=ZC2Vck0JIRDxGYhYv3LPYAdKDo13U6008WG_-XoPlIM,1012
22
- frontend/components/cell/CellButton.tsx,sha256=j3lRStluVtRXjZOhi9isS3XKsRmM0WTn5SaFDM6GuYg,1386
23
- frontend/components/cell/MonacoCell.tsx,sha256=EqsajBZuQpoOWlNVG7OmmoIyY4pUfW5obiBwpniuQbw,23862
21
+ frontend/components/cell/CellButton.tsx,sha256=7OaedFBBOygLD-AcaF_FyCFBSc9841jhTwC_mDe1JrY,1439
22
+ frontend/components/cell/MonacoCell.tsx,sha256=x0cwutkb-mueaXf-FdXnK6hmUuq1nROD04uq7_aZ5xc,24743
24
23
  frontend/components/layout/ConnectionBanner.tsx,sha256=-m77wKCFpeRJ_AQwnM38jLwCY5vfpqE846xeXmT3p8A,870
25
24
  frontend/components/layout/Sidebar.tsx,sha256=kxgO2mXX11xI6rX478cUqqQ3xB40jlby21y27GTJSLU,1551
26
25
  frontend/components/modals/ConfirmModal.tsx,sha256=3WgXy_wmR8FHZPwzMevfZHFXa0blR4v_SbKG3d5McT4,3652
27
26
  frontend/components/modals/ErrorModal.tsx,sha256=kkGHQvgyMYlScKwni04-V_Dq6a0-lg0WodglLARR-uA,3536
28
27
  frontend/components/modals/SuccessModal.tsx,sha256=7NVg0MFPVvsBGDOHPVyZTNx4kmZLHgxdhZKKtTD9FTU,3385
29
28
  frontend/components/output/CellOutput.tsx,sha256=KLRzwEchvBoeoai8TbabKEwO4tHmog3EKeTJAXtmveI,3665
30
- frontend/components/output/ErrorDisplay.tsx,sha256=kYfip5zikcFIuN7WW47RVjoqwDujC1q_yMbgHrHl0E0,4996
29
+ frontend/components/output/ErrorDisplay.tsx,sha256=va_jMO9mZnrjOWxVZ7nwL1_7-Ii9I5JVwEwAZR-jsNU,5535
31
30
  frontend/components/output/MarkdownRenderer.tsx,sha256=RtZ5yNRxDXIh_NkNsNiy30wMGIW7V1gfhsjecgMdc80,3341
32
31
  frontend/components/popups/ComputePopup.tsx,sha256=B7AwwjpJ9_nNI5qSN53jdR2tAulB5yl7spjMXjH4yDM,44021
33
32
  frontend/components/popups/FilterPopup.tsx,sha256=KRIzRvVxEF47ELWIAvrqv7BZQYmEo_zOX8Nbpz0zEik,14133
@@ -35,13 +34,13 @@ frontend/components/popups/FolderPopup.tsx,sha256=V2tDAbztvNIUyPWtFiwjeIoCmFGQyD
35
34
  frontend/components/popups/MetricsPopup.tsx,sha256=ublYbOQ-pSU_w48F10uFjDYctysEwmri9lrji5YHIC4,6034
36
35
  frontend/components/popups/PackagesPopup.tsx,sha256=2DnwNVN8kwC3ff1UN_7cl6qLwAFE9F9H8hYwl7M3jTM,4141
37
36
  frontend/components/popups/SettingsPopup.tsx,sha256=rGwYxjkADnyTtpP0MjVpFPBsGoT1eENN2z7nWjLuzFE,2773
38
- frontend/contexts/PodWebSocketContext.tsx,sha256=QyEg1Msyum7h3T6F3j6pn4Ds5sXFT_9WYu5_0YZ2IUI,8256
37
+ frontend/contexts/PodWebSocketContext.tsx,sha256=YIC7tDHM4iGYhH_B75Eiye4uveVAzWoXzLqw83obGIs,8256
39
38
  frontend/lib/api.ts,sha256=L6Js3K7RcPCLfVVUz-bKX5hLAVFKCfUSHAKSHk2keAk,11026
40
39
  frontend/lib/monaco-themes.ts,sha256=jh_pZAmSMKjY_belbMbZX2WpFBN7baRxvJp9shUDYgk,5396
41
40
  frontend/lib/settings.ts,sha256=klHVyB2n-wTc8YrjKCIf2BEmrR-JLVHrRYIXkDxv9-A,6263
42
41
  frontend/lib/themes.json,sha256=mk6IGy6o_DCOerBH3QmfXozTHEiy-alsTLTTIaba7No,292018
43
42
  frontend/lib/websocket-native.ts,sha256=KcokVZAl08iWRAcBOfyt3uej352esOa_-YwLk7M5iSU,6046
44
- frontend/lib/websocket.ts,sha256=V2Y7sktt2dm1G-ZILggcqIWccsh-xoAG9dLHpNVqIqs,4500
43
+ frontend/lib/websocket.ts,sha256=K9Pf71fbRTuwbJTToLq9r4vlbv_c3JOMxJlpDinDKMI,4500
45
44
  frontend/public/file.svg,sha256=K2eBLDJcGZoCU2zb7qDFk6cvcH0yO3LuPgjbqwZ1O9Q,391
46
45
  frontend/public/globe.svg,sha256=thS5vxg5JZV2YayFFJj-HYAp_UOmL7_thvniYkpX588,1035
47
46
  frontend/public/next.svg,sha256=VZld-tbstJRaHoVt3KA8XhaqW_E_0htN9qdK55NXvPw,1375
@@ -64,17 +63,17 @@ frontend/public/fonts/Fira.ttf,sha256=dbSM4W7Drd9n_EkfXq8P31KuxbjZ1wtuFiZ8aFebvT
64
63
  frontend/public/fonts/Tiempos.woff2,sha256=h83bJKvAK301wXCMIvK7ZG5j0H2K3tzAfgo0yk4z8OE,13604
65
64
  frontend/public/fonts/VeraMono.ttf,sha256=2kKB3H2xej385kpiztkodcWJU0AFXsi6JKORTrl7NJ0,49224
66
65
  frontend/types/notebook.ts,sha256=v23RaZe6H3lU5tq6sqnJDPxC2mu0NZFDCJfiN0mgvSs,1359
67
- more_compute-0.3.3.dist-info/licenses/LICENSE,sha256=0Ot-XIetYt06iay6IhtpJkruD-cLZtjyv7_aIEE-oSc,1073
66
+ more_compute-0.4.0.dist-info/licenses/LICENSE,sha256=0Ot-XIetYt06iay6IhtpJkruD-cLZtjyv7_aIEE-oSc,1073
68
67
  morecompute/__init__.py,sha256=pcMVq8Q7qb42AOn7tqgoZJOi3epDDBnEriiv2WVKnXY,87
69
- morecompute/__version__.py,sha256=8KcCYTXH99C2-gCLuPILJvtT9YftRWJsartIx6TQ2ZY,22
68
+ morecompute/__version__.py,sha256=42STGor_9nKYXumfeV5tiyD_M8VdcddX7CEexmibPBk,22
70
69
  morecompute/cli.py,sha256=kVvzvPBqF8xO6UuhU_-TBn99nKwJ405R2mAS6zU0KBc,734
71
- morecompute/notebook.py,sha256=KEcv0eOEh9N7bPVGoRuKJb47G9MmpQ5zz1B6Dm58w18,4651
70
+ morecompute/notebook.py,sha256=RubIXps925vCFgHznkW0QvvW0I11p6IGDDOrhnZ4jYs,6824
72
71
  morecompute/process_worker.py,sha256=KsE3r-XpkYGuyO4w3t54VKkD51LfNHAZc3TYattMtrg,7185
73
- morecompute/server.py,sha256=8Bn0RzH6HB1meCr2BXwcjcFAUSFSEVK2KSkM31wX65Q,40246
72
+ morecompute/server.py,sha256=mcJwIvT9QHfpKuAq5jP2SW5hPjFB_rySevgQvOHuud8,41689
74
73
  morecompute/execution/__init__.py,sha256=jPmBmq8BZWbUEY9XFSpqt5FkgX04uNS10WnUlr7Rnms,134
75
74
  morecompute/execution/__main__.py,sha256=pAWB_1bn99u8Gb-tVMSMI-NYvbYbDiwbn40L0a0djeA,202
76
- morecompute/execution/executor.py,sha256=C7exiQN5eeg_XJzfqbJThFvRiwBPdwNrJ6koCZnVwN8,20643
77
- morecompute/execution/worker.py,sha256=-_-KD89LVA1DmGa1fTqKekttTOGX_JMtUdO02-SV5DE,16388
75
+ morecompute/execution/executor.py,sha256=UaHsMLfbj4-jKI61vt2EhHF9-9fe01EZZwiFQPsnUMg,25912
76
+ morecompute/execution/worker.py,sha256=i1FbsIGGGqRllceerYnwR0p-M18U6Ez32aeOLhpBL9s,27529
78
77
  morecompute/models/__init__.py,sha256=VLJ5GWi2uTNiZBdvl-ipSbmA6EL8FZHZ5oq-rJmm9z0,640
79
78
  morecompute/models/api_models.py,sha256=-ydvi9SeTfdoY9oVPNQS4by-kQGSknx6BHhGD8E2tpo,4553
80
79
  morecompute/services/data_manager.py,sha256=c4GKucetMM-VPNbHyzce6bZRvFfmz8kTd5RppLjoLVc,14329
@@ -85,18 +84,20 @@ morecompute/services/prime_intellect.py,sha256=b705rHv3RPRsgWedRlHwoP_S-TxxZtMSy
85
84
  morecompute/static/styles.css,sha256=el_NtrUMbAUNSiMVBn1xlG70m3iPv7dyaIbWQMexhsY,19277
86
85
  morecompute/utils/__init__.py,sha256=VIxCL3S1pnjEs4cjKGZqZB68_P8FegdeMIqBjJhI5jQ,419
87
86
  morecompute/utils/cache_util.py,sha256=lVlXudHvtyvSo_kCSxORJrI85Jod8FrQLbI2f_JOIbA,661
88
- morecompute/utils/cell_magics.py,sha256=XUxy6lIsrx-9r0mWn0smI1gvT3-kv0rhN5km9cs3D48,27278
87
+ morecompute/utils/cell_magics.py,sha256=YLxltTBprCA8jfgsjqf7Shnk4NCmGKIp3aV5_CYkKRM,29072
89
88
  morecompute/utils/config_util.py,sha256=I90om4Wf4BODc5Gy9u8Tu34AcqpkfkGAKQO6JE_NMzU,1940
90
89
  morecompute/utils/error_utils.py,sha256=e50WLFdD6ngIC30xAgrzdTYtD8tPOIFkKAAh_sPbK0I,11667
91
90
  morecompute/utils/line_magics.py,sha256=kTutYBPAWoURY_pk8HXQ38IP712M2rBBfUg3oN8VrP0,33740
91
+ morecompute/utils/notebook_converter.py,sha256=aI9JdgPO8zpE0vYcBGlMEvQT2rLmZtjP_VD5bUn80bY,4229
92
92
  morecompute/utils/notebook_util.py,sha256=3hH94dtXvhizRVTU9a2b38m_51Y4igoXpkjAXUqpVBQ,1353
93
+ morecompute/utils/py_percent_parser.py,sha256=_CVB7uP8Qu8X_yIGKv-k3Lhpw7saH67VofYvRDfGGw8,5087
93
94
  morecompute/utils/python_environment_util.py,sha256=l8WWWPwKbypknw8GwL22NXCji5i1FOy1vWG47J6og4g,7441
94
95
  morecompute/utils/shell_utils.py,sha256=fGFLhQLZU-lmMGALbbS-fKPkhQtmMhZ1FkgQ3TeoFhA,1917
95
- morecompute/utils/special_commands.py,sha256=IyF9MTINMNNo3P_f56Q6yS_P2HNjA9vohYVxEV7KYnc,17331
96
+ morecompute/utils/special_commands.py,sha256=nf2nVea5SyFqpulNYrnRx7anU4-G-e5_kLJ0PLtEi3Q,22030
96
97
  morecompute/utils/system_environment_util.py,sha256=32mQRubo0i4X61o-825T7m-eUSidcEp07qkInP1sWZA,4774
97
98
  morecompute/utils/zmq_util.py,sha256=tx7-iS04UN69OFtBzkxcEnRhT7xtI9EzRnrZ_nsH_O0,1889
98
- more_compute-0.3.3.dist-info/METADATA,sha256=RO_WGr4vGpdG7gIvYqrMH6iBnc9dQiYSpnGYV-uhQYM,3718
99
- more_compute-0.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
100
- more_compute-0.3.3.dist-info/entry_points.txt,sha256=xp7z9eRPNRM4oxkZZVlyXkhkSjN1AjoYI_B7qpDJ1bI,49
101
- more_compute-0.3.3.dist-info/top_level.txt,sha256=Tamm6ADzjwaQa1z27O7Izcyhyt9f0gVjMv1_tC810aI,32
102
- more_compute-0.3.3.dist-info/RECORD,,
99
+ more_compute-0.4.0.dist-info/METADATA,sha256=RBEFHSXqQsBN5zbiANyf86r1cHoEEPFpzwNbIVKRTLA,3877
100
+ more_compute-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
101
+ more_compute-0.4.0.dist-info/entry_points.txt,sha256=xp7z9eRPNRM4oxkZZVlyXkhkSjN1AjoYI_B7qpDJ1bI,49
102
+ more_compute-0.4.0.dist-info/top_level.txt,sha256=Tamm6ADzjwaQa1z27O7Izcyhyt9f0gVjMv1_tC810aI,32
103
+ more_compute-0.4.0.dist-info/RECORD,,
@@ -1 +1 @@
1
- __version__ = "0.3.3"
1
+ __version__ = "0.4.0"
@@ -17,6 +17,7 @@ class NextZmqExecutor:
17
17
  error_utils: "ErrorUtils"
18
18
  cmd_addr: str
19
19
  pub_addr: str
20
+ ctrl_addr: str
20
21
  execution_count: int
21
22
  interrupt_timeout: float
22
23
  worker_pid: int | None
@@ -26,12 +27,14 @@ class NextZmqExecutor:
26
27
  ctx: object # zmq.Context - untyped due to zmq type limitations
27
28
  req: object # zmq.Socket - untyped due to zmq type limitations
28
29
  sub: object # zmq.Socket - untyped due to zmq type limitations
30
+ ctrl: object # zmq.Socket - control socket for interrupts
29
31
  is_remote: bool # Flag to track if connected to remote worker
30
32
 
31
33
  def __init__(self, error_utils: "ErrorUtils", cmd_addr: str | None = None, pub_addr: str | None = None, interrupt_timeout: float = 0.5) -> None:
32
34
  self.error_utils = error_utils
33
35
  self.cmd_addr = cmd_addr or os.getenv('MC_ZMQ_CMD_ADDR', 'tcp://127.0.0.1:5555')
34
36
  self.pub_addr = pub_addr or os.getenv('MC_ZMQ_PUB_ADDR', 'tcp://127.0.0.1:5556')
37
+ self.ctrl_addr = os.getenv('MC_ZMQ_CTRL_ADDR', self.cmd_addr.replace('5555', '5557'))
35
38
  self.execution_count = 0
36
39
  self.interrupt_timeout = interrupt_timeout
37
40
  self.worker_pid = None
@@ -46,6 +49,9 @@ class NextZmqExecutor:
46
49
  self.sub = self.ctx.socket(zmq.SUB) # type: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
47
50
  self.sub.connect(self.pub_addr) # type: ignore[reportAttributeAccessIssue]
48
51
  self.sub.setsockopt_string(zmq.SUBSCRIBE, '') # type: ignore[reportAttributeAccessIssue]
52
+ # Control socket for interrupts (DEALER to connect to worker's ROUTER)
53
+ self.ctrl = self.ctx.socket(zmq.DEALER) # type: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
54
+ self.ctrl.connect(self.ctrl_addr) # type: ignore[reportAttributeAccessIssue]
49
55
  self._ensure_worker()
50
56
 
51
57
  def _ensure_special_handler(self) -> None:
@@ -66,7 +72,9 @@ class NextZmqExecutor:
66
72
  try:
67
73
  tmp.connect(self.cmd_addr) # type: ignore[reportAttributeAccessIssue]
68
74
  tmp.send_json({'type': 'ping'}) # type: ignore[reportAttributeAccessIssue]
69
- _ = cast(dict[str, object], tmp.recv_json()) # type: ignore[reportAttributeAccessIssue]
75
+ resp = cast(dict[str, object], tmp.recv_json()) # type: ignore[reportAttributeAccessIssue]
76
+ # Store worker PID even if already running (for force-kill)
77
+ self.worker_pid = resp.get('pid') # type: ignore[assignment]
70
78
  except Exception:
71
79
  #worker not responding, need to start it
72
80
  pass
@@ -80,6 +88,7 @@ class NextZmqExecutor:
80
88
  env = os.environ.copy()
81
89
  env.setdefault('MC_ZMQ_CMD_ADDR', self.cmd_addr)
82
90
  env.setdefault('MC_ZMQ_PUB_ADDR', self.pub_addr)
91
+ env.setdefault('MC_ZMQ_CTRL_ADDR', self.ctrl_addr)
83
92
  try:
84
93
  # Keep track of the worker process
85
94
  # Redirect stderr to see errors during development
@@ -148,8 +157,10 @@ class NextZmqExecutor:
148
157
  normalized_source, result, start_time, execution_count, websocket, cell_index
149
158
  )
150
159
  result['execution_time'] = f"{(time.time()-start_time)*1000:.1f}ms"
160
+ print(f"[EXECUTOR] Sending execution_complete for cell {cell_index}, status={result.get('status')}, has_error={result.get('error') is not None}", file=sys.stderr, flush=True)
151
161
  if websocket:
152
162
  await websocket.send_json({'type': 'execution_complete', 'data': {'cell_index': cell_index, 'result': result}})
163
+ print(f"[EXECUTOR] Sent execution_complete successfully", file=sys.stderr, flush=True)
153
164
  return result
154
165
  # For remote execution OR mixed commands, fall through to send via ZMQ
155
166
 
@@ -163,10 +174,18 @@ class NextZmqExecutor:
163
174
  # Consume pub until we see complete for this cell
164
175
  start_time = time.time()
165
176
  max_wait = 300.0 # 5 minute timeout for really long operations
177
+ interrupted_time = None # Track when interrupt was sent
166
178
  while True:
167
179
  # Check if this cell was interrupted
168
- if self.interrupted_cell == cell_index:
169
- print(f"[EXECUTE] Cell {cell_index} was interrupted, breaking out of execution loop", file=sys.stderr, flush=True)
180
+ if self.interrupted_cell == cell_index and interrupted_time is None:
181
+ print(f"[EXECUTE] Cell {cell_index} was interrupted, waiting for subprocess to be killed...", file=sys.stderr, flush=True)
182
+ interrupted_time = time.time()
183
+ # Don't break immediately - wait for execution_complete from worker
184
+ # Give worker 5 seconds to kill subprocess and send completion
185
+
186
+ # If interrupted and waited long enough, force break
187
+ if interrupted_time and (time.time() - interrupted_time > 5.0):
188
+ print(f"[EXECUTE] Cell {cell_index} interrupt timeout, breaking out", file=sys.stderr, flush=True)
170
189
  self.interrupted_cell = None # Clear the flag
171
190
  result.update({
172
191
  'status': 'error',
@@ -174,7 +193,7 @@ class NextZmqExecutor:
174
193
  'output_type': 'error',
175
194
  'ename': 'KeyboardInterrupt',
176
195
  'evalue': 'Execution interrupted by user',
177
- 'traceback': ['KeyboardInterrupt: Execution was stopped by user']
196
+ 'traceback': [] # No traceback needed for user-initiated interrupt
178
197
  }
179
198
  })
180
199
  break
@@ -215,6 +234,10 @@ class NextZmqExecutor:
215
234
  elif t == 'execution_complete' and msg.get('cell_index') == cell_index:
216
235
  result.update(msg.get('result') or {})
217
236
  result.setdefault('execution_count', execution_count)
237
+ # Clear interrupted flag if this was interrupted
238
+ if self.interrupted_cell == cell_index:
239
+ print(f"[EXECUTE] Cell {cell_index} completed after interrupt", file=sys.stderr, flush=True)
240
+ self.interrupted_cell = None
218
241
  break
219
242
 
220
243
  # Try to receive the reply from REQ socket (if worker is still alive)
@@ -248,7 +271,7 @@ class NextZmqExecutor:
248
271
  return result
249
272
 
250
273
  async def interrupt_kernel(self, cell_index: int | None = None) -> None:
251
- """Interrupt the kernel with escalation to force-kill if needed"""
274
+ """Interrupt the kernel using the control socket"""
252
275
  import sys
253
276
  print(f"[INTERRUPT] Starting interrupt for cell {cell_index}", file=sys.stderr, flush=True)
254
277
 
@@ -261,32 +284,22 @@ class NextZmqExecutor:
261
284
  if isinstance(cell_index, int):
262
285
  payload['cell_index'] = cell_index
263
286
 
264
- # Try graceful interrupt, but don't trust it for blocking I/O
287
+ # Send interrupt on control socket (non-blocking)
265
288
  try:
266
- # Very short timeout since we'll force-kill anyway
267
- self.req.setsockopt(zmq.SNDTIMEO, 100) # type: ignore[reportAttributeAccessIssue]
268
- self.req.setsockopt(zmq.RCVTIMEO, 100) # type: ignore[reportAttributeAccessIssue]
269
- self.req.send_json(payload) # type: ignore[reportAttributeAccessIssue]
270
- _ = cast(dict[str, object], self.req.recv_json()) # type: ignore[reportAttributeAccessIssue]
271
- print(f"[INTERRUPT] Sent interrupt signal to worker", file=sys.stderr, flush=True)
289
+ self.ctrl.setsockopt(zmq.SNDTIMEO, 1000) # type: ignore[reportAttributeAccessIssue]
290
+ self.ctrl.setsockopt(zmq.RCVTIMEO, 1000) # type: ignore[reportAttributeAccessIssue]
291
+ self.ctrl.send_json(payload) # type: ignore[reportAttributeAccessIssue]
292
+ _ = cast(dict[str, object], self.ctrl.recv_json()) # type: ignore[reportAttributeAccessIssue]
293
+ print(f"[INTERRUPT] Sent interrupt signal to worker via control socket", file=sys.stderr, flush=True)
272
294
  except Exception as e:
273
295
  print(f"[INTERRUPT] Could not send interrupt signal: {e}", file=sys.stderr, flush=True)
296
+ # If control socket fails, try force-kill immediately
297
+ print(f"[INTERRUPT] Force killing worker immediately...", file=sys.stderr, flush=True)
298
+ await self._force_kill_worker()
274
299
  finally:
275
300
  # Reset timeouts
276
- self.req.setsockopt(zmq.SNDTIMEO, -1) # type: ignore[reportAttributeAccessIssue]
277
- self.req.setsockopt(zmq.RCVTIMEO, -1) # type: ignore[reportAttributeAccessIssue]
278
-
279
- # Wait briefly to see if worker responds, but DON'T read from pub socket
280
- # (execute_cell is already reading from it - we'd steal messages!)
281
- # Instead, just wait a moment and force-kill if needed
282
- print(f"[INTERRUPT] Waiting {self.interrupt_timeout}s before force-kill...", file=sys.stderr, flush=True)
283
- await asyncio.sleep(self.interrupt_timeout)
284
-
285
- # For blocking I/O operations, interrupt rarely works - just force-kill
286
- # The interrupted_cell flag will let execute_cell break out gracefully
287
- print(f"[INTERRUPT] Force killing worker to ensure stop...", file=sys.stderr, flush=True)
288
- await self._force_kill_worker()
289
- print(f"[INTERRUPT] Force kill completed", file=sys.stderr, flush=True)
301
+ self.ctrl.setsockopt(zmq.SNDTIMEO, -1) # type: ignore[reportAttributeAccessIssue]
302
+ self.ctrl.setsockopt(zmq.RCVTIMEO, -1) # type: ignore[reportAttributeAccessIssue]
290
303
 
291
304
  # Interrupt special handler
292
305
  if self.special_handler:
@@ -325,13 +338,16 @@ class NextZmqExecutor:
325
338
  # CRITICAL: Reset socket state - close and recreate
326
339
  # The REQ socket may be waiting for a reply from the dead worker
327
340
  try:
328
- print(f"[FORCE_KILL] Resetting REQ socket", file=sys.stderr, flush=True)
341
+ print(f"[FORCE_KILL] Resetting REQ and CTRL sockets", file=sys.stderr, flush=True)
329
342
  self.req.close(0) # type: ignore[reportAttributeAccessIssue]
330
343
  self.req = self.ctx.socket(zmq.REQ) # type: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
331
344
  self.req.connect(self.cmd_addr) # type: ignore[reportAttributeAccessIssue]
332
- print(f"[FORCE_KILL] REQ socket reset complete", file=sys.stderr, flush=True)
345
+ self.ctrl.close(0) # type: ignore[reportAttributeAccessIssue]
346
+ self.ctrl = self.ctx.socket(zmq.DEALER) # type: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
347
+ self.ctrl.connect(self.ctrl_addr) # type: ignore[reportAttributeAccessIssue]
348
+ print(f"[FORCE_KILL] Socket reset complete", file=sys.stderr, flush=True)
333
349
  except Exception as e:
334
- print(f"[FORCE_KILL] Error resetting socket: {e}", file=sys.stderr, flush=True)
350
+ print(f"[FORCE_KILL] Error resetting sockets: {e}", file=sys.stderr, flush=True)
335
351
 
336
352
  # Respawn worker
337
353
  try:
@@ -341,40 +357,86 @@ class NextZmqExecutor:
341
357
 
342
358
  def reset_kernel(self) -> None:
343
359
  """Reset the kernel by shutting down worker and restarting"""
344
- # Try graceful shutdown first
360
+ import sys
361
+ print(f"[RESET] Starting kernel reset, worker_pid={self.worker_pid}, is_remote={self.is_remote}", file=sys.stderr, flush=True)
362
+
363
+ # If connected to remote GPU, DON'T kill the worker - just send shutdown message
364
+ if self.is_remote:
365
+ print(f"[RESET] Remote worker - sending shutdown message only", file=sys.stderr, flush=True)
366
+ try:
367
+ self.req.setsockopt(zmq.SNDTIMEO, 2000) # type: ignore[reportAttributeAccessIssue]
368
+ self.req.setsockopt(zmq.RCVTIMEO, 2000) # type: ignore[reportAttributeAccessIssue]
369
+ self.req.send_json({'type': 'shutdown'}) # type: ignore[reportAttributeAccessIssue]
370
+ _ = cast(dict[str, object], self.req.recv_json()) # type: ignore[reportAttributeAccessIssue]
371
+ print(f"[RESET] Remote worker acknowledged shutdown", file=sys.stderr, flush=True)
372
+ except Exception as e:
373
+ print(f"[RESET] Remote worker shutdown failed: {e}", file=sys.stderr, flush=True)
374
+ finally:
375
+ self.req.setsockopt(zmq.SNDTIMEO, -1) # type: ignore[reportAttributeAccessIssue]
376
+ self.req.setsockopt(zmq.RCVTIMEO, -1) # type: ignore[reportAttributeAccessIssue]
377
+
378
+ # Reset execution count but don't respawn worker
379
+ self.execution_count = 0
380
+ print(f"[RESET] Remote kernel reset complete", file=sys.stderr, flush=True)
381
+ return
382
+
383
+ # Local worker mode - try graceful shutdown first
345
384
  try:
346
385
  self.req.setsockopt(zmq.SNDTIMEO, 500) # type: ignore[reportAttributeAccessIssue]
347
386
  self.req.setsockopt(zmq.RCVTIMEO, 500) # type: ignore[reportAttributeAccessIssue]
348
387
  self.req.send_json({'type': 'shutdown'}) # type: ignore[reportAttributeAccessIssue]
349
388
  _ = cast(dict[str, object], self.req.recv_json()) # type: ignore[reportAttributeAccessIssue]
350
- except Exception:
351
- pass
389
+ print(f"[RESET] Sent graceful shutdown message", file=sys.stderr, flush=True)
390
+ except Exception as e:
391
+ print(f"[RESET] Graceful shutdown failed: {e}", file=sys.stderr, flush=True)
352
392
  finally:
353
393
  self.req.setsockopt(zmq.SNDTIMEO, -1) # type: ignore[reportAttributeAccessIssue]
354
394
  self.req.setsockopt(zmq.RCVTIMEO, -1) # type: ignore[reportAttributeAccessIssue]
355
395
 
356
- # Force kill if needed
396
+ # Force kill local worker if needed
357
397
  if self.worker_pid:
358
398
  try:
399
+ print(f"[RESET] Sending SIGTERM to worker PID {self.worker_pid}", file=sys.stderr, flush=True)
359
400
  os.kill(self.worker_pid, signal.SIGTERM)
360
- time.sleep(0.2)
401
+ time.sleep(0.3) # Give it time to shutdown gracefully
361
402
  try:
403
+ # Check if still alive
362
404
  os.kill(self.worker_pid, 0)
405
+ # Still alive, force kill
406
+ print(f"[RESET] Worker still alive, sending SIGKILL", file=sys.stderr, flush=True)
363
407
  os.kill(self.worker_pid, signal.SIGKILL)
408
+ time.sleep(0.2) # Wait for SIGKILL to complete
364
409
  except ProcessLookupError:
365
- pass
366
- except Exception:
367
- pass
410
+ print(f"[RESET] Worker process terminated", file=sys.stderr, flush=True)
411
+ except Exception as e:
412
+ print(f"[RESET] Error killing worker: {e}", file=sys.stderr, flush=True)
368
413
 
369
414
  if self.worker_proc:
370
415
  try:
371
416
  self.worker_proc.terminate()
372
417
  self.worker_proc.wait(timeout=1)
418
+ print(f"[RESET] Worker process terminated via Popen", file=sys.stderr, flush=True)
373
419
  except Exception:
374
420
  try:
375
421
  self.worker_proc.kill()
376
- except Exception:
377
- pass
422
+ print(f"[RESET] Worker process killed via Popen", file=sys.stderr, flush=True)
423
+ except Exception as e:
424
+ print(f"[RESET] Error killing via Popen: {e}", file=sys.stderr, flush=True)
425
+
426
+ # Close sockets first, BEFORE recreating them
427
+ print(f"[RESET] Closing old sockets", file=sys.stderr, flush=True)
428
+ try:
429
+ self.req.close(0) # type: ignore[reportAttributeAccessIssue]
430
+ except Exception:
431
+ pass
432
+ try:
433
+ self.ctrl.close(0) # type: ignore[reportAttributeAccessIssue]
434
+ except Exception:
435
+ pass
436
+
437
+ # Wait for ZMQ to release the sockets (critical!)
438
+ time.sleep(0.5)
439
+ print(f"[RESET] Sockets closed, waited for cleanup", file=sys.stderr, flush=True)
378
440
 
379
441
  # Reset state
380
442
  self.execution_count = 0
@@ -383,11 +445,14 @@ class NextZmqExecutor:
383
445
 
384
446
  # Recreate sockets
385
447
  try:
386
- self.req.close(0) # type: ignore[reportAttributeAccessIssue]
448
+ print(f"[RESET] Creating new sockets", file=sys.stderr, flush=True)
387
449
  self.req = self.ctx.socket(zmq.REQ) # type: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
388
450
  self.req.connect(self.cmd_addr) # type: ignore[reportAttributeAccessIssue]
389
- except Exception:
390
- pass
451
+ self.ctrl = self.ctx.socket(zmq.DEALER) # type: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
452
+ self.ctrl.connect(self.ctrl_addr) # type: ignore[reportAttributeAccessIssue]
453
+ print(f"[RESET] New sockets created successfully", file=sys.stderr, flush=True)
454
+ except Exception as e:
455
+ print(f"[RESET] Error creating sockets: {e}", file=sys.stderr, flush=True)
391
456
 
392
457
  # Reset special handler
393
458
  if self.special_handler is not None:
@@ -395,6 +460,10 @@ class NextZmqExecutor:
395
460
 
396
461
  # Respawn worker
397
462
  try:
463
+ print(f"[RESET] Respawning new worker", file=sys.stderr, flush=True)
398
464
  self._ensure_worker()
399
- except Exception:
400
- pass
465
+ print(f"[RESET] Kernel reset complete, new worker_pid={self.worker_pid}", file=sys.stderr, flush=True)
466
+ except Exception as e:
467
+ print(f"[RESET] Error respawning worker: {e}", file=sys.stderr, flush=True)
468
+ import traceback
469
+ traceback.print_exc()