flowent 0.1.3 → 0.1.5

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 (71) hide show
  1. package/backend/pyproject.toml +1 -1
  2. package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
  3. package/backend/src/flowent/__pycache__/_version.cpython-313.pyc +0 -0
  4. package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
  5. package/backend/src/flowent/__pycache__/approval.cpython-313.pyc +0 -0
  6. package/backend/src/flowent/__pycache__/channels.cpython-313.pyc +0 -0
  7. package/backend/src/flowent/__pycache__/cli.cpython-313.pyc +0 -0
  8. package/backend/src/flowent/__pycache__/compact.cpython-313.pyc +0 -0
  9. package/backend/src/flowent/__pycache__/context.cpython-313.pyc +0 -0
  10. package/backend/src/flowent/__pycache__/llm.cpython-313.pyc +0 -0
  11. package/backend/src/flowent/__pycache__/logging.cpython-313.pyc +0 -0
  12. package/backend/src/flowent/__pycache__/main.cpython-313.pyc +0 -0
  13. package/backend/src/flowent/__pycache__/mcp.cpython-313.pyc +0 -0
  14. package/backend/src/flowent/__pycache__/mcp_import.cpython-313.pyc +0 -0
  15. package/backend/src/flowent/__pycache__/patch.cpython-313.pyc +0 -0
  16. package/backend/src/flowent/__pycache__/paths.cpython-313.pyc +0 -0
  17. package/backend/src/flowent/__pycache__/permissions.cpython-313.pyc +0 -0
  18. package/backend/src/flowent/__pycache__/sandbox.cpython-313.pyc +0 -0
  19. package/backend/src/flowent/__pycache__/skills.cpython-313.pyc +0 -0
  20. package/backend/src/flowent/__pycache__/storage.cpython-313.pyc +0 -0
  21. package/backend/src/flowent/__pycache__/tools.cpython-313.pyc +0 -0
  22. package/backend/src/flowent/agent.py +23 -1
  23. package/backend/src/flowent/approval.py +148 -0
  24. package/backend/src/flowent/cli.py +16 -2
  25. package/backend/src/flowent/compact.py +183 -0
  26. package/backend/src/flowent/context.py +19 -1
  27. package/backend/src/flowent/llm.py +51 -11
  28. package/backend/src/flowent/logging.py +60 -0
  29. package/backend/src/flowent/main.py +696 -192
  30. package/backend/src/flowent/mcp.py +3 -1
  31. package/backend/src/flowent/patch.py +55 -31
  32. package/backend/src/flowent/paths.py +12 -0
  33. package/backend/src/flowent/permissions.py +185 -42
  34. package/backend/src/flowent/sandbox.py +146 -13
  35. package/backend/src/flowent/static/assets/index-Cl20cARb.css +2 -0
  36. package/backend/src/flowent/static/assets/index-dsDDsEym.js +81 -0
  37. package/backend/src/flowent/static/index.html +2 -2
  38. package/backend/src/flowent/storage.py +257 -9
  39. package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  40. package/backend/tests/__pycache__/test_agent_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  41. package/backend/tests/__pycache__/test_approval.cpython-313-pytest-9.0.3.pyc +0 -0
  42. package/backend/tests/__pycache__/test_channels.cpython-313-pytest-9.0.3.pyc +0 -0
  43. package/backend/tests/__pycache__/test_health.cpython-313-pytest-9.0.3.pyc +0 -0
  44. package/backend/tests/__pycache__/test_llm_providers.cpython-313-pytest-9.0.3.pyc +0 -0
  45. package/backend/tests/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  46. package/backend/tests/__pycache__/test_mcp.cpython-313-pytest-9.0.3.pyc +0 -0
  47. package/backend/tests/__pycache__/test_patch.cpython-313-pytest-9.0.3.pyc +0 -0
  48. package/backend/tests/__pycache__/test_permissions.cpython-313-pytest-9.0.3.pyc +0 -0
  49. package/backend/tests/__pycache__/test_persistence.cpython-313-pytest-9.0.3.pyc +0 -0
  50. package/backend/tests/__pycache__/test_skills.cpython-313-pytest-9.0.3.pyc +0 -0
  51. package/backend/tests/__pycache__/test_startup_requirements.cpython-313-pytest-9.0.3.pyc +0 -0
  52. package/backend/tests/__pycache__/test_workspace_chat.cpython-313-pytest-9.0.3.pyc +0 -0
  53. package/backend/tests/test_agent_tools.py +312 -1
  54. package/backend/tests/test_approval.py +283 -0
  55. package/backend/tests/test_llm_providers.py +216 -0
  56. package/backend/tests/test_logging.py +30 -0
  57. package/backend/tests/test_mcp.py +76 -10
  58. package/backend/tests/test_patch.py +112 -0
  59. package/backend/tests/test_permissions.py +198 -53
  60. package/backend/tests/test_persistence.py +78 -0
  61. package/backend/tests/test_startup_requirements.py +96 -0
  62. package/backend/tests/test_workspace_chat.py +1265 -144
  63. package/backend/uv.lock +1 -1
  64. package/dist/frontend/assets/index-Cl20cARb.css +2 -0
  65. package/dist/frontend/assets/index-dsDDsEym.js +81 -0
  66. package/dist/frontend/index.html +2 -2
  67. package/package.json +2 -2
  68. package/backend/src/flowent/static/assets/index-DjF2KBwE.js +0 -81
  69. package/backend/src/flowent/static/assets/index-P-bBpJG8.css +0 -2
  70. package/dist/frontend/assets/index-DjF2KBwE.js +0 -81
  71. package/dist/frontend/assets/index-P-bBpJG8.css +0 -2
