flowent 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/backend/pyproject.toml +31 -5
  2. package/backend/src/flowent/agent.py +13 -4
  3. package/backend/src/flowent/compact.py +35 -14
  4. package/backend/src/flowent/llm.py +73 -7
  5. package/backend/src/flowent/main.py +260 -59
  6. package/backend/src/flowent/static/assets/index-CRSV2xu1.css +2 -0
  7. package/backend/src/flowent/static/assets/index-DUYj6rgD.js +82 -0
  8. package/backend/src/flowent/static/index.html +2 -2
  9. package/backend/src/flowent/storage.py +135 -3
  10. package/backend/src/flowent/usage.py +315 -0
  11. package/backend/uv.lock +971 -3
  12. package/dist/frontend/assets/index-CRSV2xu1.css +2 -0
  13. package/dist/frontend/assets/index-DUYj6rgD.js +82 -0
  14. package/dist/frontend/index.html +2 -2
  15. package/package.json +24 -3
  16. package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
  17. package/backend/src/flowent/__pycache__/_version.cpython-313.pyc +0 -0
  18. package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
  19. package/backend/src/flowent/__pycache__/approval.cpython-313.pyc +0 -0
  20. package/backend/src/flowent/__pycache__/channels.cpython-313.pyc +0 -0
  21. package/backend/src/flowent/__pycache__/cli.cpython-313.pyc +0 -0
  22. package/backend/src/flowent/__pycache__/compact.cpython-313.pyc +0 -0
  23. package/backend/src/flowent/__pycache__/context.cpython-313.pyc +0 -0
  24. package/backend/src/flowent/__pycache__/llm.cpython-313.pyc +0 -0
  25. package/backend/src/flowent/__pycache__/logging.cpython-313.pyc +0 -0
  26. package/backend/src/flowent/__pycache__/main.cpython-313.pyc +0 -0
  27. package/backend/src/flowent/__pycache__/mcp.cpython-313.pyc +0 -0
  28. package/backend/src/flowent/__pycache__/mcp_import.cpython-313.pyc +0 -0
  29. package/backend/src/flowent/__pycache__/patch.cpython-313.pyc +0 -0
  30. package/backend/src/flowent/__pycache__/paths.cpython-313.pyc +0 -0
  31. package/backend/src/flowent/__pycache__/permissions.cpython-313.pyc +0 -0
  32. package/backend/src/flowent/__pycache__/sandbox.cpython-313.pyc +0 -0
  33. package/backend/src/flowent/__pycache__/skills.cpython-313.pyc +0 -0
  34. package/backend/src/flowent/__pycache__/storage.cpython-313.pyc +0 -0
  35. package/backend/src/flowent/__pycache__/tools.cpython-313.pyc +0 -0
  36. package/backend/src/flowent/static/assets/index-BlaCigkZ.js +0 -82
  37. package/backend/src/flowent/static/assets/index-CRvbsH4K.css +0 -2
  38. package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  39. package/backend/tests/__pycache__/test_agent_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  40. package/backend/tests/__pycache__/test_approval.cpython-313-pytest-9.0.3.pyc +0 -0
  41. package/backend/tests/__pycache__/test_channels.cpython-313-pytest-9.0.3.pyc +0 -0
  42. package/backend/tests/__pycache__/test_health.cpython-313-pytest-9.0.3.pyc +0 -0
  43. package/backend/tests/__pycache__/test_llm_providers.cpython-313-pytest-9.0.3.pyc +0 -0
  44. package/backend/tests/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  45. package/backend/tests/__pycache__/test_mcp.cpython-313-pytest-9.0.3.pyc +0 -0
  46. package/backend/tests/__pycache__/test_patch.cpython-313-pytest-9.0.3.pyc +0 -0
  47. package/backend/tests/__pycache__/test_permissions.cpython-313-pytest-9.0.3.pyc +0 -0
  48. package/backend/tests/__pycache__/test_persistence.cpython-313-pytest-9.0.3.pyc +0 -0
  49. package/backend/tests/__pycache__/test_skills.cpython-313-pytest-9.0.3.pyc +0 -0
  50. package/backend/tests/__pycache__/test_startup_requirements.cpython-313-pytest-9.0.3.pyc +0 -0
  51. package/backend/tests/__pycache__/test_workspace_chat.cpython-313-pytest-9.0.3.pyc +0 -0
  52. package/backend/tests/conftest.py +0 -60
  53. package/backend/tests/test_agent_tools.py +0 -1124
  54. package/backend/tests/test_approval.py +0 -283
  55. package/backend/tests/test_channels.py +0 -360
  56. package/backend/tests/test_health.py +0 -12
  57. package/backend/tests/test_llm_providers.py +0 -548
  58. package/backend/tests/test_logging.py +0 -212
  59. package/backend/tests/test_mcp.py +0 -788
  60. package/backend/tests/test_patch.py +0 -112
  61. package/backend/tests/test_permissions.py +0 -588
  62. package/backend/tests/test_persistence.py +0 -249
  63. package/backend/tests/test_skills.py +0 -462
  64. package/backend/tests/test_startup_requirements.py +0 -144
  65. package/backend/tests/test_workspace_chat.py +0 -2174
  66. package/dist/frontend/assets/index-BlaCigkZ.js +0 -82
  67. package/dist/frontend/assets/index-CRvbsH4K.css +0 -2
