beswarm 0.3.12__py3-none-any.whl → 0.3.13__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.
@@ -233,73 +233,116 @@ class Files(ContextProvider):
233
233
  def __init__(self, *paths: Union[str, List[str]], name: str = "files", visible: bool = True):
234
234
  super().__init__(name, visible=visible)
235
235
  self._files: Dict[str, str] = {}
236
+ self._file_sources: Dict[str, Dict] = {}
236
237
 
237
238
  file_paths: List[str] = []
238
239
  if paths:
239
- # Handle the case where the first argument is a list of paths, e.g., Files(['a', 'b'])
240
240
  if len(paths) == 1 and isinstance(paths[0], list):
241
241
  file_paths.extend(paths[0])
242
- # Handle the case where arguments are individual string paths, e.g., Files('a', 'b')
243
242
  else:
244
243
  file_paths.extend(paths)
245
244
 
246
245
  if file_paths:
247
246
  for path in file_paths:
248
- try:
249
- with open(path, 'r', encoding='utf-8') as f:
250
- self._files[path] = f.read()
251
- except FileNotFoundError:
252
- logging.warning(f"File not found during initialization: {path}. Skipping.")
253
- except Exception as e:
254
- logging.error(f"Error reading file {path} during initialization: {e}")
247
+ self.update(path)
248
+
249
+ def _read_from_disk(self, path: str, head: Optional[int] = None) -> str:
250
+ """Reads content from a file on disk, respecting the head parameter."""
251
+ try:
252
+ with open(path, 'r', encoding='utf-8') as f:
253
+ if head is not None and head > 0:
254
+ lines = []
255
+ for _ in range(head):
256
+ try:
257
+ lines.append(next(f))
258
+ except StopIteration:
259
+ break
260
+ return "".join(lines).rstrip('\n')
261
+ else:
262
+ return f.read()
263
+ except FileNotFoundError:
264
+ raise
265
+ except Exception as e:
266
+ logging.error(f"Error reading file {path}: {e}")
267
+ return f"[Error: Could not read file at path '{path}': {e}]"
255
268
 
256
269
  async def refresh(self):
257
270
  """
258
- Overrides the default refresh behavior. It synchronizes the content of
259
- all tracked files with the file system. If a file is not found, its
260
- content is updated to reflect the error.
271
+ Synchronizes content for files sourced from disk.
272
+ Content set manually is overwritten if the file exists, but preserved if it does not.
261
273
  """
262
274
  is_changed = False
263
- for path in list(self._files.keys()):
264
- try:
265
- with open(path, 'r', encoding='utf-8') as f:
266
- new_content = f.read()
267
- if self._files.get(path) != new_content:
268
- self._files[path] = new_content
269
- is_changed = True
270
- except FileNotFoundError:
271
- error_msg = f"[Error: File not found at path '{path}']"
272
- if self._files.get(path) != error_msg:
273
- self._files[path] = error_msg
274
- is_changed = True
275
- except Exception as e:
276
- error_msg = f"[Error: Could not read file at path '{path}': {e}]"
277
- if self._files.get(path) != error_msg:
278
- self._files[path] = error_msg
279
- is_changed = True
275
+ for path, spec in list(self._file_sources.items()):
276
+ if spec.get('source') == 'disk':
277
+ try:
278
+ head = spec.get('head')
279
+ new_content = self._read_from_disk(path, head)
280
+ if self._files.get(path) != new_content:
281
+ self._files[path] = new_content
282
+ is_changed = True
283
+ except FileNotFoundError:
284
+ error_msg = f"[Error: File not found at path '{path}']"
285
+ if self._files.get(path) != error_msg:
286
+ self._files[path] = error_msg
287
+ is_changed = True
288
+ elif spec.get('source') == 'manual':
289
+ try:
290
+ # File exists, so we must overwrite manual content.
291
+ head = spec.get('head')
292
+ new_content = self._read_from_disk(path, head)
293
+ if self._files.get(path) != new_content:
294
+ self._files[path] = new_content
295
+ is_changed = True
296
+ # If we are here, manual content was overwritten by disk content,
297
+ # so we should update the source.
298
+ self._file_sources[path] = {'source': 'disk'}
299
+
300
+ except FileNotFoundError:
301
+ # File does not exist, so we keep the manual content. No change.
302
+ pass
280
303
 
