w1x-cli 3.5.0__tar.gz → 3.5.1__tar.gz

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,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: w1x-cli
3
- Version: 3.5.0
4
- Summary: A fast, keyboard-first CLI terminal assistant for Ollama with Function Calling, Persistent Memory, and Python Sandbox.
3
+ Version: 3.5.1
4
+ Summary: A fast, keyboard-first CLI terminal assistant for Ollama with Function Calling, Persistent Memory, and Interactive Python Runner.
5
5
  Author: W1bbex
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/w1bbex/w1x-cli
8
8
  Project-URL: Issues, https://github.com/w1bbex/w1x-cli/issues
9
- Keywords: ollama,cli,terminal,ai,llm,chat,tools,python-sandbox
9
+ Keywords: ollama,cli,terminal,ai,llm,chat,tools,python-runner
10
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Environment :: Console
12
12
  Classifier: Intended Audience :: Developers
@@ -30,15 +30,16 @@ Dynamic: license-file
30
30
 
31
31
  # 🚀 W1x CLI
32
32
 
33
- > **A fast, keyboard-first CLI terminal client for Ollama with Function Calling, Persistent Memory, and Interactive Python Sandbox.**
33
+ > **A fast, keyboard-first CLI terminal client for Ollama with Function Calling, Persistent Memory, and Interactive Python Runner.**
34
34
 
