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
codeframe/core/events.py
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
"""Event logging for CodeFRAME v2.
|
|
2
|
+
|
|
3
|
+
Provides an append-only event log for workspace activity. Events are:
|
|
4
|
+
- Stored durably in SQLite
|
|
5
|
+
- Printed to stdout for CLI visibility
|
|
6
|
+
- Available for tailing/streaming
|
|
7
|
+
|
|
8
|
+
This module is headless - no FastAPI or HTTP dependencies.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Iterator, Optional
|
|
16
|
+
|
|
17
|
+
from rich.console import Console
|
|
18
|
+
|
|
19
|
+
from codeframe.core.workspace import get_workspace, get_db_connection, Workspace
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _utc_now() -> datetime:
|
|
23
|
+
"""Get current UTC time as timezone-aware datetime."""
|
|
24
|
+
return datetime.now(timezone.utc)
|
|
25
|
+
|
|
26
|
+
console = Console()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Event type constants (for consistency)
|
|
30
|
+
class EventType:
|
|
31
|
+
"""Standard event types emitted by core modules."""
|
|
32
|
+
|
|
33
|
+
# Workspace events
|
|
34
|
+
WORKSPACE_INIT = "WORKSPACE_INIT"
|
|
35
|
+
|
|
36
|
+
# PRD events
|
|
37
|
+
PRD_ADDED = "PRD_ADDED"
|
|
38
|
+
PRD_UPDATED = "PRD_UPDATED"
|
|
39
|
+
PRD_DELETED = "PRD_DELETED"
|
|
40
|
+
|
|
41
|
+
# Task events
|
|
42
|
+
TASKS_GENERATED = "TASKS_GENERATED"
|
|
43
|
+
TASK_STATUS_CHANGED = "TASK_STATUS_CHANGED"
|
|
44
|
+
TASK_CREATED = "TASK_CREATED"
|
|
45
|
+
TASK_UPDATED = "TASK_UPDATED"
|
|
46
|
+
|
|
47
|
+
# Run/execution events
|
|
48
|
+
RUN_STARTED = "RUN_STARTED"
|
|
49
|
+
RUN_COMPLETED = "RUN_COMPLETED"
|
|
50
|
+
RUN_FAILED = "RUN_FAILED"
|
|
51
|
+
|
|
52
|
+
# Agent events
|
|
53
|
+
AGENT_STEP_STARTED = "AGENT_STEP_STARTED"
|
|
54
|
+
AGENT_STEP_COMPLETED = "AGENT_STEP_COMPLETED"
|
|
55
|
+
|
|
56
|
+
# ReactAgent lifecycle events
|
|
57
|
+
AGENT_STARTED = "AGENT_STARTED"
|
|
58
|
+
AGENT_COMPLETED = "AGENT_COMPLETED"
|
|
59
|
+
AGENT_FAILED = "AGENT_FAILED"
|
|
60
|
+
AGENT_ITERATION_STARTED = "AGENT_ITERATION_STARTED"
|
|
61
|
+
AGENT_ITERATION_COMPLETED = "AGENT_ITERATION_COMPLETED"
|
|
62
|
+
AGENT_TOOL_DISPATCHED = "AGENT_TOOL_DISPATCHED"
|
|
63
|
+
AGENT_TOOL_RESULT = "AGENT_TOOL_RESULT"
|
|
64
|
+
AGENT_COMPACTION = "AGENT_COMPACTION"
|
|
65
|
+
|
|
66
|
+
# ReactAgent budget and optimization events
|
|
67
|
+
AGENT_AUTOFIX_APPLIED = "AGENT_AUTOFIX_APPLIED"
|
|
68
|
+
AGENT_EARLY_TERMINATION = "AGENT_EARLY_TERMINATION"
|
|
69
|
+
AGENT_BUDGET_CALCULATED = "AGENT_BUDGET_CALCULATED"
|
|
70
|
+
AGENT_STALL_DETECTED = "AGENT_STALL_DETECTED"
|
|
71
|
+
|
|
72
|
+
# Blocker events
|
|
73
|
+
BLOCKER_CREATED = "BLOCKER_CREATED"
|
|
74
|
+
BLOCKER_ANSWERED = "BLOCKER_ANSWERED"
|
|
75
|
+
BLOCKER_RESOLVED = "BLOCKER_RESOLVED"
|
|
76
|
+
|
|
77
|
+
# Gate events
|
|
78
|
+
GATES_STARTED = "GATES_STARTED"
|
|
79
|
+
GATES_COMPLETED = "GATES_COMPLETED"
|
|
80
|
+
|
|
81
|
+
# Artifact events
|
|
82
|
+
PATCH_EXPORTED = "PATCH_EXPORTED"
|
|
83
|
+
COMMIT_CREATED = "COMMIT_CREATED"
|
|
84
|
+
FILES_MODIFIED = "FILES_MODIFIED"
|
|
85
|
+
|
|
86
|
+
# Checkpoint events
|
|
87
|
+
CHECKPOINT_CREATED = "CHECKPOINT_CREATED"
|
|
88
|
+
CHECKPOINT_RESTORED = "CHECKPOINT_RESTORED"
|
|
89
|
+
|
|
90
|
+
# Status events
|
|
91
|
+
STATUS_VIEWED = "STATUS_VIEWED"
|
|
92
|
+
SUMMARY_VIEWED = "SUMMARY_VIEWED"
|
|
93
|
+
|
|
94
|
+
# Batch execution events
|
|
95
|
+
BATCH_STARTED = "BATCH_STARTED"
|
|
96
|
+
BATCH_TASK_QUEUED = "BATCH_TASK_QUEUED"
|
|
97
|
+
BATCH_TASK_STARTED = "BATCH_TASK_STARTED"
|
|
98
|
+
BATCH_TASK_COMPLETED = "BATCH_TASK_COMPLETED"
|
|
99
|
+
BATCH_TASK_FAILED = "BATCH_TASK_FAILED"
|
|
100
|
+
BATCH_TASK_BLOCKED = "BATCH_TASK_BLOCKED"
|
|
101
|
+
BATCH_COMPLETED = "BATCH_COMPLETED"
|
|
102
|
+
BATCH_PARTIAL = "BATCH_PARTIAL"
|
|
103
|
+
BATCH_FAILED = "BATCH_FAILED"
|
|
104
|
+
BATCH_CANCELLED = "BATCH_CANCELLED"
|
|
105
|
+
BATCH_VALIDATION_FAILED = "BATCH_VALIDATION_FAILED"
|
|
106
|
+
|
|
107
|
+
# Hook events
|
|
108
|
+
HOOK_EXECUTED = "HOOK_EXECUTED"
|
|
109
|
+
HOOK_FAILED = "HOOK_FAILED"
|
|
110
|
+
|
|
111
|
+
# Reconciliation events
|
|
112
|
+
RECONCILIATION_STARTED = "RECONCILIATION_STARTED"
|
|
113
|
+
RECONCILIATION_TASK_SKIPPED = "RECONCILIATION_TASK_SKIPPED"
|
|
114
|
+
RECONCILIATION_TASK_REQUEUED = "RECONCILIATION_TASK_REQUEUED"
|
|
115
|
+
RECONCILIATION_ERROR = "RECONCILIATION_ERROR"
|
|
116
|
+
|
|
117
|
+
# Config reload events
|
|
118
|
+
CONFIG_RELOADED = "CONFIG_RELOADED"
|
|
119
|
+
CONFIG_RELOAD_FAILED = "CONFIG_RELOAD_FAILED"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass
|
|
123
|
+
class Event:
|
|
124
|
+
"""Represents a recorded event.
|
|
125
|
+
|
|
126
|
+
Attributes:
|
|
127
|
+
id: Auto-incremented event ID
|
|
128
|
+
workspace_id: Workspace this event belongs to
|
|
129
|
+
event_type: Type of event (see EventType)
|
|
130
|
+
payload: JSON-serializable event data
|
|
131
|
+
created_at: When the event occurred
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
id: int
|
|
135
|
+
workspace_id: str
|
|
136
|
+
event_type: str
|
|
137
|
+
payload: dict[str, Any]
|
|
138
|
+
created_at: datetime
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def emit(
|
|
142
|
+
workspace_id: str,
|
|
143
|
+
event_type: str,
|
|
144
|
+
payload: Optional[dict[str, Any]] = None,
|
|
145
|
+
*,
|
|
146
|
+
repo_path: Optional[Path] = None,
|
|
147
|
+
print_event: bool = True,
|
|
148
|
+
) -> Event:
|
|
149
|
+
"""Emit an event to the event log.
|
|
150
|
+
|
|
151
|
+
Events are stored durably and optionally printed to console.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
workspace_id: Workspace ID to emit event for
|
|
155
|
+
event_type: Type of event (use EventType constants)
|
|
156
|
+
payload: Optional event data (must be JSON-serializable)
|
|
157
|
+
repo_path: Optional repo path (used to find workspace if not cached)
|
|
158
|
+
print_event: Whether to print event to console (default True)
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
The created Event object
|
|
162
|
+
"""
|
|
163
|
+
payload = payload or {}
|
|
164
|
+
now = _utc_now().isoformat()
|
|
165
|
+
payload_json = json.dumps(payload)
|
|
166
|
+
|
|
167
|
+
# Find workspace database
|
|
168
|
+
# For now, we need the repo_path to find the DB. In future, we could cache this.
|
|
169
|
+
if repo_path is None:
|
|
170
|
+
# Try current directory
|
|
171
|
+
repo_path = Path.cwd()
|
|
172
|
+
|
|
173
|
+
workspace = get_workspace(repo_path)
|
|
174
|
+
conn = get_db_connection(workspace)
|
|
175
|
+
try:
|
|
176
|
+
cursor = conn.cursor()
|
|
177
|
+
cursor.execute(
|
|
178
|
+
"INSERT INTO events (workspace_id, event_type, payload, created_at) VALUES (?, ?, ?, ?)",
|
|
179
|
+
(workspace_id, event_type, payload_json, now),
|
|
180
|
+
)
|
|
181
|
+
event_id = cursor.lastrowid
|
|
182
|
+
conn.commit()
|
|
183
|
+
finally:
|
|
184
|
+
conn.close()
|
|
185
|
+
|
|
186
|
+
event = Event(
|
|
187
|
+
id=event_id,
|
|
188
|
+
workspace_id=workspace_id,
|
|
189
|
+
event_type=event_type,
|
|
190
|
+
payload=payload,
|
|
191
|
+
created_at=datetime.fromisoformat(now),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if print_event:
|
|
195
|
+
_print_event(event)
|
|
196
|
+
|
|
197
|
+
return event
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def emit_for_workspace(
|
|
201
|
+
workspace: Workspace,
|
|
202
|
+
event_type: str,
|
|
203
|
+
payload: Optional[dict[str, Any]] = None,
|
|
204
|
+
*,
|
|
205
|
+
print_event: bool = True,
|
|
206
|
+
) -> Event:
|
|
207
|
+
"""Emit an event using a Workspace object directly.
|
|
208
|
+
|
|
209
|
+
Preferred when you already have a Workspace reference.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
workspace: Workspace object
|
|
213
|
+
event_type: Type of event
|
|
214
|
+
payload: Optional event data
|
|
215
|
+
print_event: Whether to print event to console
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
The created Event object
|
|
219
|
+
"""
|
|
220
|
+
payload = payload or {}
|
|
221
|
+
now = _utc_now().isoformat()
|
|
222
|
+
payload_json = json.dumps(payload)
|
|
223
|
+
|
|
224
|
+
conn = get_db_connection(workspace)
|
|
225
|
+
try:
|
|
226
|
+
cursor = conn.cursor()
|
|
227
|
+
cursor.execute(
|
|
228
|
+
"INSERT INTO events (workspace_id, event_type, payload, created_at) VALUES (?, ?, ?, ?)",
|
|
229
|
+
(workspace.id, event_type, payload_json, now),
|
|
230
|
+
)
|
|
231
|
+
event_id = cursor.lastrowid
|
|
232
|
+
conn.commit()
|
|
233
|
+
finally:
|
|
234
|
+
conn.close()
|
|
235
|
+
|
|
236
|
+
event = Event(
|
|
237
|
+
id=event_id,
|
|
238
|
+
workspace_id=workspace.id,
|
|
239
|
+
event_type=event_type,
|
|
240
|
+
payload=payload,
|
|
241
|
+
created_at=datetime.fromisoformat(now),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
if print_event:
|
|
245
|
+
_print_event(event)
|
|
246
|
+
|
|
247
|
+
return event
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def list_recent(
|
|
251
|
+
workspace: Workspace,
|
|
252
|
+
limit: int = 20,
|
|
253
|
+
since_id: Optional[int] = None,
|
|
254
|
+
) -> list[Event]:
|
|
255
|
+
"""List recent events for a workspace.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
workspace: Workspace to query
|
|
259
|
+
limit: Maximum number of events to return
|
|
260
|
+
since_id: Only return events after this ID (for pagination)
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
List of Event objects, newest first
|
|
264
|
+
"""
|
|
265
|
+
conn = get_db_connection(workspace)
|
|
266
|
+
try:
|
|
267
|
+
cursor = conn.cursor()
|
|
268
|
+
|
|
269
|
+
if since_id:
|
|
270
|
+
cursor.execute(
|
|
271
|
+
"""
|
|
272
|
+
SELECT id, workspace_id, event_type, payload, created_at
|
|
273
|
+
FROM events
|
|
274
|
+
WHERE workspace_id = ? AND id > ?
|
|
275
|
+
ORDER BY id DESC
|
|
276
|
+
LIMIT ?
|
|
277
|
+
""",
|
|
278
|
+
(workspace.id, since_id, limit),
|
|
279
|
+
)
|
|
280
|
+
else:
|
|
281
|
+
cursor.execute(
|
|
282
|
+
"""
|
|
283
|
+
SELECT id, workspace_id, event_type, payload, created_at
|
|
284
|
+
FROM events
|
|
285
|
+
WHERE workspace_id = ?
|
|
286
|
+
ORDER BY id DESC
|
|
287
|
+
LIMIT ?
|
|
288
|
+
""",
|
|
289
|
+
(workspace.id, limit),
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
rows = cursor.fetchall()
|
|
293
|
+
finally:
|
|
294
|
+
conn.close()
|
|
295
|
+
|
|
296
|
+
return [
|
|
297
|
+
Event(
|
|
298
|
+
id=row[0],
|
|
299
|
+
workspace_id=row[1],
|
|
300
|
+
event_type=row[2],
|
|
301
|
+
payload=json.loads(row[3]) if row[3] else {},
|
|
302
|
+
created_at=datetime.fromisoformat(row[4]),
|
|
303
|
+
)
|
|
304
|
+
for row in rows
|
|
305
|
+
]
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def tail(
|
|
309
|
+
workspace: Workspace,
|
|
310
|
+
since_id: int = 0,
|
|
311
|
+
) -> Iterator[Event]:
|
|
312
|
+
"""Tail the event log, yielding new events.
|
|
313
|
+
|
|
314
|
+
This is a generator that yields events as they appear.
|
|
315
|
+
Note: This is a simple polling implementation. For real-time,
|
|
316
|
+
consider using a file watcher or database triggers.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
workspace: Workspace to tail
|
|
320
|
+
since_id: Start after this event ID
|
|
321
|
+
|
|
322
|
+
Yields:
|
|
323
|
+
Event objects as they are recorded
|
|
324
|
+
"""
|
|
325
|
+
import time
|
|
326
|
+
|
|
327
|
+
last_id = since_id
|
|
328
|
+
|
|
329
|
+
while True:
|
|
330
|
+
events = list_recent(workspace, limit=50, since_id=last_id)
|
|
331
|
+
|
|
332
|
+
# Events are returned newest-first, so reverse for chronological order
|
|
333
|
+
for event in reversed(events):
|
|
334
|
+
if event.id > last_id:
|
|
335
|
+
last_id = event.id
|
|
336
|
+
yield event
|
|
337
|
+
|
|
338
|
+
time.sleep(0.5) # Poll interval
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _print_event(event: Event) -> None:
|
|
342
|
+
"""Print an event to the console in a readable format."""
|
|
343
|
+
timestamp = event.created_at.strftime("%H:%M:%S")
|
|
344
|
+
type_color = _get_event_color(event.event_type)
|
|
345
|
+
|
|
346
|
+
console.print(
|
|
347
|
+
f"[dim]{timestamp}[/dim] [{type_color}]{event.event_type}[/{type_color}]",
|
|
348
|
+
end="",
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
# Print key payload items if present
|
|
352
|
+
if event.payload:
|
|
353
|
+
items = []
|
|
354
|
+
for key in ["path", "task_id", "status", "name", "title"]:
|
|
355
|
+
if key in event.payload:
|
|
356
|
+
items.append(f"{key}={event.payload[key]}")
|
|
357
|
+
if items:
|
|
358
|
+
console.print(f" [dim]{' '.join(items)}[/dim]")
|
|
359
|
+
else:
|
|
360
|
+
console.print()
|
|
361
|
+
else:
|
|
362
|
+
console.print()
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _get_event_color(event_type: str) -> str:
|
|
366
|
+
"""Get the Rich color for an event type."""
|
|
367
|
+
if "ERROR" in event_type or "FAILED" in event_type:
|
|
368
|
+
return "red"
|
|
369
|
+
if "COMPLETED" in event_type or "CREATED" in event_type:
|
|
370
|
+
return "green"
|
|
371
|
+
if "STARTED" in event_type or "INIT" in event_type:
|
|
372
|
+
return "blue"
|
|
373
|
+
if "BLOCKED" in event_type or "BLOCKER" in event_type:
|
|
374
|
+
return "yellow"
|
|
375
|
+
return "cyan"
|