281
304
  if is_changed:
282
305
  self.mark_stale()
283
-
284
306
  await super().refresh()
285
307
 
286
- def update(self, path: str, content: Optional[str] = None):
308
+ def update(self, path: str, content: Optional[str] = None, head: Optional[Union[int, str]] = None):
287
309
  """
288
- Updates a single file. If content is provided, it updates the file in
289
- memory. If content is None, it reads the file from disk.
310
+ Updates a single file's content and its source specification.
290
311
  """
312
+ if head is not None:
313
+ try:
314
+ head = int(head)
315
+ except (ValueError, TypeError):
316
+ logging.warning(f"Invalid 'head' parameter for file '{path}': {head}. Must be an integer. Ignoring.")
317
+ head = None
318
+
291
319
  if content is not None:
292
- self._files[path] = content
320
+ # New logic: if head is also provided, decide what to do with content.
321
+ if head is not None and head > 0:
322
+ try:
323
+ # If file exists, prioritize reading from disk.
324
+ self._files[path] = self._read_from_disk(path, head)
325
+ self._file_sources[path] = {'source': 'disk', 'head': head}
326
+ except FileNotFoundError:
327
+ # If file does not exist, use the provided content's head.
328
+ lines = content.split('\n')
329
+ self._files[path] = "\n".join(lines[:head])
330
+ self._file_sources[path] = {'source': 'manual', 'head': head}
331
+ else:
332
+ # Original logic for when only content is provided.
333
+ self._files[path] = content
334
+ self._file_sources[path] = {'source': 'manual'}
293
335
  else:
336
+ # Original logic for when only path (and optional head) is provided.
294
337
  try:
295
- with open(path, 'r', encoding='utf-8') as f:
296
- self._files[path] = f.read()
338
+ self._files[path] = self._read_from_disk(path, head)
339
+ spec = {'source': 'disk'}
340
+ if head is not None and head > 0:
341
+ spec['head'] = head
342
+ self._file_sources[path] = spec
297
343
  except FileNotFoundError:
298
- logging.error(f"File not found for update: {path}.")
299
344
  self._files[path] = f"[Error: File not found at path '{path}']"
300
- except Exception as e:
301
- logging.error(f"Error reading file for update {path}: {e}.")
302
- self._files[path] = f"[Error: Could not read file at path '{path}': {e}]"
345
+ self._file_sources[path] = {'source': 'disk'}
303
346
  self.mark_stale()
304
347
  async def render(self) -> str:
305
348
  if not self._files: return None
@@ -1783,6 +1783,107 @@ Files: {Files(visible=True, name="files")}
1783
1783
  self.assertEqual(len(rendered_separated), 1, "被空消息隔开的同角色消息在渲染时应该合并")
1784
1784
  self.assertEqual(rendered_separated[0]['content'], "hihi2", "被空消息隔开的同角色消息合并后内容不正确")
1785
1785
 