35
35
  ![Python](https://img.shields.io/badge/Python-3.10%2B-blue?logo=python)
36
+ [![PyPI](https://img.shields.io/pypi/v/w1x-cli?color=blue&logo=pypi)](https://pypi.org/project/w1x-cli/)
36
37
  ![Ollama](https://img.shields.io/badge/Ollama-Compatible-black?logo=ollama)
37
38
  ![License](https://img.shields.io/badge/License-MIT-green)
38
39
  ![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20Linux%20%7C%20macOS-brightgreen)
39
40
 
40
41
  W1x — это легковесный, отзывчивый терминальный ассистент для локальных нейросетей Ollama.
41
- Он превращает обычные локальные модели (Qwen, Llama, Gemma, DeepSeek) в полноценного автономного помощника с доступом в интернет, точными вычислениями, памятью между сессиями и интерактивной песочницей кода.
42
+ Он превращает обычные локальные модели (Qwen, Llama, Gemma, DeepSeek) в полноценного автономного помощника с доступом в интернет, точными вычислениями, памятью между сессиями и интерактивным запуском кода в отдельном терминале.
42
43
 
43
44
  ---
44
45
 
@@ -53,8 +54,8 @@ W1x — это легковесный, отзывчивый терминальн
53
54
  * **Точная дата и время:** вычисление дней недели, смещений и таймзон.
54
55
  * **Калькулятор:** точные математические формулы через безопасное синтаксическое дерево.
55
56
  * **Чтение файлов проекта:** безопасный доступ к рабочей папке через `/workspace`.
56
- * 🐍 **Интерактивная песочница кода (`/testpy`):**
57
- * Модель пишет код ➔ вы вводите `/testpy` ➔ открывается отдельная консоль ➔ вы тестируете ввод/вывод вживую ➔ полный лог тестирования автоматически возвращается в контекст нейросети.
57
+ * 🐍 **Интерактивный REPL-раннер кода (`/testpy`):**
58
+ * Модель пишет код ➔ вы вводите `/testpy` ➔ открывается отдельное окно локального терминала ➔ вы тестируете ввод/вывод вживую ➔ полный лог запуска автоматически возвращается в контекст нейросети (работает с локальным окружением пользователя, не является изолированным контейнером).
58
59
  * 🧠 **Долговременная память (`/memory`):**
59
60
  * Запоминание фактов пользователя навсегда (`"Запомни: у меня есть кот Барсик"`).
60
61
  * Автоматический стемминг падежей русского языка и умная подмешка в контекст.
@@ -74,7 +75,13 @@ W1x — это легковесный, отзывчивый терминальн
74
75
  1. Установленный **[Ollama](https://ollama.com/)** (модели можно выбрать прямо при первом запуске W1x).
75
76
  2. **Python 3.10+**
76
77
 
77
- ### Установка (Windows)
78
+ ### Вариант 1. Установка через PyPI (Рекомендуется)
79
+
80
+ `ash
81
+ pip install --upgrade w1x-cli
82
+ `
83
+
84
+ ### Вариант 2. Установка из исходников (Windows / Linux / macOS)
78
85
 
79
86
  Просто запустите **`install.bat`** или выполните в терминале:
80
87
 
@@ -163,6 +170,22 @@ w1x-cli/
163
170
  └── README.md # Документация
164
171
  ```
165
172
 
173
+ ## 🧪 Тестирование
174
+
175
+ Проект покрыт автоматическими тестами. Для запуска выполните:
176
+
177
+ `ash
178
+ pytest
179
+ `
180
+
181
+ Все **16 тестов** проверяют:
182
+ * Точность вычислений и безопасность математического синтаксического дерева.
183
+ * Изоляцию фоновых задач и корректное завершение воркеров загрузчика.
184
+ * Атомарное сохранение конфигурации и обработку поврежденных файлов сессий.
185
+ * Защиту от рассинхронизации документации и кодовой базы.
186
+
187
+ ---
188
+
166
189
  ---
167
190
 
168
191
  ## 🐛 Сообщить об ошибке и контакты
@@ -1,14 +1,15 @@
1
1
  # 🚀 W1x CLI
2
2
 
3
- > **A fast, keyboard-first CLI terminal client for Ollama with Function Calling, Persistent Memory, and Interactive Python Sandbox.**
3
+ > **A fast, keyboard-first CLI terminal client for Ollama with Function Calling, Persistent Memory, and Interactive Python Runner.**
4
4
 
5
5
  ![Python](https://img.shields.io/badge/Python-3.10%2B-blue?logo=python)
6
+ [![PyPI](https://img.shields.io/pypi/v/w1x-cli?color=blue&logo=pypi)](https://pypi.org/project/w1x-cli/)
6
7
  ![Ollama](https://img.shields.io/badge/Ollama-Compatible-black?logo=ollama)
7
8
  ![License](https://img.shields.io/badge/License-MIT-green)
8
9
  ![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20Linux%20%7C%20macOS-brightgreen)
9
10
 
10
11
  W1x — это легковесный, отзывчивый терминальный ассистент для локальных нейросетей Ollama.
11
- Он превращает обычные локальные модели (Qwen, Llama, Gemma, DeepSeek) в полноценного автономного помощника с доступом в интернет, точными вычислениями, памятью между сессиями и интерактивной песочницей кода.
12
+ Он превращает обычные локальные модели (Qwen, Llama, Gemma, DeepSeek) в полноценного автономного помощника с доступом в интернет, точными вычислениями, памятью между сессиями и интерактивным запуском кода в отдельном терминале.
12
13
 
13
14
  ---
14
15
 
@@ -23,8 +24,8 @@ W1x — это легковесный, отзывчивый терминальн
23
24
  * **Точная дата и время:** вычисление дней недели, смещений и таймзон.
24
25
  * **Калькулятор:** точные математические формулы через безопасное синтаксическое дерево.
25
26
  * **Чтение файлов проекта:** безопасный доступ к рабочей папке через `/workspace`.
26
- * 🐍 **Интерактивная песочница кода (`/testpy`):**
27
- * Модель пишет код ➔ вы вводите `/testpy` ➔ открывается отдельная консоль ➔ вы тестируете ввод/вывод вживую ➔ полный лог тестирования автоматически возвращается в контекст нейросети.
27
+ * 🐍 **Интерактивный REPL-раннер кода (`/testpy`):**
28
+ * Модель пишет код ➔ вы вводите `/testpy` ➔ открывается отдельное окно локального терминала ➔ вы тестируете ввод/вывод вживую ➔ полный лог запуска автоматически возвращается в контекст нейросети (работает с локальным окружением пользователя, не является изолированным контейнером).
28
29
  * 🧠 **Долговременная память (`/memory`):**
29
30
  * Запоминание фактов пользователя навсегда (`"Запомни: у меня есть кот Барсик"`).
30
31
  * Автоматический стемминг падежей русского языка и умная подмешка в контекст.
@@ -44,7 +45,13 @@ W1x — это легковесный, отзывчивый терминальн
44
45
  1. Установленный **[Ollama](https://ollama.com/)** (модели можно выбрать прямо при первом запуске W1x).
45
46
  2. **Python 3.10+**
46
47
 
47
- ### Установка (Windows)
48
+ ### Вариант 1. Установка через PyPI (Рекомендуется)
49
+
50
+ `ash
51
+ pip install --upgrade w1x-cli
52
+ `
53
+
54
+ ### Вариант 2. Установка из исходников (Windows / Linux / macOS)
48
55
 
49
56
  Просто запустите **`install.bat`** или выполните в терминале:
50
57
 
@@ -133,6 +140,22 @@ w1x-cli/
133
140
  └── README.md # Документация
134
141
  ```
135
142
 
143
+ ## 🧪 Тестирование
144
+
145
+ Проект покрыт автоматическими тестами. Для запуска выполните:
146
+
147
+ `ash
148
+ pytest
149
+ `
150
+
151
+ Все **16 тестов** проверяют:
152
+ * Точность вычислений и безопасность математического синтаксического дерева.
153
+ * Изоляцию фоновых задач и корректное завершение воркеров загрузчика.
154
+ * Атомарное сохранение конфигурации и обработку поврежденных файлов сессий.
155
+ * Защиту от рассинхронизации документации и кодовой базы.
156
+
157
+ ---
158
+
136
159
  ---
137
160
 
138
161
  ## 🐛 Сообщить об ошибке и контакты
@@ -1,42 +1,46 @@
1
- [build-system]
2
- requires = ["setuptools>=61.0"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "w1x-cli"
7
- version = "3.5.0"
8
- description = "A fast, keyboard-first CLI terminal assistant for Ollama with Function Calling, Persistent Memory, and Python Sandbox."
9
- readme = "README.md"
10
- requires-python = ">=3.10"
11
- license = { text = "MIT" }
12
- authors = [
13
- { name = "W1bbex" }
14
- ]
15
- keywords = ["ollama", "cli", "terminal", "ai", "llm", "chat", "tools", "python-sandbox"]
16
- classifiers = [
17
- "Development Status :: 4 - Beta",
18
- "Environment :: Console",
19
- "Intended Audience :: Developers",
20
- "License :: OSI Approved :: MIT License",
21
- "Operating System :: OS Independent",
22
- "Programming Language :: Python :: 3",
23
- "Programming Language :: Python :: 3.10",
24
- "Programming Language :: Python :: 3.11",
25
- "Programming Language :: Python :: 3.12",
26
- "Programming Language :: Python :: 3.13",
27
- "Topic :: Software Development :: User Interfaces",
28
- ]
29
- dependencies = [
30
- "ollama>=0.4.0",
31
- "rich>=13.7.0",
32
- "prompt_toolkit>=3.0.43",
33
- "requests>=2.31.0",
34
- "duckduckgo_search>=7.0.0",
35
- ]
36
-
37
- [project.scripts]
38
- w1x = "w1x.app:main"
39
-
40
- [project.urls]
41
- Homepage = "https://github.com/w1bbex/w1x-cli"
42
- Issues = "https://github.com/w1bbex/w1x-cli/issues"
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "w1x-cli"
7
+ version = "3.5.1"
8
+ description = "A fast, keyboard-first CLI terminal assistant for Ollama with Function Calling, Persistent Memory, and Interactive Python Runner."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "W1bbex" }
14
+ ]
15
+ keywords = ["ollama", "cli", "terminal", "ai", "llm", "chat", "tools", "python-runner"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Environment :: Console",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Topic :: Software Development :: User Interfaces",
28
+ ]
29
+ dependencies = [
30
+ "ollama>=0.4.0",
31
+ "rich>=13.7.0",
32
+ "prompt_toolkit>=3.0.43",
33
+ "requests>=2.31.0",
34
+ "duckduckgo_search>=7.0.0",
35
+ ]
36
+
37
+ [project.scripts]
38
+ w1x = "w1x.app:main"
39
+
40
+ [project.urls]
41
+ Homepage = "https://github.com/w1bbex/w1x-cli"
42
+ Issues = "https://github.com/w1bbex/w1x-cli/issues"
43
+
44
+ [tool.pytest.ini_options]
45
+ addopts = "--basetemp=.pytest_temp"
46
+ pythonpath = ["."]
@@ -0,0 +1,41 @@
1
+ import json
2
+ import pytest
3
+ from w1x.tools import calculate_datetime, calculate_expression, _format_offset
4
+ from datetime import timedelta
5
+
6
+
7
+ def test_calculate_datetime_mixed_offsets():
8
+ # days=1, hours=-2 -> net offset should be +22 hours forward, NOT "1 день 2 часа вперёд"
9
+ res_raw = calculate_datetime(days=1, hours=-2, base_datetime="2026-01-01T12:00:00")
10
+ data = json.loads(res_raw)
11
+ answer = data["answer"]
12
+ assert "22 часа вперёд" in answer
13
+ assert "2026-01-02T10:00:00" in data["result"]
14
+
15
+
16
+ def test_calculate_datetime_negative_offset():
17
+ # days=-1, hours=-2 -> net offset should be 1 day 2 hours backward
18
+ res_raw = calculate_datetime(days=-1, hours=-2, base_datetime="2026-01-05T12:00:00")
19
+ data = json.loads(res_raw)
20
+ answer = data["answer"]
21
+ assert "1 день 2 часа назад" in answer
22
+ assert "2026-01-04T10:00:00" in data["result"]
23
+
24
+
25
+ def test_calculate_expression_exact_float_precision():
26
+ # 0.1 + 0.2 must be strictly "0.3" without IEEE 754 float drift
27
+ res = calculate_expression("0.1 + 0.2")
28
+ assert res == "0.3"
29
+
30
+ res = calculate_expression("100 - 0.01")
31
+ assert res == "99.99"
32
+
33
+ res = calculate_expression("0.1 * 3")
34
+ assert res == "0.3"
35
+
36
+
37
+ def test_calculate_expression_integer_and_power():
38
+ assert calculate_expression("10 / 2") == "5"
39
+ assert calculate_expression("2 ** 10") == "1024"
40
+ assert calculate_expression("10 // 3") == "3"
41
+ assert calculate_expression("10 % 3") == "1"
@@ -0,0 +1,217 @@
1
+ """
2
+ Regression and resilience test suite for w1x-cli.
3
+ Covers:
4
+ - Preload task isolation and error reset under concurrency
5
+ - Worker queue termination atomicity
6
+ - Memory facts sandbox and corrupted file recovery
7
+ - Chat session recovery from invalid payloads
8
+ - Arithmetic parsing without misinterpreting dates or phone numbers
9
+ - Terminal runner and configuration atomic persistence
10
+ """
11
+ import json
12
+ import os
13
+ import re
14
+ import sys
15
+ import tempfile
16
+ import threading
17
+ import time
18
+ from pathlib import Path
19
+ from unittest.mock import MagicMock, patch
20
+
21
+ import pytest
22
+
23
+ from w1x import config, memory, models, tools
24
+ from w1x.app import load_current_chat
25
+ import w1x.app as w1x_app
26
+ from w1x.config import load_user_config, save_user_config
27
+ import w1x.config as w1x_config
28
+ from w1x.engine import calculate_expression
29
+ from w1x.memory import load_facts
30
+ from w1x.models import PreloadTask, wait_for_model_ready, _PRELOAD_LOCK
31
+ import w1x.models as w1x_models
32
+ from w1x.puller import AsyncPullManager, DownloadTask
33
+ from w1x.tools import calculate_datetime
34
+
35
+
36
+ def test_preload_task_isolation():
37
+ """Verify that multiple preload calls create isolated tasks and don't mix events."""
38
+ a_finished = threading.Event()
39
+ b_started = threading.Event()
40
+
41
+ def fake_generate_a(*args, **kwargs):
42
+ b_started.wait(timeout=2.0)
43
+ time.sleep(0.05)
44
+ a_finished.set()
45
+
46
+ with patch("w1x.models.generate", side_effect=fake_generate_a):
47
+ models.start_background_preload("model_a")
48
+ task_a = models._CURRENT_PRELOAD
49
+ assert task_a is not None
50
+ assert task_a.name == "model_a"
51
+ assert not task_a.event.is_set()
52
+
53
+ b_finished = threading.Event()
54
+ def fake_generate_b(*args, **kwargs):
55
+ b_finished.wait(timeout=2.0)
56
+
57
+ with patch("w1x.models.generate", side_effect=fake_generate_b):
58
+ b_started.set()
59
+ models.start_background_preload("model_b")
60
+ task_b = models._CURRENT_PRELOAD
61
+ assert task_b is not None
62
+ assert task_b.name == "model_b"
63
+ assert task_b is not task_a
64
+
65
+ a_finished.wait(timeout=2.0)
66
+ assert task_a.event.is_set()
67
+ assert not task_b.event.is_set()
68
+
69
+ b_finished.set()
70
+
71
+
72
+ def test_puller_worker_termination_atomicity():
73
+ """Verify worker terminates safely when queue is empty."""
74
+ mgr = AsyncPullManager()
75
+ t = threading.Thread(target=mgr._worker_loop, daemon=True)
76
+ mgr._worker_thread = t
77
+ t.start()
78
+ t.join(timeout=1.0)
79
+ assert not t.is_alive()
80
+ assert mgr._worker_thread is None
81
+
82
+
83
+ def test_models_preload_error_clears_task():
84
+ """Verify failed model preloads clear _CURRENT_PRELOAD so subsequent requests are not blocked."""
85
+ task = PreloadTask(name="broken_model")
86
+ task.error = RuntimeError("Ollama connection failed")
87
+ task.event.set()
88
+
89
+ with _PRELOAD_LOCK:
90
+ w1x_models._CURRENT_PRELOAD = task
91
+
92
+ with pytest.raises(RuntimeError, match="Ollama connection failed"):
93
+ wait_for_model_ready("broken_model")
94
+
95
+ with _PRELOAD_LOCK:
96
+ assert w1x_models._CURRENT_PRELOAD is None
97
+
98
+ wait_for_model_ready("broken_model")
99
+
100
+
101
+ def test_memory_corrupted_file_moved(tmp_path, monkeypatch):
102
+ """Verify corrupted memory-facts.json is archived to .corrupted.<timestamp>.json and reset."""
103
+ facts_file = tmp_path / "memory-facts.json"
104
+ facts_file.write_text("{broken json syntax: missing quotes", encoding="utf-8")
105
+ monkeypatch.setattr("w1x.memory._get_facts_path", lambda: facts_file)
106
+
107
+ res1 = load_facts()
108
+ assert res1 == []
109
+ assert not facts_file.exists()
110
+
111
+ corrupted_files = list(tmp_path.glob("memory-facts.corrupted.*.json"))
112
+ assert len(corrupted_files) == 1
113
+
114
+ res2 = load_facts()
115
+ assert res2 == []
116
+ corrupted_files_after = list(tmp_path.glob("memory-facts.corrupted.*.json"))
117
+ assert len(corrupted_files_after) == 1
118
+
119
+
120
+ def test_memory_sandbox_redirection(tmp_path, monkeypatch):
121
+ """Verify mutating config paths redirects facts storage without polluting user home."""
122
+ sandbox_facts = tmp_path / "test-facts.json"
123
+ sandbox_chat = tmp_path / "test-chat"
124
+
125
+ monkeypatch.setattr(config, "FACTS_PATH", sandbox_facts)
126
+ monkeypatch.setattr(config, "CHAT_DIR", sandbox_chat)
127
+
128
+ assert memory._get_facts_path() == sandbox_facts
129
+ assert memory._get_chat_dir() == sandbox_chat
130
+ assert memory.load_facts() == []
131
+
132
+ memory.save_facts([{"fact": "Test fact", "created_at": "2026-11-04"}])
133
+ assert sandbox_facts.exists()
134
+ assert len(memory.load_facts()) == 1
135
+
136
+
137
+ def test_tools_datetime_offset_safe_types():
138
+ """Verify datetime offset parameters accept flexible string floats and None safely."""
139
+ res_str = calculate_datetime(days="1.5", hours=None, minutes="0", seconds=10)
140
+ data = json.loads(res_str)
141
+ assert "answer" in data or "datetime" in data or "error" not in data
142
+
143
+ res_err = calculate_datetime(days="invalid_days")
144
+ data_err = json.loads(res_err)
145
+ assert "error" in data_err
146
+
147
+
148
+ def test_app_load_current_chat_corrupted_list(tmp_path, monkeypatch):
149
+ """Verify chat memory loader recovers gracefully from empty or list-typed JSON."""
150
+ chat_file = tmp_path / "current-chat.json"
151
+ chat_file.write_text("[]", encoding="utf-8")
152
+ monkeypatch.setattr(w1x_app, "CURRENT_CHAT_PATH", chat_file)
153
+
154
+ sys_prompt, messages = load_current_chat()
155
+ assert sys_prompt == w1x_app.DEFAULT_SYSTEM_PROMPT
156
+ assert len(messages) >= 1
157
+ assert messages[0]["role"] == "system"
158
+
159
+
160
+ def test_engine_dates_and_phones_not_computed_as_math():
161
+ """Verify dates and phone numbers are excluded from automated arithmetic precomputation."""
162
+ text = "События 2026-11-08 и звонок на номер +7-999-123-45-67, но посчитай 25 * 4"
163
+ math_candidates = re.findall(r"[\d\s().,+\-*/%]{3,}", text.replace("×", "*").replace("÷", "/"))
164
+ precomputed = []
165
+ for cand in math_candidates:
166
+ cand_clean = cand.strip(" ,.")
167
+ if re.search(r"\b\d{4}-\d{2}-\d{2}\b", cand_clean) or re.search(r"\b\d{2}\.\d{2}\.\d{4}\b", cand_clean):
168
+ continue
169
+ if re.search(r"^\+?\d[\d\s-]{7,}\d$", cand_clean):
170
+ continue
171
+ if re.search(r"[+\-*/%]", cand_clean) and re.search(r"\d", cand_clean) and len(cand_clean) >= 3:
172
+ val = calculate_expression(cand_clean)
173
+ if not val.startswith("Ошибка"):
174
+ precomputed.append(f"{cand_clean} = {val}")
175
+
176
+ assert not any("2026-11-08" in item for item in precomputed)
177
+ assert not any("999-123" in item for item in precomputed)
178
+ assert any("25 * 4 = 100" in item for item in precomputed)
179
+
180
+
181
+ def test_config_atomic_save(tmp_path, monkeypatch):
182
+ """Verify user configuration is saved atomically via temporary replacement."""
183
+ cfg_file = tmp_path / "config.json"
184
+ monkeypatch.setattr(w1x_config, "CONFIG_FILE_PATH", cfg_file)
185
+
186
+ save_user_config({"test_key": "test_val"})
187
+ assert cfg_file.exists()
188
+ saved = json.loads(cfg_file.read_text(encoding="utf-8"))
189
+ assert saved.get("test_key") == "test_val"
190
+
191
+
192
+ def test_reset_all_models_accepts_show_message():
193
+ """Verify reset_all_models accepts show_message parameter without crash."""
194
+ res = models.reset_all_models(show_message=True)
195
+ assert isinstance(res, (list, bool, type(None)))
196
+
197
+
198
+ def test_set_file_workspace_syncs_globals(tmp_path):
199
+ """Verify set_file_workspace updates both tools and config roots."""
200
+ orig = getattr(config, "ACTIVE_FILE_ROOT", None)
201
+ try:
202
+ tools.set_file_workspace(str(tmp_path))
203
+ assert tools.ACTIVE_FILE_ROOT == tmp_path.resolve()
204
+ assert config.ACTIVE_FILE_ROOT == tmp_path.resolve()
205
+ finally:
206
+ config.ACTIVE_FILE_ROOT = orig
207
+ tools.ACTIVE_FILE_ROOT = orig
208
+
209
+
210
+ def test_testpy_runner_code_templates():
211
+ """Verify interactive test runner template handles dynamic streams and error codes."""
212
+ import inspect
213
+ from w1x import testpy
214
+ source = inspect.getsource(testpy.run_interactive_python_test)
215
+ assert "__getattr__" in source
216
+ assert "getattr(self.original, name)" in source
217
+ assert "exit_code = exc.code if isinstance(exc.code, int) else (1 if exc.code is not None else 0)" in source
@@ -1,5 +1,5 @@
1
- """W1x — Персональный локальный ИИ."""
2
-
3
- from .app import main
4
- __version__ = "3.5"
5
- __all__ = ["main"]
1
+ """W1x — Персональный локальный ИИ."""
2
+
3
+ from .app import main
4
+ __version__ = "3.5.1"
5
+ __all__ = ["main"]