@@ -1,11 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import json
3
4
  import logging
4
5
  import os
5
6
  import re
6
7
  import sys
7
8
  from datetime import datetime
8
9
  from pathlib import Path
10
+ from typing import Any
9
11
 
10
12
  from flowent.paths import data_directory
11
13
 
@@ -14,6 +16,7 @@ DEFAULT_LOG_RETENTION = 5
14
16
  LITELLM_LOGGER_NAMES = ("LiteLLM", "LiteLLM Router", "LiteLLM Proxy")
15
17
  _configured_log_file: Path | None = None
16
18
  _configured_log_process_id: int | None = None
19
+ _llm_request_counter = 0
17
20
  _SECRET_PATTERNS = (
18
21
  re.compile(r"(?i)\b(bearer)\s+([^\s,}]+)"),
19
22
  re.compile(
@@ -36,10 +39,21 @@ def redact_log_value(value: object) -> str:
36
39
  return text
37
40
 
38
41
 
42
+ def redact_diagnostic_value(value: object) -> str:
43
+ text = str(value)
44
+ for pattern in _SECRET_PATTERNS:
45
+ text = pattern.sub("[REDACTED]", text)
46
+ return text
47
+
48
+
39
49
  def log_directory(directory: Path | None = None) -> Path:
40
50
  return (directory or data_directory()) / "logs"
41
51
 
42
52
 
53
+ def llm_request_log_directory(directory: Path | None = None) -> Path:
54
+ return log_directory(directory) / "llm-requests"
55
+
56
+
43
57
  def parse_log_level(value: str | None, default: int) -> int:
44
58
  if not value:
45
59
  return default
@@ -112,6 +126,52 @@ def new_log_file_path(directory: Path | None = None) -> Path:
112
126
  return logs / f"flowent-{timestamp}-{os.getpid()}.log"
113
127
 
114
128
 
129
+ def sanitize_diagnostic_value(value: Any) -> Any:
130
+ if isinstance(value, dict):
131
+ return {
132
+ key: sanitize_diagnostic_value(item)
133
+ for key, item in value.items()
134
+ if not secret_field_name(str(key))
135
+ }
136
+ if isinstance(value, list | tuple):
137
+ return [sanitize_diagnostic_value(item) for item in value]
138
+ if isinstance(value, str):
139
+ return redact_diagnostic_value(value)
140
+ return value
141
+
142
+
143
+ def secret_field_name(name: str) -> bool:
144
+ normalized = re.sub(r"[^a-z0-9]", "", name.lower())
145
+ return "secret" in normalized or normalized in {
146
+ "accesstoken",
147
+ "apikey",
148
+ "authorization",
149
+ "password",
150
+ "refreshtoken",
151
+ "token",
152
+ }
153
+
154
+
155
+ def write_llm_request_diagnostic(payload: dict[str, Any]) -> Path | None:
156
+ global _llm_request_counter
157
+
158
+ if not development_mode():
159
+ return None
160
+
161
+ _llm_request_counter += 1
162
+ timestamp = datetime.now().strftime("%Y%m%d-%H%M%S-%f")
163
+ path = llm_request_log_directory() / (
164
+ f"llm-request-{timestamp}-{os.getpid()}-{_llm_request_counter:06d}.json"
165
+ )
166
+ path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
167
+ path.write_text(
168
+ json.dumps(sanitize_diagnostic_value(payload), ensure_ascii=False, indent=2)
169
+ + "\n",
170
+ encoding="utf-8",
171
+ )
172
+ return path
173
+
174
+
115
175
  def prune_old_logs(logs: Path, *, keep: int = DEFAULT_LOG_RETENTION) -> None:
116
176
  files = sorted(logs.glob("flowent-*.log"), key=lambda item: item.name)
117
177
  for old_log in files[:-keep]: