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.
- package/backend/pyproject.toml +31 -5
- package/backend/src/flowent/agent.py +13 -4
- package/backend/src/flowent/compact.py +35 -14
- package/backend/src/flowent/llm.py +73 -7
- package/backend/src/flowent/main.py +260 -59
- package/backend/src/flowent/static/assets/index-CRSV2xu1.css +2 -0
- package/backend/src/flowent/static/assets/index-DUYj6rgD.js +82 -0
- package/backend/src/flowent/static/index.html +2 -2
- package/backend/src/flowent/storage.py +135 -3
- package/backend/src/flowent/usage.py +315 -0
- package/backend/uv.lock +971 -3
- package/dist/frontend/assets/index-CRSV2xu1.css +2 -0
- package/dist/frontend/assets/index-DUYj6rgD.js +82 -0
- package/dist/frontend/index.html +2 -2
- package/package.json +24 -3
- package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/_version.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/approval.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/channels.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/cli.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/compact.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/context.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/llm.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/logging.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/main.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/mcp.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/mcp_import.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/patch.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/paths.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/permissions.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/sandbox.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/skills.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/storage.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/tools.cpython-313.pyc +0 -0
- package/backend/src/flowent/static/assets/index-BlaCigkZ.js +0 -82
- package/backend/src/flowent/static/assets/index-CRvbsH4K.css +0 -2
- package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_agent_tools.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_approval.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_channels.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_health.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_llm_providers.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_mcp.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_patch.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_permissions.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_persistence.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_skills.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_startup_requirements.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_workspace_chat.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/conftest.py +0 -60
- package/backend/tests/test_agent_tools.py +0 -1124
- package/backend/tests/test_approval.py +0 -283
- package/backend/tests/test_channels.py +0 -360
- package/backend/tests/test_health.py +0 -12
- package/backend/tests/test_llm_providers.py +0 -548
- package/backend/tests/test_logging.py +0 -212
- package/backend/tests/test_mcp.py +0 -788
- package/backend/tests/test_patch.py +0 -112
- package/backend/tests/test_permissions.py +0 -588
- package/backend/tests/test_persistence.py +0 -249
- package/backend/tests/test_skills.py +0 -462
- package/backend/tests/test_startup_requirements.py +0 -144
- package/backend/tests/test_workspace_chat.py +0 -2174
- package/dist/frontend/assets/index-BlaCigkZ.js +0 -82
- 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()
|