@@ -1,212 +0,0 @@
1
- import logging
2
- import sys
3
-
4
- from flowent.logging import (
5
- LITELLM_LOGGER_NAMES,
6
- TRACE_LEVEL,
7
- configure_litellm_logging,
8
- configure_logging,
9
- ensure_logging_configured,
10
- redact_log_value,
11
- sanitize_diagnostic_value,
12
- )
13
-
14
-
15
- def test_logging_creates_run_file_under_data_logs(tmp_path, monkeypatch) -> None:
16
- monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path))
17
-
18
- log_file = configure_logging()
19
-
20
- assert log_file.parent == tmp_path / "logs"
21
- assert log_file.name.startswith("flowent-")
22
- assert log_file.suffix == ".log"
23
- assert log_file.is_file()
24
-
25
-
26
- def test_file_logging_accepts_trace_and_console_uses_mode_levels(
27
- tmp_path, monkeypatch
28
- ) -> None:
29
- monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path))
30
- monkeypatch.setenv("DEBUG", "False")
31
-
32
- log_file = configure_logging()
33
- handlers = logging.getLogger().handlers
34
- file_handler = next(
35
- handler for handler in handlers if isinstance(handler, logging.FileHandler)
36
- )
37
- console_handler = next(
38
- handler for handler in handlers if not isinstance(handler, logging.FileHandler)
39
- )
40
-
41
- assert file_handler.level == TRACE_LEVEL
42
- assert console_handler.level == logging.INFO
43
-
44
- logging.getLogger("flowent.test").log(TRACE_LEVEL, "trace-only detail")
45
- for handler in logging.getLogger().handlers:
46
- handler.flush()
47
-
48
- assert "trace-only detail" in log_file.read_text()
49
-
50
- monkeypatch.setenv("DEBUG", "True")
51
- configure_logging()
52
- console_handler = next(
53
- handler
54
- for handler in logging.getLogger().handlers
55
- if not isinstance(handler, logging.FileHandler)
56
- )
57
-
58
- assert console_handler.level == logging.DEBUG
59
-
60
-
61
- def test_logging_prunes_old_run_logs(tmp_path, monkeypatch) -> None:
62
- monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path))
63
- logs = tmp_path / "logs"
64
- logs.mkdir(parents=True)
65
- for index in range(6):
66
- (logs / f"flowent-20260101-00000{index}-1.log").write_text(str(index))
67
-
68
- configure_logging()
69
-
70
- files = sorted(log.name for log in logs.glob("flowent-*.log"))
71
- assert len(files) == 5
72
- assert "flowent-20260101-000000-1.log" not in files
73
-
74
-
75
- def test_logging_path_follows_flowent_data_dir(tmp_path, monkeypatch) -> None:
76
- data_dir = tmp_path / "custom-flowent"
77
- monkeypatch.setenv("FLOWENT_DATA_DIR", str(data_dir))
78
-
79
- log_file = configure_logging()
80
-
81
- assert log_file.parent == data_dir / "logs"
82
-
83
-
84
- def test_logging_redacts_full_api_key_but_keeps_context(tmp_path, monkeypatch) -> None:
85
- monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path))
86
- log_file = configure_logging()
87
-
88
- logging.getLogger("flowent.test").log(
89
- TRACE_LEVEL,
90
- "provider=OpenAI model=gpt-5 output=hello api_key=sk-secret-value",
91
- )
92
- for handler in logging.getLogger().handlers:
93
- handler.flush()
94
-
95
- rendered = log_file.read_text()
96
-
97
- assert "provider=OpenAI" in rendered
98
- assert "model=gpt-5" in rendered
99
- assert "output=hello" in rendered
100
- assert "sk-secret-value" not in rendered
101
- assert "api_key=[REDACTED]" in rendered
102
- assert redact_log_value("authorization=Bearer sk-secret-value") == (
103
- "authorization=[REDACTED]"
104
- )
105
-
106
-
107
- def test_diagnostic_sanitizer_removes_secret_fields_and_values() -> None:
108
- sanitized = sanitize_diagnostic_value(
109
- {
110
- "api_key": "sk-root-secret",
111
- "messages": [
112
- {
113
- "role": "user",
114
- "content": "authorization=Bearer sk-message-secret",
115
- }
116
- ],
117
- "tools": [
118
- {
119
- "function": {
120
- "name": "send_message",
121
- "description": "Needs api_key=sk-tool-secret.",
122
- }
123
- }
124
- ],
125
- }
126
- )
127
-
128
- rendered = str(sanitized)
129
-
130
- assert "api_key" not in rendered
131
- assert "sk-root-secret" not in rendered
132
- assert "sk-message-secret" not in rendered
133
- assert "sk-tool-secret" not in rendered
134
-
135
-
136
- def test_direct_main_app_import_creates_data_log_file(tmp_path, monkeypatch) -> None:
137
- monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path))
138
- sys.modules.pop("flowent.main", None)
139
-
140
- try:
141
- __import__("flowent.main")
142
- finally:
143
- sys.modules.pop("flowent.main", None)
144
-
145
- files = sorted((tmp_path / "logs").glob("flowent-*.log"))
146
- assert len(files) == 1
147
-
148
-
149
- def test_create_app_creates_data_log_file(tmp_path, monkeypatch) -> None:
150
- monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path))
151
-
152
- from flowent.main import create_app
153
-
154
- create_app(serve_frontend=False)
155
-
156
- files = sorted((tmp_path / "logs").glob("flowent-*.log"))
157
- assert len(files) == 1
158
-
159
-
160
- def test_create_app_reuses_logging_handlers(tmp_path, monkeypatch) -> None:
161
- monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path))
162
-
163
- from flowent.main import create_app
164
-
165
- create_app(serve_frontend=False)
166
- create_app(serve_frontend=False)
167
-
168
- handlers = logging.getLogger().handlers
169
- file_handlers = [
170
- handler for handler in handlers if isinstance(handler, logging.FileHandler)
171
- ]
172
- console_handlers = [
173
- handler for handler in handlers if not isinstance(handler, logging.FileHandler)
174
- ]
175
- files = sorted((tmp_path / "logs").glob("flowent-*.log"))
176
-
177
- assert len(file_handlers) == 1
178
- assert len(console_handlers) == 1
179
- assert len(files) == 1
180
-
181
-
182
- def test_any_logging_path_follows_flowent_data_dir(tmp_path, monkeypatch) -> None:
183
- data_dir = tmp_path / "custom-flowent"
184
- monkeypatch.setenv("FLOWENT_DATA_DIR", str(data_dir))
185
-
186
- log_file = ensure_logging_configured()
187
-
188
- assert log_file.parent == data_dir / "logs"
189
-
190
-
191
- def test_litellm_debug_logs_use_flowent_handlers(tmp_path, monkeypatch, capsys) -> None:
192
- monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path))
193
- monkeypatch.delenv("LITELLM_LOG", raising=False)
194
-
195
- log_file = configure_logging()
196
- import litellm # noqa: F401
197
-
198
- configure_litellm_logging()
199
- logging.getLogger("LiteLLM").debug("stream chunk detail")
200
- for handler in logging.getLogger().handlers:
201
- handler.flush()
202
-
203
- captured = capsys.readouterr()
204
- litellm_handlers = [
205
- handler
206
- for logger_name in LITELLM_LOGGER_NAMES
207
- for handler in logging.getLogger(logger_name).handlers
208
- ]
209
-
210
- assert litellm_handlers == []
211
- assert "stream chunk detail" not in captured.err
212
- assert "stream chunk detail" in log_file.read_text()