codeframe-ai 0.9.0__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.
- codeframe/__init__.py +11 -0
- codeframe/__main__.py +20 -0
- codeframe/adapters/__init__.py +5 -0
- codeframe/adapters/e2b/__init__.py +13 -0
- codeframe/adapters/e2b/adapter.py +342 -0
- codeframe/adapters/e2b/budget.py +71 -0
- codeframe/adapters/e2b/credential_scanner.py +134 -0
- codeframe/adapters/llm/__init__.py +92 -0
- codeframe/adapters/llm/anthropic.py +414 -0
- codeframe/adapters/llm/base.py +444 -0
- codeframe/adapters/llm/mock.py +281 -0
- codeframe/adapters/llm/openai.py +483 -0
- codeframe/agents/__init__.py +8 -0
- codeframe/agents/dependency_resolver.py +714 -0
- codeframe/auth/__init__.py +16 -0
- codeframe/auth/api_key_router.py +238 -0
- codeframe/auth/api_keys.py +156 -0
- codeframe/auth/dependencies.py +358 -0
- codeframe/auth/manager.py +178 -0
- codeframe/auth/models.py +30 -0
- codeframe/auth/router.py +93 -0
- codeframe/auth/schemas.py +15 -0
- codeframe/auth/scopes.py +53 -0
- codeframe/cli/__init__.py +12 -0
- codeframe/cli/__main__.py +20 -0
- codeframe/cli/api_client.py +275 -0
- codeframe/cli/app.py +5688 -0
- codeframe/cli/auth.py +122 -0
- codeframe/cli/auth_commands.py +958 -0
- codeframe/cli/commands/__init__.py +5 -0
- codeframe/cli/config_commands.py +79 -0
- codeframe/cli/dashboard_commands.py +67 -0
- codeframe/cli/engines_commands.py +205 -0
- codeframe/cli/env_commands.py +409 -0
- codeframe/cli/helpers.py +56 -0
- codeframe/cli/hooks_commands.py +208 -0
- codeframe/cli/import_commands.py +129 -0
- codeframe/cli/pr_commands.py +549 -0
- codeframe/cli/proof_commands.py +415 -0
- codeframe/cli/stats_commands.py +311 -0
- codeframe/cli/telemetry_runtime.py +153 -0
- codeframe/cli/validators.py +123 -0
- codeframe/config/rate_limits.py +165 -0
- codeframe/core/__init__.py +15 -0
- codeframe/core/adapters/__init__.py +43 -0
- codeframe/core/adapters/agent_adapter.py +114 -0
- codeframe/core/adapters/builtin.py +326 -0
- codeframe/core/adapters/claude_code.py +62 -0
- codeframe/core/adapters/codex.py +393 -0
- codeframe/core/adapters/git_utils.py +40 -0
- codeframe/core/adapters/kilocode.py +126 -0
- codeframe/core/adapters/opencode.py +48 -0
- codeframe/core/adapters/streaming_chat.py +483 -0
- codeframe/core/adapters/subprocess_adapter.py +213 -0
- codeframe/core/adapters/verification_wrapper.py +269 -0
- codeframe/core/agent.py +2183 -0
- codeframe/core/agents_config.py +569 -0
- codeframe/core/api_key_service.py +211 -0
- codeframe/core/artifacts.py +428 -0
- codeframe/core/blocker_detection.py +218 -0
- codeframe/core/blockers.py +433 -0
- codeframe/core/checkpoints.py +481 -0
- codeframe/core/conductor.py +2255 -0
- codeframe/core/config.py +827 -0
- codeframe/core/config_watcher.py +268 -0
- codeframe/core/context.py +542 -0
- codeframe/core/context_packager.py +234 -0
- codeframe/core/credentials.py +735 -0
- codeframe/core/dependency_analyzer.py +229 -0
- codeframe/core/dependency_graph.py +290 -0
- codeframe/core/diagnostic_agent.py +712 -0
- codeframe/core/diagnostics.py +616 -0
- codeframe/core/editor.py +556 -0
- codeframe/core/engine_registry.py +256 -0
- codeframe/core/engine_stats.py +231 -0
- codeframe/core/environment.py +697 -0
- codeframe/core/events.py +375 -0
- codeframe/core/executor.py +1005 -0
- codeframe/core/fix_tracker.py +480 -0
- codeframe/core/gates.py +1322 -0
- codeframe/core/git.py +477 -0
- codeframe/core/github_connect_service.py +178 -0
- codeframe/core/github_integration_config.py +118 -0
- codeframe/core/github_issues_service.py +449 -0
- codeframe/core/hooks.py +184 -0
- codeframe/core/importers/__init__.py +1 -0
- codeframe/core/importers/ralph.py +540 -0
- codeframe/core/installer.py +650 -0
- codeframe/core/models.py +1026 -0
- codeframe/core/notifications_config.py +183 -0
- codeframe/core/planner.py +437 -0
- codeframe/core/prd.py +670 -0
- codeframe/core/prd_discovery.py +1118 -0
- codeframe/core/prd_stress_test.py +499 -0
- codeframe/core/progress.py +126 -0
- codeframe/core/proof/__init__.py +34 -0
- codeframe/core/proof/capture.py +79 -0
- codeframe/core/proof/evidence.py +56 -0
- codeframe/core/proof/ledger.py +574 -0
- codeframe/core/proof/models.py +162 -0
- codeframe/core/proof/obligations.py +103 -0
- codeframe/core/proof/runner.py +233 -0
- codeframe/core/proof/scope.py +81 -0
- codeframe/core/proof/stubs.py +156 -0
- codeframe/core/quick_fixes.py +558 -0
- codeframe/core/react_agent.py +1650 -0
- codeframe/core/reconciliation.py +183 -0
- codeframe/core/replay.py +788 -0
- codeframe/core/review.py +285 -0
- codeframe/core/runtime.py +1134 -0
- codeframe/core/sandbox/__init__.py +27 -0
- codeframe/core/sandbox/context.py +98 -0
- codeframe/core/sandbox/worktree.py +20 -0
- codeframe/core/schedule.py +396 -0
- codeframe/core/stall_detector.py +71 -0
- codeframe/core/stall_monitor.py +134 -0
- codeframe/core/state_machine.py +121 -0
- codeframe/core/streaming.py +502 -0
- codeframe/core/task_tree.py +400 -0
- codeframe/core/tasks.py +1022 -0
- codeframe/core/telemetry.py +232 -0
- codeframe/core/templates.py +221 -0
- codeframe/core/tools.py +942 -0
- codeframe/core/workspace.py +887 -0
- codeframe/core/worktrees.py +276 -0
- codeframe/git/__init__.py +5 -0
- codeframe/git/github_integration.py +505 -0
- codeframe/lib/__init__.py +0 -0
- codeframe/lib/audit_logger.py +248 -0
- codeframe/lib/metrics_tracker.py +800 -0
- codeframe/lib/quality/__init__.py +7 -0
- codeframe/lib/quality/complexity_analyzer.py +316 -0
- codeframe/lib/quality/owasp_patterns.py +284 -0
- codeframe/lib/quality/security_scanner.py +250 -0
- codeframe/lib/rate_limiter.py +312 -0
- codeframe/notifications/__init__.py +0 -0
- codeframe/notifications/webhook.py +380 -0
- codeframe/planning/__init__.py +30 -0
- codeframe/planning/issue_generator.py +219 -0
- codeframe/planning/prd_template_functions.py +137 -0
- codeframe/planning/prd_templates.py +975 -0
- codeframe/planning/task_scheduler.py +511 -0
- codeframe/planning/task_templates.py +533 -0
- codeframe/platform_store/__init__.py +5 -0
- codeframe/platform_store/database.py +277 -0
- codeframe/platform_store/repositories/__init__.py +24 -0
- codeframe/platform_store/repositories/api_key_repository.py +245 -0
- codeframe/platform_store/repositories/audit_repository.py +67 -0
- codeframe/platform_store/repositories/base.py +295 -0
- codeframe/platform_store/repositories/interactive_sessions.py +165 -0
- codeframe/platform_store/repositories/token_repository.py +598 -0
- codeframe/platform_store/repositories/workspace_registry_repository.py +175 -0
- codeframe/platform_store/schema_manager.py +321 -0
- codeframe/templates/AGENTS.md.default +94 -0
- codeframe/tui/__init__.py +5 -0
- codeframe/tui/app.py +256 -0
- codeframe/tui/data_service.py +103 -0
- codeframe/ui/__init__.py +0 -0
- codeframe/ui/dependencies.py +103 -0
- codeframe/ui/models.py +999 -0
- codeframe/ui/response_models.py +201 -0
- codeframe/ui/routers/__init__.py +5 -0
- codeframe/ui/routers/_helpers.py +29 -0
- codeframe/ui/routers/batches_v2.py +315 -0
- codeframe/ui/routers/blockers_v2.py +320 -0
- codeframe/ui/routers/checkpoints_v2.py +310 -0
- codeframe/ui/routers/costs_v2.py +322 -0
- codeframe/ui/routers/diagnose_v2.py +225 -0
- codeframe/ui/routers/discovery_v2.py +417 -0
- codeframe/ui/routers/environment_v2.py +284 -0
- codeframe/ui/routers/events_v2.py +75 -0
- codeframe/ui/routers/gates_v2.py +166 -0
- codeframe/ui/routers/git_v2.py +284 -0
- codeframe/ui/routers/github_integrations_v2.py +532 -0
- codeframe/ui/routers/interactive_sessions_v2.py +238 -0
- codeframe/ui/routers/pr_v2.py +709 -0
- codeframe/ui/routers/prd_v2.py +695 -0
- codeframe/ui/routers/proof_v2.py +755 -0
- codeframe/ui/routers/review_v2.py +360 -0
- codeframe/ui/routers/schedule_v2.py +214 -0
- codeframe/ui/routers/session_chat_ws.py +354 -0
- codeframe/ui/routers/settings_v2.py +562 -0
- codeframe/ui/routers/streaming_v2.py +155 -0
- codeframe/ui/routers/tasks_v2.py +1098 -0
- codeframe/ui/routers/templates_v2.py +232 -0
- codeframe/ui/routers/terminal_ws.py +267 -0
- codeframe/ui/routers/workspace_v2.py +527 -0
- codeframe/ui/server.py +568 -0
- codeframe/ui/shared.py +241 -0
- codeframe/workspace/__init__.py +5 -0
- codeframe/workspace/manager.py +249 -0
- codeframe_ai-0.9.0.dist-info/METADATA +517 -0
- codeframe_ai-0.9.0.dist-info/RECORD +197 -0
- codeframe_ai-0.9.0.dist-info/WHEEL +5 -0
- codeframe_ai-0.9.0.dist-info/entry_points.txt +3 -0
- codeframe_ai-0.9.0.dist-info/licenses/LICENSE +661 -0
- codeframe_ai-0.9.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""Dynamic configuration reload for batch execution.
|
|
2
|
+
|
|
3
|
+
Watches CODEFRAME.md, AGENTS.md, and CLAUDE.md for changes during
|
|
4
|
+
long-running batch executions and hot-reloads configuration without
|
|
5
|
+
restarting. Inspired by Symphony's dynamic WORKFLOW.md reload pattern.
|
|
6
|
+
|
|
7
|
+
Components:
|
|
8
|
+
- ConfigReloadState: Thread-safe shared state holding current config
|
|
9
|
+
- ConfigFileWatcher: Daemon thread polling file mtimes for changes
|
|
10
|
+
|
|
11
|
+
What reloads dynamically:
|
|
12
|
+
- Agent prompt / system prompt supplement (always_do, ask_first, never_do, raw_content)
|
|
13
|
+
- Tool preferences (tooling, commands)
|
|
14
|
+
- Code style preferences (code_style)
|
|
15
|
+
|
|
16
|
+
What requires restart (not reloaded):
|
|
17
|
+
- Engine selection (react/plan)
|
|
18
|
+
- Workspace path
|
|
19
|
+
- Tech stack
|
|
20
|
+
|
|
21
|
+
This module is headless - no FastAPI or HTTP dependencies.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
import threading
|
|
26
|
+
from datetime import datetime, timezone
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Optional
|
|
29
|
+
|
|
30
|
+
from codeframe.core.agents_config import AgentPreferences, load_preferences
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _utc_now() -> datetime:
|
|
36
|
+
return datetime.now(timezone.utc)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Config files to watch, in priority order
|
|
40
|
+
_CONFIG_FILES = ("CODEFRAME.md", "AGENTS.md", "CLAUDE.md")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ConfigReloadState:
|
|
44
|
+
"""Thread-safe shared state for dynamic config reload.
|
|
45
|
+
|
|
46
|
+
Holds the last-known-good configuration and tracks reload history.
|
|
47
|
+
Accessed by the watcher thread (writes) and the conductor (reads).
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, initial_prefs: AgentPreferences) -> None:
|
|
51
|
+
self._prefs = initial_prefs
|
|
52
|
+
self._lock = threading.Lock()
|
|
53
|
+
self.last_reload_at: Optional[datetime] = None
|
|
54
|
+
self.last_error: Optional[str] = None
|
|
55
|
+
self.reload_timestamps: list[str] = []
|
|
56
|
+
|
|
57
|
+
def get_prefs(self) -> AgentPreferences:
|
|
58
|
+
with self._lock:
|
|
59
|
+
return self._prefs
|
|
60
|
+
|
|
61
|
+
def apply_reload(self, new_prefs: AgentPreferences, timestamp: datetime) -> None:
|
|
62
|
+
with self._lock:
|
|
63
|
+
self._prefs = new_prefs
|
|
64
|
+
self.last_reload_at = timestamp
|
|
65
|
+
self.last_error = None
|
|
66
|
+
self.reload_timestamps.append(timestamp.isoformat())
|
|
67
|
+
if len(self.reload_timestamps) > 100:
|
|
68
|
+
self.reload_timestamps = self.reload_timestamps[-100:]
|
|
69
|
+
|
|
70
|
+
def record_error(self, msg: str) -> None:
|
|
71
|
+
with self._lock:
|
|
72
|
+
self.last_error = msg
|
|
73
|
+
|
|
74
|
+
def has_reloaded_since(self, since: datetime) -> bool:
|
|
75
|
+
with self._lock:
|
|
76
|
+
return self.last_reload_at is not None and self.last_reload_at > since
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class ConfigFileWatcher:
|
|
80
|
+
"""Polling-based file watcher for config hot-reload.
|
|
81
|
+
|
|
82
|
+
Monitors CODEFRAME.md, AGENTS.md, and CLAUDE.md for mtime changes.
|
|
83
|
+
On change, re-parses and validates the config, updating shared state.
|
|
84
|
+
|
|
85
|
+
Follows the same daemon-thread pattern as StallMonitor.
|
|
86
|
+
|
|
87
|
+
Usage::
|
|
88
|
+
|
|
89
|
+
watcher = ConfigFileWatcher(workspace_path, poll_interval_s=2.0)
|
|
90
|
+
state = watcher.start(initial_prefs)
|
|
91
|
+
try:
|
|
92
|
+
# ... batch execution ...
|
|
93
|
+
# Check state.get_prefs() between tasks
|
|
94
|
+
finally:
|
|
95
|
+
watcher.stop()
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
workspace_path: Path,
|
|
101
|
+
poll_interval_s: float = 2.0,
|
|
102
|
+
) -> None:
|
|
103
|
+
self._workspace_path = workspace_path
|
|
104
|
+
self._poll_interval_s = poll_interval_s
|
|
105
|
+
self._stop_event = threading.Event()
|
|
106
|
+
self._thread: Optional[threading.Thread] = None
|
|
107
|
+
self._state: Optional[ConfigReloadState] = None
|
|
108
|
+
self._watched_mtimes: dict[Path, float] = {}
|
|
109
|
+
self._workspace_ref: Optional[object] = None # Cached workspace for event emission
|
|
110
|
+
|
|
111
|
+
def start(self, initial_prefs: AgentPreferences) -> ConfigReloadState:
|
|
112
|
+
"""Begin watching config files.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
initial_prefs: The current agent preferences to use as baseline.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Shared ConfigReloadState that the conductor can query.
|
|
119
|
+
"""
|
|
120
|
+
self.stop()
|
|
121
|
+
|
|
122
|
+
self._state = ConfigReloadState(initial_prefs)
|
|
123
|
+
self._stop_event.clear()
|
|
124
|
+
|
|
125
|
+
# Snapshot current mtimes
|
|
126
|
+
self._watched_mtimes = {}
|
|
127
|
+
for name in _CONFIG_FILES:
|
|
128
|
+
path = self._workspace_path / name
|
|
129
|
+
if path.is_file():
|
|
130
|
+
self._watched_mtimes[path] = path.stat().st_mtime
|
|
131
|
+
|
|
132
|
+
self._thread = threading.Thread(
|
|
133
|
+
target=self._poll_loop,
|
|
134
|
+
name="config-file-watcher",
|
|
135
|
+
daemon=True,
|
|
136
|
+
)
|
|
137
|
+
self._thread.start()
|
|
138
|
+
return self._state
|
|
139
|
+
|
|
140
|
+
def stop(self) -> None:
|
|
141
|
+
"""Stop the watcher thread."""
|
|
142
|
+
self._stop_event.set()
|
|
143
|
+
if self._thread is not None:
|
|
144
|
+
self._thread.join(timeout=2.0)
|
|
145
|
+
self._thread = None
|
|
146
|
+
|
|
147
|
+
def _poll_loop(self) -> None:
|
|
148
|
+
"""Daemon thread: check file mtimes at regular intervals."""
|
|
149
|
+
while not self._stop_event.wait(timeout=self._poll_interval_s):
|
|
150
|
+
self._check_for_changes()
|
|
151
|
+
|
|
152
|
+
def _check_for_changes(self) -> None:
|
|
153
|
+
"""Compare current mtimes against snapshots."""
|
|
154
|
+
changed = False
|
|
155
|
+
|
|
156
|
+
for name in _CONFIG_FILES:
|
|
157
|
+
path = self._workspace_path / name
|
|
158
|
+
if not path.is_file():
|
|
159
|
+
# File might have been created since start
|
|
160
|
+
if path not in self._watched_mtimes:
|
|
161
|
+
continue
|
|
162
|
+
# File was deleted — skip
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
current_mtime = path.stat().st_mtime
|
|
167
|
+
except OSError:
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
prev_mtime = self._watched_mtimes.get(path, 0.0)
|
|
171
|
+
if current_mtime > prev_mtime:
|
|
172
|
+
self._watched_mtimes[path] = current_mtime
|
|
173
|
+
changed = True
|
|
174
|
+
|
|
175
|
+
# Also detect newly created config files
|
|
176
|
+
for name in _CONFIG_FILES:
|
|
177
|
+
path = self._workspace_path / name
|
|
178
|
+
try:
|
|
179
|
+
if path.is_file() and path not in self._watched_mtimes:
|
|
180
|
+
self._watched_mtimes[path] = path.stat().st_mtime
|
|
181
|
+
changed = True
|
|
182
|
+
except OSError:
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
if changed:
|
|
186
|
+
self._reload()
|
|
187
|
+
|
|
188
|
+
def _reload(self) -> None:
|
|
189
|
+
"""Re-parse config files and update shared state."""
|
|
190
|
+
try:
|
|
191
|
+
new_prefs = load_preferences(self._workspace_path)
|
|
192
|
+
|
|
193
|
+
if not new_prefs.has_preferences() and not new_prefs.raw_content:
|
|
194
|
+
msg = "Reloaded config is empty — retaining previous config"
|
|
195
|
+
logger.warning(msg)
|
|
196
|
+
if self._state:
|
|
197
|
+
self._state.record_error(msg)
|
|
198
|
+
self._emit_reload_failed(msg)
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
now = _utc_now()
|
|
202
|
+
if self._state:
|
|
203
|
+
old_prefs = self._state.get_prefs()
|
|
204
|
+
self._state.apply_reload(new_prefs, now)
|
|
205
|
+
diff = self._build_diff_summary(old_prefs, new_prefs)
|
|
206
|
+
logger.info("Config reloaded: %s", diff)
|
|
207
|
+
self._emit_reload_success(diff)
|
|
208
|
+
|
|
209
|
+
except Exception as e:
|
|
210
|
+
msg = f"Config reload failed: {e}"
|
|
211
|
+
logger.warning(msg)
|
|
212
|
+
if self._state:
|
|
213
|
+
self._state.record_error(msg)
|
|
214
|
+
self._emit_reload_failed(msg)
|
|
215
|
+
|
|
216
|
+
def _build_diff_summary(
|
|
217
|
+
self, old: AgentPreferences, new: AgentPreferences
|
|
218
|
+
) -> str:
|
|
219
|
+
"""Build a human-readable summary of what changed."""
|
|
220
|
+
changes = []
|
|
221
|
+
if old.always_do != new.always_do:
|
|
222
|
+
changes.append(f"always_do: {len(old.always_do)} -> {len(new.always_do)} items")
|
|
223
|
+
if old.never_do != new.never_do:
|
|
224
|
+
changes.append(f"never_do: {len(old.never_do)} -> {len(new.never_do)} items")
|
|
225
|
+
if old.ask_first != new.ask_first:
|
|
226
|
+
changes.append(f"ask_first: {len(old.ask_first)} -> {len(new.ask_first)} items")
|
|
227
|
+
if old.tooling != new.tooling:
|
|
228
|
+
changes.append("tooling changed")
|
|
229
|
+
if old.commands != new.commands:
|
|
230
|
+
changes.append("commands changed")
|
|
231
|
+
if old.code_style != new.code_style:
|
|
232
|
+
changes.append("code_style changed")
|
|
233
|
+
return "; ".join(changes) if changes else "content changed"
|
|
234
|
+
|
|
235
|
+
def _get_workspace(self):
|
|
236
|
+
"""Get cached workspace reference for event emission."""
|
|
237
|
+
if self._workspace_ref is None:
|
|
238
|
+
from codeframe.core.workspace import get_workspace
|
|
239
|
+
self._workspace_ref = get_workspace(self._workspace_path)
|
|
240
|
+
return self._workspace_ref
|
|
241
|
+
|
|
242
|
+
def _emit_reload_success(self, diff_summary: str) -> None:
|
|
243
|
+
"""Emit CONFIG_RELOADED event (best-effort)."""
|
|
244
|
+
try:
|
|
245
|
+
from codeframe.core import events
|
|
246
|
+
|
|
247
|
+
events.emit_for_workspace(
|
|
248
|
+
self._get_workspace(),
|
|
249
|
+
events.EventType.CONFIG_RELOADED,
|
|
250
|
+
{"diff_summary": diff_summary},
|
|
251
|
+
print_event=True,
|
|
252
|
+
)
|
|
253
|
+
except Exception:
|
|
254
|
+
logger.debug("Failed to emit CONFIG_RELOADED event", exc_info=True)
|
|
255
|
+
|
|
256
|
+
def _emit_reload_failed(self, error_msg: str) -> None:
|
|
257
|
+
"""Emit CONFIG_RELOAD_FAILED event (best-effort)."""
|
|
258
|
+
try:
|
|
259
|
+
from codeframe.core import events
|
|
260
|
+
|
|
261
|
+
events.emit_for_workspace(
|
|
262
|
+
self._get_workspace(),
|
|
263
|
+
events.EventType.CONFIG_RELOAD_FAILED,
|
|
264
|
+
{"error": error_msg},
|
|
265
|
+
print_event=True,
|
|
266
|
+
)
|
|
267
|
+
except Exception:
|
|
268
|
+
logger.debug("Failed to emit CONFIG_RELOAD_FAILED event", exc_info=True)
|