1786
+ async def test_zzc_files_update_with_head(self):
1787
+ """测试 Files.update 是否支持 head 参数以及新的 refresh 逻辑"""
1788
+ test_file = "test_file_head.txt"
1789
+ file_content = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5"
1790
+ with open(test_file, "w", encoding='utf-8') as f:
1791
+ f.write(file_content)
1792
+
1793
+ try:
1794
+ messages = Messages(UserMessage(Files(name="files")))
1795
+
1796
+ # 1. Test update with head=3 using the provider from Messages
1797
+ files_provider = messages.provider("files")
1798
+ self.assertIsInstance(files_provider, Files)
1799
+ files_provider.update(path=test_file, head=3)
1800
+
1801
+ rendered_head = await messages.render_latest()
1802
+ expected_head_content = "Line 1\nLine 2\nLine 3"
1803
+ self.assertIn(f"<file_content>{expected_head_content}</file_content>", rendered_head[0]['content'])
1804
+ self.assertNotIn("Line 4", rendered_head[0]['content'])
1805
+
1806
+ # 2. Test update without head (default behavior)
1807
+ files_provider.update(path=test_file)
1808
+ rendered_full = await messages.render_latest()
1809
+ self.assertIn(f"<file_content>{file_content}</file_content>", rendered_full[0]['content'])
1810
+
1811
+ # 3. Test that refresh overwrites manual content when file exists
1812
+ files_provider.update(path=test_file, content="manual content")
1813
+ rendered_overwritten = await messages.render_latest()
1814
+ self.assertIn(f"<file_content>{file_content}</file_content>", rendered_overwritten[0]['content'])
1815
+ self.assertNotIn("manual content", rendered_overwritten[0]['content'])
1816
+
1817
+ # 4. Test that manual content is kept if file does not exist
1818
+ non_existent_file = "non_existent_for_sure.txt"
1819
+ files_provider.update(path=non_existent_file, content="manual content to keep")
1820
+ rendered_kept = await messages.render_latest()
1821
+ self.assertIn("<file_content>manual content to keep</file_content>", rendered_kept[0]['content'])
1822
+
1823
+ finally:
1824
+ if os.path.exists(test_file):
1825
+ os.remove(test_file)
1826
+
1827
+ async def test_zzd_files_update_with_string_head(self):
1828
+ """测试 Files.update 是否能自动将字符串 head 参数转换为整数"""
1829
+ test_file = "test_file_string_head.txt"
1830
+ file_content = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5"
1831
+ with open(test_file, "w", encoding='utf-8') as f:
1832
+ f.write(file_content)
1833
+
1834
+ try:
1835
+ messages = Messages(UserMessage(Files(name="files")))
1836
+
1837
+ # Test update with head="3"
1838
+ files_provider = messages.provider("files")
1839
+ self.assertIsInstance(files_provider, Files)
1840
+ # This is the part that will likely fail before implementation
1841
+ files_provider.update(path=test_file, head="3")
1842
+
1843
+ rendered_head = await messages.render_latest()
1844
+ expected_head_content = "Line 1\nLine 2\nLine 3"
1845
+ self.assertIn(f"<file_content>{expected_head_content}</file_content>", rendered_head[0]['content'])
1846
+ self.assertNotIn("Line 4", rendered_head[0]['content'])
1847
+
1848
+ finally:
1849
+ if os.path.exists(test_file):
1850
+ os.remove(test_file)
1851
+
1852
+ async def test_zzc_files_update_with_head_and_content(self):
1853
+ """测试update同时设置path, content, head时的行为"""
1854
+ existing_file = "test_file_existing.txt"
1855
+ non_existent_file = "test_file_non_existent.txt"
1856
+ existing_content = "Line 1 (from file)\nLine 2 (from file)\nLine 3 (from file)\nLine 4 (from file)"
1857
+ manual_content = "Line 1 (manual)\nLine 2 (manual)\nLine 3 (manual)\nLine 4 (manual)"
1858
+
1859
+ # --- 场景1: 文件存在 ---
1860
+ with open(existing_file, "w", encoding='utf-8') as f:
1861
+ f.write(existing_content)
1862
+ try:
1863
+ messages = Messages(UserMessage(Files(name="files")))
1864
+ files_provider = messages.provider("files")
1865
+ files_provider.update(path=existing_file, content=manual_content, head=2)
1866
+ rendered_existing = await messages.render_latest()
1867
+ expected_existing_head = "Line 1 (from file)\nLine 2 (from file)"
1868
+ self.assertIn(f"<file_content>{expected_existing_head}</file_content>", rendered_existing[0]['content'])
1869
+ self.assertNotIn("manual", rendered_existing[0]['content'])
1870
+ finally:
1871
+ if os.path.exists(existing_file):
1872
+ os.remove(existing_file)
1873
+
1874
+ # --- 场景2: 文件不存在 ---
1875
+ try:
1876
+ messages = Messages(UserMessage(Files(name="files")))
1877
+ files_provider = messages.provider("files")
1878
+ files_provider.update(path=non_existent_file, content=manual_content, head=2)
1879
+ rendered_non_existent = await messages.render_latest()
1880
+ expected_manual_head = "Line 1 (manual)\nLine 2 (manual)"
1881
+ self.assertIn(f"<file_content>{expected_manual_head}</file_content>", rendered_non_existent[0]['content'])
1882
+ self.assertNotIn("from file", rendered_non_existent[0]['content'])
1883
+ finally:
1884
+ if os.path.exists(non_existent_file):
1885
+ os.remove(non_existent_file)
1886
+
1786
1887
  # ==============================================================================
1787
1888
  # 6. 演示
1788
1889
  # ==============================================================================
@@ -615,7 +615,7 @@ class chatgpt(BaseLLM):
615
615
  final_tool_response = tool_response
616
616
  if "<tool_error>" not in tool_response:
617
617
  if tool_name == "read_file":
618
- self.conversation[convo_id].provider("files").update(tool_info['parameter']["file_path"], tool_response)
618
+ self.conversation[convo_id].provider("files").update(tool_info['parameter']["file_path"], tool_response, head=safe_get(tool_info, 'parameter', "head", default=None))
619
619
  final_tool_response = "Read file successfully! The file content has been updated in the tag <latest_file_content>."
620
620
  elif tool_name == "get_knowledge_graph_tree":
621
621
  self.conversation[convo_id].provider("knowledge_graph").visible = True
@@ -2,7 +2,6 @@ import os
2
2
  import json
3
3
  import chardet
4
4
  from pdfminer.high_level import extract_text
5
-
6
5
  from ..aient.aient.plugins import register_tool
7
6
  from ..core import current_work_dir
8
7
 
@@ -52,9 +51,7 @@ Examples:
52
51
  </read_file>
53
52
  """
54
53
 
55
- work_dir = current_work_dir.get()
56
- if not work_dir:
57
- return f"<tool_error>关键上下文 'current_work_dir' 未设置,无法确定安全的工作目录。</tool_error>"
54
+ work_dir = current_work_dir.get(os.getcwd())
58
55
 
59
56
  # Determine the final, absolute path for the file operation.
60
57
  if os.path.isabs(file_path):
@@ -62,12 +59,7 @@ Examples:
62
59
  else:
63
60
  final_path = os.path.join(work_dir, file_path)
64
61
 
65
- # Security check: Ensure the final path is within the designated work directory.
66
- # abs_work_dir = os.path.abspath(work_dir)
67
62
  abs_final_path = os.path.abspath(final_path)
68
- # if not abs_final_path.startswith(abs_work_dir):
69
- # return f"<tool_error>路径遍历攻击被阻止。尝试写入的路径 '{file_path}' 解析后超出了允许的工作目录范围 '{abs_work_dir}'。</tool_error>"
70
-
71
63
  file_path = abs_final_path
72
64
 
73
65
  try:
@@ -125,16 +117,23 @@ Examples:
125
117
  # 更新:修改通用文件读取逻辑以支持多种编码
126
118
  # 这部分替换了原有的 else 块内容
127
119
  try:
120
+ # 优化:分块读取以加速 chardet
128
121
  with open(file_path, 'rb') as file: # 以二进制模式读取
129
- raw_data = file.read()
122
+ # 1. 读取用于检测编码的初始块
123
+ detection_chunk = file.read(4096) # Read first 4KB
124
+ # 2. 读取文件的剩余部分
125
+ remaining_data = file.read()
126
+
127
+ # 将两部分数据合并为完整的文件内容
128
+ raw_data = detection_chunk + remaining_data
130
129
 
131
130
  if not raw_data: # 处理空文件
132
131
  text_content = ""
133
132
  else:
134
- detected_info = chardet.detect(raw_data)
133
+ # 3. 仅对初始块运行 chardet 以提高速度
134
+ detected_info = chardet.detect(detection_chunk) # Detect on the small chunk
135
135
  primary_encoding_to_try = detected_info['encoding']
136
136
  confidence = detected_info['confidence']
137
-
138
137
  decoded_successfully = False
139
138
 
140
139
  # 尝试1: 使用检测到的编码 (如果置信度高且编码有效)
@@ -193,14 +192,6 @@ Examples:
193
192
  # Invalid head value, ignore and proceed with normal logic.
194
193
  pass
195
194
 
196
- # if file_path.lower().endswith('.csv'):
197
- # lines = text_content.splitlines(True)
198
- # if len(lines) > 500:
199
- # top_lines = lines[:250]
200
- # bottom_lines = lines[-250:]
201
- # omitted_count = len(lines) - 500
202
- # text_content = "".join(top_lines) + f"\n... (中间省略了 {omitted_count} 行) ...\n" + "".join(bottom_lines)
203
-
204
195
  # 返回文件内容
205
196
  return text_content
206
197
 
@@ -213,7 +204,8 @@ Examples:
213
204
  return f"<tool_error>读取文件时发生错误: {e}</tool_error>"
214
205
 
215
206
  if __name__ == "__main__":
216
- # python -m beswarm.aient.aient.plugins.read_file
217
- result = read_file("./work/cax/Lenia Notebook.ipynb", head=10)
207
+ # python -m beswarm.tools.read_file
208
+ # result = read_file("traindata.csv", head=3)
209
+ result = read_file("testdata.csv", head=3)
218
210
  print(result)
219
- print(len(result))
211
+ print(f"行数: {len(result.splitlines())}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: beswarm
3
- Version: 0.3.12
3
+ Version: 0.3.13
4
4
  Summary: MAS
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -10,9 +10,9 @@ beswarm/agents/chatgroup.py,sha256=PzrmRcDKAbB7cxL16nMod_CzPosDV6bfTmXxQVuv-AQ,1
10
10
  beswarm/agents/planact.py,sha256=dhPkMUaZLc1dO0TrI1Orr_i60iLzJHQIYBI_QjjSJwI,18723
11
11
  beswarm/aient/aient/__init__.py,sha256=SRfF7oDVlOOAi6nGKiJIUK6B_arqYLO9iSMp-2IZZps,21
12
12
  beswarm/aient/aient/architext/architext/__init__.py,sha256=79Ih1151rfcqZdr7F8HSZSTs_iT2SKd1xCkehMsXeXs,19
13
- beswarm/aient/aient/architext/architext/core.py,sha256=Sid3_Bf1a988xOWIfXOrojeCSn6ElJ4Hqxy6rmDnqFY,35591
13
+ beswarm/aient/aient/architext/architext/core.py,sha256=3io21wNZ-Za8VpQZn9nnc8qIjfON9fxMxURMC7fYCPY,37559
14
14
  beswarm/aient/aient/architext/test/openai_client.py,sha256=Dqtbmubv6vwF8uBqcayG0kbsiO65of7sgU2-DRBi-UM,4590
15
- beswarm/aient/aient/architext/test/test.py,sha256=c3ev6as7k9eDpV-le1xvj--x_hUN2xXH-oIzyQ5hsjA,83798
15
+ beswarm/aient/aient/architext/test/test.py,sha256=kWsvKq8lYLuRVlU6Sqe7PyQAP15ENpemy5EV6Se4UpE,89019
16
16
  beswarm/aient/aient/architext/test/test_save_load.py,sha256=o8DqH6gDYZkFkQy-a7blqLtJTRj5e4a-Lil48pJ0V3g,3260
17
17
  beswarm/aient/aient/core/__init__.py,sha256=NxjebTlku35S4Dzr16rdSqSTWUvvwEeACe8KvHJnjPg,34
18
18
  beswarm/aient/aient/core/log_config.py,sha256=kz2_yJv1p-o3lUQOwA3qh-LSc3wMHv13iCQclw44W9c,274
@@ -27,7 +27,7 @@ beswarm/aient/aient/core/test/test_payload.py,sha256=8jBiJY1uidm1jzL-EiK0s6UGmW9
27
27
  beswarm/aient/aient/models/__init__.py,sha256=ZTiZgbfBPTjIPSKURE7t6hlFBVLRS9lluGbmqc1WjxQ,43
28
28
  beswarm/aient/aient/models/audio.py,sha256=FNW4lxG1IhxOU7L8mvcbaeC1nXk_lpUZQlg9ijQ0h_Q,1937
29
29
  beswarm/aient/aient/models/base.py,sha256=HWIGfa2A7OTccvHK0wG1-UlHB-yaWRC7hbi4oR1Mu1Y,7228
30
- beswarm/aient/aient/models/chatgpt.py,sha256=d1ZE12AQriIl8DF6OQ3612_ieP5cbGdBhngYVUdIhKs,43814
30
+ beswarm/aient/aient/models/chatgpt.py,sha256=ZM0Ol_0vTZ5-5NjTolVfQzN47OyH1e8eJEDf2QRqwHA,43875
31
31
  beswarm/aient/aient/plugins/__init__.py,sha256=p3KO6Aa3Lupos4i2SjzLQw1hzQTigOAfEHngsldrsyk,986
32
32
  beswarm/aient/aient/plugins/arXiv.py,sha256=yHjb6PS3GUWazpOYRMKMzghKJlxnZ5TX8z9F6UtUVow,1461
33
33
  beswarm/aient/aient/plugins/config.py,sha256=TGgZ5SnNKZ8MmdznrZ-TEq7s2ulhAAwTSKH89bci3dA,7079
@@ -111,7 +111,7 @@ beswarm/tools/deep_search.py,sha256=O5n9L1foEdfNiwVpjnbM6_1X-MT4kQ2lSWKRvYWhbEg,
111
111
  beswarm/tools/edit_file.py,sha256=ZTJvbpsfRlp2t98kTn9XQ5qZBTdsWJVWv9t0lvK4RfU,9147
112
112
  beswarm/tools/graph.py,sha256=Ni51LCB65bLDDLC3ZyEJj_BeHAzhimhFgH9Ekr56Ll8,5871
113
113
  beswarm/tools/planner.py,sha256=vsHd7rE8RQHJrZ7BQ0ZXhbt4Fjh3DeyxU4piA5R-VPM,1253
114
- beswarm/tools/read_file.py,sha256=_MX2y-EtBH3wxcCBUtkAaiU4GZX34z2q3VKXjSs9gLs,10060
114
+ beswarm/tools/read_file.py,sha256=ytXkYGzI0ryNmj3_F2kv5qxL2Yb4xWDmpFrvvel3XzI,9675
115
115
  beswarm/tools/repomap.py,sha256=w98aHmjNjtvcUVc5maWORqzKqDy2KVGLooOe__uJVCU,45235
116
116
  beswarm/tools/request_input.py,sha256=3n2UW8m8Q7dxGhd7L7hzSJ1kk4ekMbtdtNZZT3dJf20,938
117
117
  beswarm/tools/screenshot.py,sha256=hyL6F8_k9Y03Nb_X18cY-klCpWWdkqyC-iGXfKX-7jc,1007
@@ -121,8 +121,8 @@ beswarm/tools/subtasks.py,sha256=Fsf_542CkECcwFwxD-F38_IUsmk06xe1P7e6pBPTy4Y,128
121
121
  beswarm/tools/worker.py,sha256=mQ1qdrQ8MgL99byAbTvxfEByFFGN9mty3UHqHjARMQ8,2331
122
122
  beswarm/tools/write_csv.py,sha256=u0Hq18Ksfheb52MVtyLNCnSDHibITpsYBPs2ub7USYA,1466
123
123
  beswarm/tools/write_file.py,sha256=L2coBqz-aRFxPBvJBrbWbUJhu7p3oKAAGb9R144bFtk,4926
124
- beswarm-0.3.12.dist-info/METADATA,sha256=lsjwHlJwRdbZPGPqsihISy5xKb1Rim2bsiTcQaYJXVo,3878
125
- beswarm-0.3.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
126
- beswarm-0.3.12.dist-info/entry_points.txt,sha256=URK7Y4PDzBgxIecQnxsWTu4O-eaFa1CoAcNTWh5R7LM,45
127
- beswarm-0.3.12.dist-info/top_level.txt,sha256=pJw4O87wvt5882smuSO6DfByJz7FJ8SxxT8h9fHCmpo,8
128
- beswarm-0.3.12.dist-info/RECORD,,
124
+ beswarm-0.3.13.dist-info/METADATA,sha256=MQHN-a_l_axNIuLC3PR6h0r_3xWYTkMiy4dBC-K3frs,3878
125
+ beswarm-0.3.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
126
+ beswarm-0.3.13.dist-info/entry_points.txt,sha256=URK7Y4PDzBgxIecQnxsWTu4O-eaFa1CoAcNTWh5R7LM,45
127
+ beswarm-0.3.13.dist-info/top_level.txt,sha256=pJw4O87wvt5882smuSO6DfByJz7FJ8SxxT8h9fHCmpo,8
128
+ beswarm-0.3.13.dist-info/RECORD,,