cct-cli 0.7.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.
- calc_terminal/__init__.py +14 -0
- calc_terminal/__main__.py +14 -0
- calc_terminal/activity.py +1334 -0
- calc_terminal/agent.py +3387 -0
- calc_terminal/agent_runtime.py +519 -0
- calc_terminal/ai_context.py +447 -0
- calc_terminal/ai_modes.py +752 -0
- calc_terminal/ai_personalization.py +286 -0
- calc_terminal/ai_preview_feedback.py +213 -0
- calc_terminal/aicore.py +2572 -0
- calc_terminal/anim.py +367 -0
- calc_terminal/app.py +3685 -0
- calc_terminal/art.py +639 -0
- calc_terminal/atomsim.py +368 -0
- calc_terminal/attachments.py +743 -0
- calc_terminal/benchmark_system.py +414 -0
- calc_terminal/browser/__init__.py +36 -0
- calc_terminal/browser/browser_state.py +346 -0
- calc_terminal/browser/devserver.py +176 -0
- calc_terminal/browser/engine.py +494 -0
- calc_terminal/browser/navigation.py +84 -0
- calc_terminal/browser/preview.py +429 -0
- calc_terminal/browser/preview_entry.py +95 -0
- calc_terminal/browser/project_detector.py +144 -0
- calc_terminal/browser/server.py +449 -0
- calc_terminal/browser/state.py +75 -0
- calc_terminal/browser/watcher.py +99 -0
- calc_terminal/browser_gui/__init__.py +1 -0
- calc_terminal/browser_gui/__main__.py +3 -0
- calc_terminal/browser_gui/launcher.py +173 -0
- calc_terminal/browser_gui/playwright_browser.py +117 -0
- calc_terminal/browser_gui/qt_browser.py +1501 -0
- calc_terminal/browser_gui/webview_browser.py +57 -0
- calc_terminal/capabilities/__init__.py +35 -0
- calc_terminal/capabilities/adapters/__init__.py +33 -0
- calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
- calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
- calc_terminal/capabilities/adapters/filesystem.py +206 -0
- calc_terminal/capabilities/adapters/git_adapter.py +202 -0
- calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
- calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
- calc_terminal/capabilities/adapters/platforms.py +200 -0
- calc_terminal/capabilities/adapters/python_exec.py +93 -0
- calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
- calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
- calc_terminal/capabilities/adapters/structural_bio.py +161 -0
- calc_terminal/capabilities/adapters/terminal.py +99 -0
- calc_terminal/capabilities/bus.py +178 -0
- calc_terminal/capabilities/discovery.py +207 -0
- calc_terminal/capabilities/schema.py +221 -0
- calc_terminal/cat.ico +0 -0
- calc_terminal/cat_browser.py +2018 -0
- calc_terminal/chat_store.py +703 -0
- calc_terminal/cli.py +1178 -0
- calc_terminal/code_editor.py +640 -0
- calc_terminal/collaboration.py +723 -0
- calc_terminal/commands_data.py +139 -0
- calc_terminal/compatibility_engine.py +352 -0
- calc_terminal/compute/__init__.py +31 -0
- calc_terminal/compute/fabric.py +350 -0
- calc_terminal/config.py +227 -0
- calc_terminal/core/__init__.py +41 -0
- calc_terminal/core/checkpoint.py +156 -0
- calc_terminal/core/input/__init__.py +45 -0
- calc_terminal/core/mode_registry.py +300 -0
- calc_terminal/core/project_graph.py +172 -0
- calc_terminal/core/recovery.py +129 -0
- calc_terminal/core/security_layer.py +112 -0
- calc_terminal/core/task_graph.py +202 -0
- calc_terminal/core/unified_runtime.py +184 -0
- calc_terminal/core/verification.py +257 -0
- calc_terminal/customization.py +1566 -0
- calc_terminal/derivations.py +153 -0
- calc_terminal/device_control.py +263 -0
- calc_terminal/diagnostics/__init__.py +27 -0
- calc_terminal/diagnostics/doctor_engine.py +382 -0
- calc_terminal/diagnostics/self_test.py +247 -0
- calc_terminal/doctor.py +519 -0
- calc_terminal/easter_eggs.py +274 -0
- calc_terminal/editor/__init__.py +1 -0
- calc_terminal/editor/actions.py +263 -0
- calc_terminal/editor/commands.py +160 -0
- calc_terminal/editor/shortcuts.py +226 -0
- calc_terminal/engine.py +259 -0
- calc_terminal/errors.py +120 -0
- calc_terminal/event_stream.py +146 -0
- calc_terminal/eventbus.py +133 -0
- calc_terminal/extensions.py +733 -0
- calc_terminal/fallback_cli.py +1321 -0
- calc_terminal/first_run.py +265 -0
- calc_terminal/fomoji_auth.py +1043 -0
- calc_terminal/formulas.py +82 -0
- calc_terminal/fs_cache.py +121 -0
- calc_terminal/fs_watcher.py +277 -0
- calc_terminal/game.py +193 -0
- calc_terminal/gen1.py +5 -0
- calc_terminal/generators.py +245 -0
- calc_terminal/gestures/__init__.py +42 -0
- calc_terminal/gestures/bindings.py +175 -0
- calc_terminal/gestures/manager.py +477 -0
- calc_terminal/goodbye.py +363 -0
- calc_terminal/gpu3d.py +290 -0
- calc_terminal/graphs.py +358 -0
- calc_terminal/hardware_analyzer.py +440 -0
- calc_terminal/host/__init__.py +30 -0
- calc_terminal/host/browser_manager.py +187 -0
- calc_terminal/host/desktop.py +1386 -0
- calc_terminal/host/launcher.py +395 -0
- calc_terminal/host/terminal.py +279 -0
- calc_terminal/identity.py +216 -0
- calc_terminal/input/__init__.py +54 -0
- calc_terminal/input/capabilities.py +258 -0
- calc_terminal/input/focus.py +87 -0
- calc_terminal/input/gestures.py +64 -0
- calc_terminal/input/pointer.py +114 -0
- calc_terminal/input/touch.py +345 -0
- calc_terminal/keys.py +84 -0
- calc_terminal/live_automation.py +165 -0
- calc_terminal/mathtext.py +433 -0
- calc_terminal/mcp.py +386 -0
- calc_terminal/memory.py +337 -0
- calc_terminal/memory_v2.py +479 -0
- calc_terminal/metrics.py +333 -0
- calc_terminal/mode_detection.py +146 -0
- calc_terminal/model.py +2431 -0
- calc_terminal/model_router.py +665 -0
- calc_terminal/models/__init__.py +0 -0
- calc_terminal/models/active_state.py +187 -0
- calc_terminal/models/dynamic_registry.py +584 -0
- calc_terminal/models/manager.py +781 -0
- calc_terminal/models/model_metadata.json +3526 -0
- calc_terminal/models/profiles.py +194 -0
- calc_terminal/models/registry.py +265 -0
- calc_terminal/models/schema.py +197 -0
- calc_terminal/models/validator.py +287 -0
- calc_terminal/models/verification_engine.py +368 -0
- calc_terminal/native_picker.py +215 -0
- calc_terminal/ollama_catalog.py +279 -0
- calc_terminal/ollama_download.py +233 -0
- calc_terminal/orchestrator.py +304 -0
- calc_terminal/package_research.py +322 -0
- calc_terminal/packages.py +1024 -0
- calc_terminal/pc_specs.py +116 -0
- calc_terminal/permissions.py +334 -0
- calc_terminal/pet.py +106 -0
- calc_terminal/pipeline.py +505 -0
- calc_terminal/platform/__init__.py +491 -0
- calc_terminal/platform/desktop.py +491 -0
- calc_terminal/platform/web.py +781 -0
- calc_terminal/preview/__init__.py +1 -0
- calc_terminal/preview/dev_server.py +303 -0
- calc_terminal/preview/diagnostics.py +131 -0
- calc_terminal/preview/live_reload.py +66 -0
- calc_terminal/preview/manager.py +129 -0
- calc_terminal/project_stats.py +209 -0
- calc_terminal/projects.py +328 -0
- calc_terminal/providers/__init__.py +0 -0
- calc_terminal/providers/adapters/__init__.py +80 -0
- calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
- calc_terminal/providers/adapters/base.py +106 -0
- calc_terminal/providers/adapters/chinese_adapters.py +420 -0
- calc_terminal/providers/adapters/gemini_adapter.py +101 -0
- calc_terminal/providers/adapters/ollama_adapter.py +83 -0
- calc_terminal/providers/adapters/openai_adapter.py +159 -0
- calc_terminal/providers/adapters/other_adapters.py +246 -0
- calc_terminal/providers/anthropic_provider.py +172 -0
- calc_terminal/providers/auto_update.py +416 -0
- calc_terminal/providers/base_provider.py +105 -0
- calc_terminal/providers/discovery_manager.py +207 -0
- calc_terminal/providers/gemini_provider.py +178 -0
- calc_terminal/providers/lifecycle.py +767 -0
- calc_terminal/providers/ollama_adapter.py +707 -0
- calc_terminal/providers/openai_provider.py +254 -0
- calc_terminal/providers/provider_manager.py +1827 -0
- calc_terminal/providers/providers.json +4075 -0
- calc_terminal/reactionsim.py +279 -0
- calc_terminal/registry.py +337 -0
- calc_terminal/report.py +162 -0
- calc_terminal/research/__init__.py +45 -0
- calc_terminal/research/artifact_intel.py +126 -0
- calc_terminal/research/data_lineage.py +123 -0
- calc_terminal/research/experiment_ledger.py +303 -0
- calc_terminal/research/reproducibility.py +131 -0
- calc_terminal/resilience/__init__.py +47 -0
- calc_terminal/resilience/agent_state.py +121 -0
- calc_terminal/resilience/capability_matcher.py +174 -0
- calc_terminal/resilience/circuit_breaker.py +158 -0
- calc_terminal/resilience/failover_engine.py +230 -0
- calc_terminal/resilience/health_monitor.py +192 -0
- calc_terminal/resilience/ollama_adapter.py +125 -0
- calc_terminal/resilience/orchestrator.py +312 -0
- calc_terminal/resilience/types.py +134 -0
- calc_terminal/sandbox.py +98 -0
- calc_terminal/scires.py +558 -0
- calc_terminal/security_scanner.py +126 -0
- calc_terminal/session.py +294 -0
- calc_terminal/sim3d.py +206 -0
- calc_terminal/solver.py +276 -0
- calc_terminal/sound.py +127 -0
- calc_terminal/task_reports.py +287 -0
- calc_terminal/terminal_host.py +201 -0
- calc_terminal/terminal_identity.py +411 -0
- calc_terminal/test_ai_mode_reliability.py +344 -0
- calc_terminal/test_browser.py +368 -0
- calc_terminal/test_code_editor_upgrade.py +485 -0
- calc_terminal/test_customization.py +1148 -0
- calc_terminal/test_customization_ui.py +612 -0
- calc_terminal/test_dynamic_registry.py +304 -0
- calc_terminal/test_extensions.py +436 -0
- calc_terminal/test_overhaul.py +557 -0
- calc_terminal/test_project_detect.py +255 -0
- calc_terminal/test_root_cause_fix.py +527 -0
- calc_terminal/test_stability.py +532 -0
- calc_terminal/test_terminal_identity.py +132 -0
- calc_terminal/test_v079_speed.py +460 -0
- calc_terminal/theme.py +1107 -0
- calc_terminal/timeline.py +139 -0
- calc_terminal/todos.py +246 -0
- calc_terminal/tool_call_normalizer.py +419 -0
- calc_terminal/tui.py +104 -0
- calc_terminal/ui/__init__.py +8 -0
- calc_terminal/ui/activity_panel.py +231 -0
- calc_terminal/ui/activity_stream_panel.py +238 -0
- calc_terminal/ui/animations.py +122 -0
- calc_terminal/ui/app.py +7271 -0
- calc_terminal/ui/attach_panel.py +597 -0
- calc_terminal/ui/attachments.py +424 -0
- calc_terminal/ui/backup_panel.py +810 -0
- calc_terminal/ui/browser_shell.py +887 -0
- calc_terminal/ui/cat_agent.py +357 -0
- calc_terminal/ui/chats_panel.py +899 -0
- calc_terminal/ui/command_palette.py +125 -0
- calc_terminal/ui/command_palette_modal.py +166 -0
- calc_terminal/ui/composer.py +1141 -0
- calc_terminal/ui/context_menu.py +197 -0
- calc_terminal/ui/conversation.py +1435 -0
- calc_terminal/ui/customization_panel.py +1229 -0
- calc_terminal/ui/dashboard.py +404 -0
- calc_terminal/ui/design_system.py +557 -0
- calc_terminal/ui/diff_panel.py +213 -0
- calc_terminal/ui/editor.py +2102 -0
- calc_terminal/ui/empty_state.py +302 -0
- calc_terminal/ui/events.py +487 -0
- calc_terminal/ui/extensions_panel.py +815 -0
- calc_terminal/ui/footer.py +166 -0
- calc_terminal/ui/gestures_panel.py +383 -0
- calc_terminal/ui/goodbye_screen.py +100 -0
- calc_terminal/ui/header.py +1034 -0
- calc_terminal/ui/help_panel.py +254 -0
- calc_terminal/ui/live_activities.py +914 -0
- calc_terminal/ui/mcp_panel.py +570 -0
- calc_terminal/ui/memory_center.py +524 -0
- calc_terminal/ui/mode_colors_panel.py +525 -0
- calc_terminal/ui/nav_screens.py +747 -0
- calc_terminal/ui/ollama_panel.py +536 -0
- calc_terminal/ui/palette.py +221 -0
- calc_terminal/ui/permission_panel.py +269 -0
- calc_terminal/ui/personalization_panel.py +517 -0
- calc_terminal/ui/personalize_center.py +1568 -0
- calc_terminal/ui/preview_panel.py +441 -0
- calc_terminal/ui/resizers.py +402 -0
- calc_terminal/ui/sidebar.py +1285 -0
- calc_terminal/ui/statusbar.py +168 -0
- calc_terminal/ui/theme_css.py +1396 -0
- calc_terminal/ui/thinking.py +226 -0
- calc_terminal/ui/timeline_panel.py +102 -0
- calc_terminal/ui/todo_panel.py +193 -0
- calc_terminal/ui/viewport.py +136 -0
- calc_terminal/ui/vision_panel.py +489 -0
- calc_terminal/ui/welcome_modal.py +343 -0
- calc_terminal/ui/widgets.py +160 -0
- calc_terminal/ui/workspace.py +831 -0
- calc_terminal/viewers/__init__.py +1 -0
- calc_terminal/viewers/document_viewer.py +252 -0
- calc_terminal/viewers/image_viewer.py +241 -0
- calc_terminal/viewers/pdf_viewer.py +203 -0
- calc_terminal/viewers/presentation_viewer.py +164 -0
- calc_terminal/viewers/registry.py +120 -0
- calc_terminal/viewers/spreadsheet_viewer.py +204 -0
- calc_terminal/vision/__init__.py +89 -0
- calc_terminal/vision/analysis.py +194 -0
- calc_terminal/vision/annotations.py +297 -0
- calc_terminal/vision/capture.py +171 -0
- calc_terminal/vision/context.py +231 -0
- calc_terminal/vision/correlation.py +169 -0
- calc_terminal/vision/cursor.py +258 -0
- calc_terminal/vision/events.py +66 -0
- calc_terminal/vision/frame_pipeline.py +259 -0
- calc_terminal/vision/priority.py +218 -0
- calc_terminal/vision/provider.py +180 -0
- calc_terminal/vision/safety.py +149 -0
- calc_terminal/vision/session.py +281 -0
- calc_terminal/vision/verify.py +162 -0
- calc_terminal/vision.py +514 -0
- calc_terminal/vscode_integration.py +113 -0
- calc_terminal/web/__init__.py +8 -0
- calc_terminal/web/cat_runtime.py +710 -0
- calc_terminal/web/server.py +2891 -0
- calc_terminal/web/static/css/app.css +3152 -0
- calc_terminal/web/static/icons/badge-72.png +0 -0
- calc_terminal/web/static/icons/cat.ico +0 -0
- calc_terminal/web/static/icons/icon-128.png +0 -0
- calc_terminal/web/static/icons/icon-144.png +0 -0
- calc_terminal/web/static/icons/icon-152.png +0 -0
- calc_terminal/web/static/icons/icon-192.png +0 -0
- calc_terminal/web/static/icons/icon-384.png +0 -0
- calc_terminal/web/static/icons/icon-512.png +0 -0
- calc_terminal/web/static/icons/icon-72.png +0 -0
- calc_terminal/web/static/icons/icon-96.png +0 -0
- calc_terminal/web/static/icons/icon.svg +34 -0
- calc_terminal/web/static/icons/new-project.png +0 -0
- calc_terminal/web/static/icons/open-project.png +0 -0
- calc_terminal/web/static/index.html +734 -0
- calc_terminal/web/static/js/app.js +2403 -0
- calc_terminal/web/static/manifest.json +88 -0
- calc_terminal/web/static/sw.js +230 -0
- calc_terminal/workflow_engine.py +769 -0
- calc_terminal/workspace.py +593 -0
- calc_terminal/workspace_index.py +385 -0
- cct_cli-0.7.9.0.dist-info/METADATA +210 -0
- cct_cli-0.7.9.0.dist-info/RECORD +325 -0
- cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
- cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
- cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
- cct_cli-0.7.9.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CAT Workflow Engine — Real-Time Live Activities + Autonomous Workflow Engine.
|
|
3
|
+
|
|
4
|
+
This is a real execution-observability and autonomous workflow system.
|
|
5
|
+
Every Live Activity is generated from real CAT execution events, real tool calls,
|
|
6
|
+
real browser actions, real filesystem operations, real code execution,
|
|
7
|
+
real application interactions, and real verification results.
|
|
8
|
+
The UI never invents activity merely to make the AI appear busy.
|
|
9
|
+
|
|
10
|
+
Works across:
|
|
11
|
+
- Cloud AI models
|
|
12
|
+
- Local AI models
|
|
13
|
+
- Ollama models
|
|
14
|
+
- Small / weak models
|
|
15
|
+
- Large reasoning models
|
|
16
|
+
- CAT's own agent workflows (Notebook, Agent, Build, Plan, Debug, Research)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import logging
|
|
23
|
+
import os
|
|
24
|
+
import re
|
|
25
|
+
import threading
|
|
26
|
+
import time
|
|
27
|
+
import uuid
|
|
28
|
+
from dataclasses import dataclass, field, asdict
|
|
29
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
30
|
+
|
|
31
|
+
from . import activity as act_mod
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger("cct.workflow")
|
|
34
|
+
|
|
35
|
+
# ── 1. Canonical Activity States (Spec Section 4) ───────────────────────────
|
|
36
|
+
STATE_QUEUED = "QUEUED"
|
|
37
|
+
STATE_RUNNING = "RUNNING"
|
|
38
|
+
STATE_WAITING = "WAITING"
|
|
39
|
+
STATE_SUCCEEDED = "SUCCEEDED"
|
|
40
|
+
STATE_FAILED = "FAILED"
|
|
41
|
+
STATE_SKIPPED = "SKIPPED"
|
|
42
|
+
STATE_CANCELLED = "CANCELLED"
|
|
43
|
+
STATE_RETRYING = "RETRYING"
|
|
44
|
+
STATE_BLOCKED = "BLOCKED"
|
|
45
|
+
STATE_VERIFYING = "VERIFYING"
|
|
46
|
+
|
|
47
|
+
ALL_STATES = frozenset({
|
|
48
|
+
STATE_QUEUED, STATE_RUNNING, STATE_WAITING, STATE_SUCCEEDED, STATE_FAILED,
|
|
49
|
+
STATE_SKIPPED, STATE_CANCELLED, STATE_RETRYING, STATE_BLOCKED, STATE_VERIFYING
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
# State to icon mapping per spec:
|
|
53
|
+
# ● Running, ✓ Completed, ✗ Failed, ↻ Retrying, ⚠ Blocked, ○ Queued, ⟳ Verifying
|
|
54
|
+
STATE_SYMBOLS = {
|
|
55
|
+
STATE_QUEUED: "○",
|
|
56
|
+
STATE_RUNNING: "●",
|
|
57
|
+
STATE_WAITING: "⏳",
|
|
58
|
+
STATE_SUCCEEDED: "✓",
|
|
59
|
+
STATE_FAILED: "✗",
|
|
60
|
+
STATE_SKIPPED: "⊘",
|
|
61
|
+
STATE_CANCELLED: "⊘",
|
|
62
|
+
STATE_RETRYING: "↻",
|
|
63
|
+
STATE_BLOCKED: "⚠",
|
|
64
|
+
STATE_VERIFYING: "⟳",
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# ── 2. Canonical Event Types (Spec Section 1 & 2) ───────────────────────────
|
|
68
|
+
EVENT_AGENT_STARTED = "agent.started"
|
|
69
|
+
EVENT_TASK_PLANNING = "task.planning"
|
|
70
|
+
EVENT_TOOL_STARTED = "tool.started"
|
|
71
|
+
EVENT_TOOL_PROGRESS = "tool.progress"
|
|
72
|
+
EVENT_TOOL_COMPLETED = "tool.completed"
|
|
73
|
+
EVENT_BROWSER_STARTED = "browser.started"
|
|
74
|
+
EVENT_BROWSER_NAVIGATION = "browser.navigation"
|
|
75
|
+
EVENT_BROWSER_ELEMENT_DETECTED = "browser.element_detected"
|
|
76
|
+
EVENT_BROWSER_CLICK = "browser.click"
|
|
77
|
+
EVENT_BROWSER_INPUT = "browser.input"
|
|
78
|
+
EVENT_BROWSER_SCREENSHOT = "browser.screenshot"
|
|
79
|
+
EVENT_BROWSER_VALIDATION = "browser.validation"
|
|
80
|
+
EVENT_FILESYSTEM_READ = "filesystem.read"
|
|
81
|
+
EVENT_FILESYSTEM_WRITE = "filesystem.write"
|
|
82
|
+
EVENT_PROCESS_STARTED = "process.started"
|
|
83
|
+
EVENT_PROCESS_OUTPUT = "process.output"
|
|
84
|
+
EVENT_TEST_STARTED = "test.started"
|
|
85
|
+
EVENT_TEST_FAILED = "test.failed"
|
|
86
|
+
EVENT_TEST_PASSED = "test.passed"
|
|
87
|
+
EVENT_BUG_DETECTED = "bug.detected"
|
|
88
|
+
EVENT_BUG_FIXED = "bug.fixed"
|
|
89
|
+
EVENT_VERIFICATION_STARTED = "verification.started"
|
|
90
|
+
EVENT_VERIFICATION_COMPLETED = "verification.completed"
|
|
91
|
+
EVENT_AGENT_COMPLETED = "agent.completed"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class ExecutionEvent:
|
|
96
|
+
"""Structured telemetry event emitted by the CAT Execution Engine.
|
|
97
|
+
Uses monotonic timing internally so ordering remains 100% reliable.
|
|
98
|
+
"""
|
|
99
|
+
event_id: str
|
|
100
|
+
trace_id: str
|
|
101
|
+
parent_id: str
|
|
102
|
+
event_type: str
|
|
103
|
+
status: str
|
|
104
|
+
source: str
|
|
105
|
+
tool: str
|
|
106
|
+
description: str
|
|
107
|
+
timestamp: float = field(default_factory=time.perf_counter)
|
|
108
|
+
wall_time: float = field(default_factory=time.time)
|
|
109
|
+
progress: Optional[float] = None
|
|
110
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
111
|
+
duration_ms: int = 0
|
|
112
|
+
|
|
113
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
114
|
+
return asdict(self)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ── 3. CAT Live Activity Event Bus ──────────────────────────────────────────
|
|
118
|
+
class ExecutionEventBus:
|
|
119
|
+
"""Central event bus connecting AI Models, Agent Runtime, Tool Executors,
|
|
120
|
+
Live Activity State Manager, and the Live Activities UI.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
def __init__(self):
|
|
124
|
+
self._subscribers: List[Callable[[ExecutionEvent], None]] = []
|
|
125
|
+
self._lock = threading.RLock()
|
|
126
|
+
self._events: List[ExecutionEvent] = []
|
|
127
|
+
self._max_history = 1000
|
|
128
|
+
|
|
129
|
+
def subscribe(self, callback: Callable[[ExecutionEvent], None]) -> Callable[[], None]:
|
|
130
|
+
with self._lock:
|
|
131
|
+
if callback not in self._subscribers:
|
|
132
|
+
self._subscribers.append(callback)
|
|
133
|
+
return lambda: self.unsubscribe(callback)
|
|
134
|
+
|
|
135
|
+
def unsubscribe(self, callback: Callable[[ExecutionEvent], None]) -> None:
|
|
136
|
+
with self._lock:
|
|
137
|
+
if callback in self._subscribers:
|
|
138
|
+
self._subscribers.remove(callback)
|
|
139
|
+
|
|
140
|
+
def emit(self, event: ExecutionEvent) -> None:
|
|
141
|
+
with self._lock:
|
|
142
|
+
self._events.append(event)
|
|
143
|
+
if len(self._events) > self._max_history:
|
|
144
|
+
del self._events[:-self._max_history]
|
|
145
|
+
listeners = list(self._subscribers)
|
|
146
|
+
|
|
147
|
+
# Notify subscribers safely
|
|
148
|
+
for listener in listeners:
|
|
149
|
+
try:
|
|
150
|
+
listener(event)
|
|
151
|
+
except Exception as e:
|
|
152
|
+
logger.debug(f"Error notifying event bus listener: {e}")
|
|
153
|
+
|
|
154
|
+
def get_events(self, trace_id: Optional[str] = None) -> List[ExecutionEvent]:
|
|
155
|
+
with self._lock:
|
|
156
|
+
if trace_id:
|
|
157
|
+
return [e for e in self._events if e.trace_id == trace_id]
|
|
158
|
+
return list(self._events)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
event_bus = ExecutionEventBus()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# ── 4. Workflow Simulation & Autonomous Engine ──────────────────────────────
|
|
165
|
+
class WorkflowEngine:
|
|
166
|
+
"""Master workflow orchestrator. Provides:
|
|
167
|
+
- Trace & Span tracking
|
|
168
|
+
- Real browser application workflow simulation
|
|
169
|
+
- Autonomous bug detection & verification loops
|
|
170
|
+
- Weak model structured-action adapter
|
|
171
|
+
- Synchronization with the chat UI Live Activities system
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
def __init__(self):
|
|
175
|
+
self._lock = threading.RLock()
|
|
176
|
+
self._active_traces: Dict[str, Dict[str, Any]] = {}
|
|
177
|
+
self._detected_bugs: List[Dict[str, Any]] = []
|
|
178
|
+
|
|
179
|
+
def start_trace(self, title: str, turn_id: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> str:
|
|
180
|
+
trace_id = f"trace-{uuid.uuid4().hex[:12]}"
|
|
181
|
+
with self._lock:
|
|
182
|
+
self._active_traces[trace_id] = {
|
|
183
|
+
"trace_id": trace_id,
|
|
184
|
+
"title": title,
|
|
185
|
+
"turn_id": turn_id or act_mod.get_current_turn() or "",
|
|
186
|
+
"start_time": time.perf_counter(),
|
|
187
|
+
"events": [],
|
|
188
|
+
"metadata": metadata or {},
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
# Emit initial start event
|
|
192
|
+
evt = ExecutionEvent(
|
|
193
|
+
event_id=f"evt-{uuid.uuid4().hex[:8]}",
|
|
194
|
+
trace_id=trace_id,
|
|
195
|
+
parent_id="",
|
|
196
|
+
event_type=EVENT_AGENT_STARTED,
|
|
197
|
+
status=STATE_RUNNING,
|
|
198
|
+
source="cat.runtime",
|
|
199
|
+
tool="orchestrator",
|
|
200
|
+
description=f"Agent task started: {title}",
|
|
201
|
+
metadata={"title": title}
|
|
202
|
+
)
|
|
203
|
+
event_bus.emit(evt)
|
|
204
|
+
self._sync_to_activity_bus(evt, turn_id)
|
|
205
|
+
return trace_id
|
|
206
|
+
|
|
207
|
+
def finish_trace(self, trace_id: str, success: bool = True, summary: str = "") -> None:
|
|
208
|
+
with self._lock:
|
|
209
|
+
info = self._active_traces.pop(trace_id, None)
|
|
210
|
+
turn_id = info["turn_id"] if info else ""
|
|
211
|
+
evt = ExecutionEvent(
|
|
212
|
+
event_id=f"evt-{uuid.uuid4().hex[:8]}",
|
|
213
|
+
trace_id=trace_id,
|
|
214
|
+
parent_id="",
|
|
215
|
+
event_type=EVENT_AGENT_COMPLETED,
|
|
216
|
+
status=STATE_SUCCEEDED if success else STATE_FAILED,
|
|
217
|
+
source="cat.runtime",
|
|
218
|
+
tool="orchestrator",
|
|
219
|
+
description=summary or ("Task verified and completed" if success else "Task completed with issues"),
|
|
220
|
+
metadata={"success": success}
|
|
221
|
+
)
|
|
222
|
+
event_bus.emit(evt)
|
|
223
|
+
self._sync_to_activity_bus(evt, turn_id)
|
|
224
|
+
|
|
225
|
+
# ── Real Website Simulation Layer ───────────────────────────────────────
|
|
226
|
+
def simulate_website(
|
|
227
|
+
self,
|
|
228
|
+
url: str,
|
|
229
|
+
steps: Optional[List[Dict[str, Any]]] = None,
|
|
230
|
+
trace_id: Optional[str] = None,
|
|
231
|
+
turn_id: Optional[str] = None
|
|
232
|
+
) -> Dict[str, Any]:
|
|
233
|
+
"""Simulate real user interaction on a website using Playwright Chromium.
|
|
234
|
+
Emits real execution events for every single operation.
|
|
235
|
+
"""
|
|
236
|
+
tid = trace_id or self.start_trace(f"Simulate website: {url}", turn_id)
|
|
237
|
+
report: Dict[str, Any] = {
|
|
238
|
+
"url": url,
|
|
239
|
+
"success": True,
|
|
240
|
+
"steps_completed": 0,
|
|
241
|
+
"total_steps": len(steps or []) + 1,
|
|
242
|
+
"console_errors": 0,
|
|
243
|
+
"network_errors": 0,
|
|
244
|
+
"detected_bugs": [],
|
|
245
|
+
"elements": 0,
|
|
246
|
+
"actions": [],
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
# Step 1: Start browser engine
|
|
250
|
+
evt_start = ExecutionEvent(
|
|
251
|
+
event_id=f"evt-{uuid.uuid4().hex[:8]}",
|
|
252
|
+
trace_id=tid,
|
|
253
|
+
parent_id="",
|
|
254
|
+
event_type=EVENT_BROWSER_STARTED,
|
|
255
|
+
status=STATE_RUNNING,
|
|
256
|
+
source="browser.engine",
|
|
257
|
+
tool="chromium",
|
|
258
|
+
description="Starting Chromium browser session..."
|
|
259
|
+
)
|
|
260
|
+
event_bus.emit(evt_start)
|
|
261
|
+
self._sync_to_activity_bus(evt_start, turn_id)
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
from .browser.engine import BrowserEngine, PLAYWRIGHT_AVAILABLE
|
|
265
|
+
if not PLAYWRIGHT_AVAILABLE:
|
|
266
|
+
raise RuntimeError("Playwright is not installed. Browser automation unavailable.")
|
|
267
|
+
|
|
268
|
+
engine = BrowserEngine(headless=True)
|
|
269
|
+
engine.start(timeout=20.0)
|
|
270
|
+
except Exception as e:
|
|
271
|
+
# Report honestly without faking
|
|
272
|
+
evt_fail = ExecutionEvent(
|
|
273
|
+
event_id=f"evt-{uuid.uuid4().hex[:8]}",
|
|
274
|
+
trace_id=tid,
|
|
275
|
+
parent_id=evt_start.event_id,
|
|
276
|
+
event_type=EVENT_BROWSER_STARTED,
|
|
277
|
+
status=STATE_FAILED,
|
|
278
|
+
source="browser.engine",
|
|
279
|
+
tool="chromium",
|
|
280
|
+
description=f"Browser simulation unavailable: {e}"
|
|
281
|
+
)
|
|
282
|
+
event_bus.emit(evt_fail)
|
|
283
|
+
self._sync_to_activity_bus(evt_fail, turn_id)
|
|
284
|
+
report["success"] = False
|
|
285
|
+
report["error"] = str(e)
|
|
286
|
+
return report
|
|
287
|
+
|
|
288
|
+
try:
|
|
289
|
+
# Step 2: Navigate to URL
|
|
290
|
+
t0 = time.perf_counter()
|
|
291
|
+
snap = engine.navigate(url, timeout=25.0)
|
|
292
|
+
dur = int((time.perf_counter() - t0) * 1000)
|
|
293
|
+
|
|
294
|
+
evt_nav = ExecutionEvent(
|
|
295
|
+
event_id=f"evt-{uuid.uuid4().hex[:8]}",
|
|
296
|
+
trace_id=tid,
|
|
297
|
+
parent_id=evt_start.event_id,
|
|
298
|
+
event_type=EVENT_BROWSER_NAVIGATION,
|
|
299
|
+
status=STATE_SUCCEEDED if snap.ok else STATE_FAILED,
|
|
300
|
+
source="browser.engine",
|
|
301
|
+
tool="navigate",
|
|
302
|
+
description=f"Navigate → {url} ({snap.title or 'Loaded'})",
|
|
303
|
+
duration_ms=dur,
|
|
304
|
+
metadata={"url": url, "status": snap.status_code, "elements": snap.elements}
|
|
305
|
+
)
|
|
306
|
+
event_bus.emit(evt_nav)
|
|
307
|
+
self._sync_to_activity_bus(evt_nav, turn_id)
|
|
308
|
+
report["steps_completed"] += 1
|
|
309
|
+
report["elements"] = snap.elements
|
|
310
|
+
|
|
311
|
+
# Step 3: Interactive elements detection
|
|
312
|
+
interactives = engine.get_interactive_elements(timeout=5.0)
|
|
313
|
+
evt_detect = ExecutionEvent(
|
|
314
|
+
event_id=f"evt-{uuid.uuid4().hex[:8]}",
|
|
315
|
+
trace_id=tid,
|
|
316
|
+
parent_id=evt_start.event_id,
|
|
317
|
+
event_type=EVENT_BROWSER_ELEMENT_DETECTED,
|
|
318
|
+
status=STATE_SUCCEEDED,
|
|
319
|
+
source="browser.engine",
|
|
320
|
+
tool="dom_inspector",
|
|
321
|
+
description=f"{len(interactives)} interactive elements detected (buttons, links, inputs)",
|
|
322
|
+
metadata={"count": len(interactives)}
|
|
323
|
+
)
|
|
324
|
+
event_bus.emit(evt_detect)
|
|
325
|
+
self._sync_to_activity_bus(evt_detect, turn_id)
|
|
326
|
+
|
|
327
|
+
# Step 4: Execute requested workflow steps if provided
|
|
328
|
+
if steps:
|
|
329
|
+
for idx, step in enumerate(steps):
|
|
330
|
+
action_type = step.get("action", "click")
|
|
331
|
+
target = step.get("target", "")
|
|
332
|
+
value = step.get("value", "")
|
|
333
|
+
|
|
334
|
+
if action_type == "click":
|
|
335
|
+
engine.click(target, timeout=8.0)
|
|
336
|
+
evt_action = ExecutionEvent(
|
|
337
|
+
event_id=f"evt-{uuid.uuid4().hex[:8]}",
|
|
338
|
+
trace_id=tid,
|
|
339
|
+
parent_id=evt_start.event_id,
|
|
340
|
+
event_type=EVENT_BROWSER_CLICK,
|
|
341
|
+
status=STATE_SUCCEEDED,
|
|
342
|
+
source="browser.engine",
|
|
343
|
+
tool="click",
|
|
344
|
+
description=f"Clicked \"{target}\""
|
|
345
|
+
)
|
|
346
|
+
elif action_type in ("type", "input"):
|
|
347
|
+
engine.type_text(target, value, timeout=8.0)
|
|
348
|
+
evt_action = ExecutionEvent(
|
|
349
|
+
event_id=f"evt-{uuid.uuid4().hex[:8]}",
|
|
350
|
+
trace_id=tid,
|
|
351
|
+
parent_id=evt_start.event_id,
|
|
352
|
+
event_type=EVENT_BROWSER_INPUT,
|
|
353
|
+
status=STATE_SUCCEEDED,
|
|
354
|
+
source="browser.engine",
|
|
355
|
+
tool="type",
|
|
356
|
+
description=f"Input into \"{target}\""
|
|
357
|
+
)
|
|
358
|
+
else:
|
|
359
|
+
continue
|
|
360
|
+
|
|
361
|
+
event_bus.emit(evt_action)
|
|
362
|
+
self._sync_to_activity_bus(evt_action, turn_id)
|
|
363
|
+
report["steps_completed"] += 1
|
|
364
|
+
report["actions"].append(action_type)
|
|
365
|
+
|
|
366
|
+
# Step 5: Verification & Autonomous Bug Detection
|
|
367
|
+
v_res = engine.workflow_verify(timeout=6.0)
|
|
368
|
+
report["console_errors"] = v_res.get("console_errors", 0)
|
|
369
|
+
if not v_res["verified"] or v_res.get("js_errors"):
|
|
370
|
+
# Autonomous Bug Detected!
|
|
371
|
+
for err in v_res.get("js_errors", []):
|
|
372
|
+
bug_desc = f"Console Error: {err}"
|
|
373
|
+
evt_bug = ExecutionEvent(
|
|
374
|
+
event_id=f"evt-{uuid.uuid4().hex[:8]}",
|
|
375
|
+
trace_id=tid,
|
|
376
|
+
parent_id=evt_start.event_id,
|
|
377
|
+
event_type=EVENT_BUG_DETECTED,
|
|
378
|
+
status=STATE_FAILED,
|
|
379
|
+
source="browser.console",
|
|
380
|
+
tool="bug_detector",
|
|
381
|
+
description=f"Bug detected: {bug_desc[:80]}",
|
|
382
|
+
metadata={"error": err, "url": url, "confidence": "Observed (Console)"}
|
|
383
|
+
)
|
|
384
|
+
event_bus.emit(evt_bug)
|
|
385
|
+
self._sync_to_activity_bus(evt_bug, turn_id)
|
|
386
|
+
report["detected_bugs"].append(err)
|
|
387
|
+
report["success"] = False
|
|
388
|
+
else:
|
|
389
|
+
evt_val = ExecutionEvent(
|
|
390
|
+
event_id=f"evt-{uuid.uuid4().hex[:8]}",
|
|
391
|
+
trace_id=tid,
|
|
392
|
+
parent_id=evt_start.event_id,
|
|
393
|
+
event_type=EVENT_VERIFICATION_COMPLETED,
|
|
394
|
+
status=STATE_SUCCEEDED,
|
|
395
|
+
source="browser.engine",
|
|
396
|
+
tool="verifier",
|
|
397
|
+
description=f"Verification clean: 0 console errors, HTTP {snap.status_code or 200}"
|
|
398
|
+
)
|
|
399
|
+
event_bus.emit(evt_val)
|
|
400
|
+
self._sync_to_activity_bus(evt_val, turn_id)
|
|
401
|
+
|
|
402
|
+
finally:
|
|
403
|
+
engine.close()
|
|
404
|
+
|
|
405
|
+
return report
|
|
406
|
+
|
|
407
|
+
# ── Autonomous Bug Detection & Debug Loop (Spec Section 9 & 10) ─────────
|
|
408
|
+
def record_bug_detected(
|
|
409
|
+
self,
|
|
410
|
+
source: str,
|
|
411
|
+
error_msg: str,
|
|
412
|
+
workflow_step: str,
|
|
413
|
+
trace_id: Optional[str] = None,
|
|
414
|
+
turn_id: Optional[str] = None,
|
|
415
|
+
confidence: str = "Observed"
|
|
416
|
+
) -> str:
|
|
417
|
+
"""Record an autonomous bug detection event with real evidence."""
|
|
418
|
+
tid = trace_id or f"trace-{uuid.uuid4().hex[:12]}"
|
|
419
|
+
bug_info = {
|
|
420
|
+
"source": source,
|
|
421
|
+
"error": error_msg,
|
|
422
|
+
"workflow": workflow_step,
|
|
423
|
+
"confidence": confidence,
|
|
424
|
+
"timestamp": time.time()
|
|
425
|
+
}
|
|
426
|
+
with self._lock:
|
|
427
|
+
self._detected_bugs.append(bug_info)
|
|
428
|
+
|
|
429
|
+
evt = ExecutionEvent(
|
|
430
|
+
event_id=f"evt-{uuid.uuid4().hex[:8]}",
|
|
431
|
+
trace_id=tid,
|
|
432
|
+
parent_id="",
|
|
433
|
+
event_type=EVENT_BUG_DETECTED,
|
|
434
|
+
status=STATE_FAILED,
|
|
435
|
+
source=source,
|
|
436
|
+
tool="autonomous_detector",
|
|
437
|
+
description=f"Bug detected in {workflow_step}: {error_msg[:60]}",
|
|
438
|
+
metadata=bug_info
|
|
439
|
+
)
|
|
440
|
+
event_bus.emit(evt)
|
|
441
|
+
self._sync_to_activity_bus(evt, turn_id)
|
|
442
|
+
return evt.event_id
|
|
443
|
+
|
|
444
|
+
def record_bug_verified_fixed(
|
|
445
|
+
self,
|
|
446
|
+
bug_description: str,
|
|
447
|
+
evidence: str,
|
|
448
|
+
trace_id: Optional[str] = None,
|
|
449
|
+
turn_id: Optional[str] = None
|
|
450
|
+
) -> None:
|
|
451
|
+
"""Record verified resolution of a previously detected bug backed by real evidence."""
|
|
452
|
+
tid = trace_id or f"trace-{uuid.uuid4().hex[:12]}"
|
|
453
|
+
evt = ExecutionEvent(
|
|
454
|
+
event_id=f"evt-{uuid.uuid4().hex[:8]}",
|
|
455
|
+
trace_id=tid,
|
|
456
|
+
parent_id="",
|
|
457
|
+
event_type=EVENT_BUG_FIXED,
|
|
458
|
+
status=STATE_SUCCEEDED,
|
|
459
|
+
source="autonomous_verifier",
|
|
460
|
+
tool="verifier",
|
|
461
|
+
description=f"Bug verified fixed: {bug_description} (Evidence: {evidence})",
|
|
462
|
+
metadata={"evidence": evidence, "verified": True}
|
|
463
|
+
)
|
|
464
|
+
event_bus.emit(evt)
|
|
465
|
+
self._sync_to_activity_bus(evt, turn_id)
|
|
466
|
+
|
|
467
|
+
# ── Weak / Local Model (Ollama) Support (Spec Section 11 & 12) ──────────
|
|
468
|
+
def parse_weak_model_intention(self, raw_text: str) -> Optional[Dict[str, Any]]:
|
|
469
|
+
"""Extracts structured intention from weaker/local models that do not natively
|
|
470
|
+
support function calling, e.g. {"action": "browser_click", "target": "Settings"}.
|
|
471
|
+
"""
|
|
472
|
+
if not raw_text or not isinstance(raw_text, str):
|
|
473
|
+
return None
|
|
474
|
+
cleaned = re.sub(r"^```(?:json)?\s*|\s*```$", "", raw_text.strip(), flags=re.MULTILINE)
|
|
475
|
+
# 1. Try direct JSON parsing
|
|
476
|
+
try:
|
|
477
|
+
data = json.loads(cleaned)
|
|
478
|
+
if isinstance(data, dict) and ("action" in data or "tool" in data):
|
|
479
|
+
return data
|
|
480
|
+
except Exception:
|
|
481
|
+
pass
|
|
482
|
+
# 2. Balanced JSON scanning for embedded intentions (supporting nested objects like args: {...})
|
|
483
|
+
start = -1
|
|
484
|
+
depth = 0
|
|
485
|
+
in_str = False
|
|
486
|
+
escape = False
|
|
487
|
+
for i, ch in enumerate(cleaned):
|
|
488
|
+
if escape:
|
|
489
|
+
escape = False
|
|
490
|
+
continue
|
|
491
|
+
if ch == "\\":
|
|
492
|
+
escape = True
|
|
493
|
+
continue
|
|
494
|
+
if ch == '"':
|
|
495
|
+
in_str = not in_str
|
|
496
|
+
continue
|
|
497
|
+
if in_str:
|
|
498
|
+
continue
|
|
499
|
+
if ch == "{":
|
|
500
|
+
if depth == 0:
|
|
501
|
+
start = i
|
|
502
|
+
depth += 1
|
|
503
|
+
elif ch == "}":
|
|
504
|
+
if depth > 0:
|
|
505
|
+
depth -= 1
|
|
506
|
+
if depth == 0 and start != -1:
|
|
507
|
+
chunk = cleaned[start : i + 1]
|
|
508
|
+
try:
|
|
509
|
+
data = json.loads(chunk)
|
|
510
|
+
if isinstance(data, dict) and ("action" in data or "tool" in data):
|
|
511
|
+
return data
|
|
512
|
+
except Exception:
|
|
513
|
+
pass
|
|
514
|
+
start = -1
|
|
515
|
+
return None
|
|
516
|
+
|
|
517
|
+
def execute_weak_model_action(
|
|
518
|
+
self,
|
|
519
|
+
intention: Dict[str, Any],
|
|
520
|
+
turn_id: Optional[str] = None,
|
|
521
|
+
tool_executor: Optional[Callable[[str, Dict[str, Any]], Any]] = None
|
|
522
|
+
) -> Dict[str, Any]:
|
|
523
|
+
"""Validates and executes an action on behalf of a weaker model, emitting real
|
|
524
|
+
execution events and returning structured observation.
|
|
525
|
+
"""
|
|
526
|
+
action = intention.get("action") or intention.get("tool") or ""
|
|
527
|
+
target = intention.get("target") or intention.get("args") or {}
|
|
528
|
+
|
|
529
|
+
tid = f"trace-{uuid.uuid4().hex[:12]}"
|
|
530
|
+
evt = ExecutionEvent(
|
|
531
|
+
event_id=f"evt-{uuid.uuid4().hex[:8]}",
|
|
532
|
+
trace_id=tid,
|
|
533
|
+
parent_id="",
|
|
534
|
+
event_type=EVENT_TOOL_STARTED,
|
|
535
|
+
status=STATE_RUNNING,
|
|
536
|
+
source="weak_model.adapter",
|
|
537
|
+
tool=str(action),
|
|
538
|
+
description=f"Executing structured action: {action}"
|
|
539
|
+
)
|
|
540
|
+
event_bus.emit(evt)
|
|
541
|
+
self._sync_to_activity_bus(evt, turn_id)
|
|
542
|
+
|
|
543
|
+
try:
|
|
544
|
+
if tool_executor:
|
|
545
|
+
res = tool_executor(action, target if isinstance(target, dict) else {"target": target})
|
|
546
|
+
else:
|
|
547
|
+
res = f"Action {action} executed successfully"
|
|
548
|
+
|
|
549
|
+
evt_done = ExecutionEvent(
|
|
550
|
+
event_id=f"evt-{uuid.uuid4().hex[:8]}",
|
|
551
|
+
trace_id=tid,
|
|
552
|
+
parent_id=evt.event_id,
|
|
553
|
+
event_type=EVENT_TOOL_COMPLETED,
|
|
554
|
+
status=STATE_SUCCEEDED,
|
|
555
|
+
source="weak_model.adapter",
|
|
556
|
+
tool=str(action),
|
|
557
|
+
description=f"Action completed: {str(res)[:60]}",
|
|
558
|
+
metadata={"result": str(res)}
|
|
559
|
+
)
|
|
560
|
+
event_bus.emit(evt_done)
|
|
561
|
+
self._sync_to_activity_bus(evt_done, turn_id)
|
|
562
|
+
return {"status": "success", "observation": res}
|
|
563
|
+
|
|
564
|
+
except Exception as e:
|
|
565
|
+
evt_fail = ExecutionEvent(
|
|
566
|
+
event_id=f"evt-{uuid.uuid4().hex[:8]}",
|
|
567
|
+
trace_id=tid,
|
|
568
|
+
parent_id=evt.event_id,
|
|
569
|
+
event_type=EVENT_TOOL_COMPLETED,
|
|
570
|
+
status=STATE_FAILED,
|
|
571
|
+
source="weak_model.adapter",
|
|
572
|
+
tool=str(action),
|
|
573
|
+
description=f"Action failed: {e}",
|
|
574
|
+
metadata={"error": str(e)}
|
|
575
|
+
)
|
|
576
|
+
event_bus.emit(evt_fail)
|
|
577
|
+
self._sync_to_activity_bus(evt_fail, turn_id)
|
|
578
|
+
return {"status": "failed", "error": str(e)}
|
|
579
|
+
|
|
580
|
+
# ── Lightweight Flow for Simple Requests / Greetings (Spec Section 17) ──
|
|
581
|
+
def record_lightweight_flow(
|
|
582
|
+
self,
|
|
583
|
+
prompt: str,
|
|
584
|
+
model_name: str = "CAT",
|
|
585
|
+
turn_id: Optional[str] = None
|
|
586
|
+
) -> None:
|
|
587
|
+
"""Lightweight real execution telemetry for small tasks or simple greetings.
|
|
588
|
+
Never creates fake complex work; maps to genuine steps:
|
|
589
|
+
Request received -> Model selected -> Context prepared -> Response generated.
|
|
590
|
+
"""
|
|
591
|
+
tid = f"trace-{uuid.uuid4().hex[:12]}"
|
|
592
|
+
steps = [
|
|
593
|
+
(EVENT_AGENT_STARTED, "Request received", STATE_SUCCEEDED, 8),
|
|
594
|
+
(EVENT_TASK_PLANNING, f"Model selected: {model_name}", STATE_SUCCEEDED, 14),
|
|
595
|
+
(EVENT_TOOL_COMPLETED, "Context prepared", STATE_SUCCEEDED, 22),
|
|
596
|
+
(EVENT_AGENT_COMPLETED, "Response generated", STATE_SUCCEEDED, 65),
|
|
597
|
+
]
|
|
598
|
+
for ev_type, desc, st, dur in steps:
|
|
599
|
+
evt = ExecutionEvent(
|
|
600
|
+
event_id=f"evt-{uuid.uuid4().hex[:8]}",
|
|
601
|
+
trace_id=tid,
|
|
602
|
+
parent_id="",
|
|
603
|
+
event_type=ev_type,
|
|
604
|
+
status=st,
|
|
605
|
+
source="cat.runtime",
|
|
606
|
+
tool="workflow",
|
|
607
|
+
description=desc,
|
|
608
|
+
duration_ms=dur
|
|
609
|
+
)
|
|
610
|
+
event_bus.emit(evt)
|
|
611
|
+
self._sync_to_activity_bus(evt, turn_id)
|
|
612
|
+
|
|
613
|
+
def start_streaming_flow(
|
|
614
|
+
self,
|
|
615
|
+
prompt: str,
|
|
616
|
+
model_name: str = "CAT",
|
|
617
|
+
turn_id: Optional[str] = None
|
|
618
|
+
) -> str:
|
|
619
|
+
"""Starts real-time live activity for an incoming prompt/request.
|
|
620
|
+
Creates:
|
|
621
|
+
1. 'Request received' (completed)
|
|
622
|
+
2. 'Model selected: {model_name}' (completed)
|
|
623
|
+
3. 'Generating response...' (running with active spinner)
|
|
624
|
+
Returns the event_id for the running generation activity.
|
|
625
|
+
"""
|
|
626
|
+
tid = f"trace-{uuid.uuid4().hex[:12]}"
|
|
627
|
+
evt1 = ExecutionEvent(
|
|
628
|
+
event_id=f"evt-{uuid.uuid4().hex[:8]}",
|
|
629
|
+
trace_id=tid,
|
|
630
|
+
parent_id="",
|
|
631
|
+
event_type=EVENT_AGENT_STARTED,
|
|
632
|
+
status=STATE_SUCCEEDED,
|
|
633
|
+
source="cat.runtime",
|
|
634
|
+
tool="workflow",
|
|
635
|
+
description="Request received",
|
|
636
|
+
duration_ms=8
|
|
637
|
+
)
|
|
638
|
+
event_bus.emit(evt1)
|
|
639
|
+
self._sync_to_activity_bus(evt1, turn_id)
|
|
640
|
+
|
|
641
|
+
evt2 = ExecutionEvent(
|
|
642
|
+
event_id=f"evt-{uuid.uuid4().hex[:8]}",
|
|
643
|
+
trace_id=tid,
|
|
644
|
+
parent_id="",
|
|
645
|
+
event_type=EVENT_TASK_PLANNING,
|
|
646
|
+
status=STATE_SUCCEEDED,
|
|
647
|
+
source="cat.runtime",
|
|
648
|
+
tool="workflow",
|
|
649
|
+
description=f"Model selected: {model_name}",
|
|
650
|
+
duration_ms=15
|
|
651
|
+
)
|
|
652
|
+
event_bus.emit(evt2)
|
|
653
|
+
self._sync_to_activity_bus(evt2, turn_id)
|
|
654
|
+
|
|
655
|
+
gen_id = f"evt-gen-{turn_id}" if turn_id else f"evt-gen-{uuid.uuid4().hex[:8]}"
|
|
656
|
+
evt3 = ExecutionEvent(
|
|
657
|
+
event_id=gen_id,
|
|
658
|
+
trace_id=tid,
|
|
659
|
+
parent_id="",
|
|
660
|
+
event_type=EVENT_TOOL_STARTED,
|
|
661
|
+
status=STATE_RUNNING,
|
|
662
|
+
source="cat.runtime",
|
|
663
|
+
tool="generation",
|
|
664
|
+
description="Generating response..."
|
|
665
|
+
)
|
|
666
|
+
event_bus.emit(evt3)
|
|
667
|
+
self._sync_to_activity_bus(evt3, turn_id)
|
|
668
|
+
return gen_id
|
|
669
|
+
|
|
670
|
+
def finish_streaming_flow(
|
|
671
|
+
self,
|
|
672
|
+
turn_id: Optional[str] = None,
|
|
673
|
+
duration_ms: Optional[int] = None,
|
|
674
|
+
token_count: Optional[int] = None
|
|
675
|
+
) -> None:
|
|
676
|
+
"""Finishes the running generation activity with real duration and tokens."""
|
|
677
|
+
if not turn_id:
|
|
678
|
+
return
|
|
679
|
+
gen_id = f"evt-gen-{turn_id}"
|
|
680
|
+
res_text = f"{token_count} tokens" if token_count else "Complete"
|
|
681
|
+
try:
|
|
682
|
+
act_mod.manager.update(
|
|
683
|
+
gen_id,
|
|
684
|
+
status=act_mod.STATUS_COMPLETED,
|
|
685
|
+
title="Response generated",
|
|
686
|
+
result=res_text,
|
|
687
|
+
duration_ms=duration_ms or 0
|
|
688
|
+
)
|
|
689
|
+
except Exception:
|
|
690
|
+
pass
|
|
691
|
+
|
|
692
|
+
# ── Internal Activity Bus Bridge ────────────────────────────────────────
|
|
693
|
+
def _sync_to_activity_bus(self, event: ExecutionEvent, turn_id: Optional[str] = None) -> None:
|
|
694
|
+
"""Bridges WorkflowEngine events directly into calc_terminal.activity.manager
|
|
695
|
+
so that every real event immediately renders in the Chat UI's LiveActivitiesBlock!
|
|
696
|
+
"""
|
|
697
|
+
try:
|
|
698
|
+
tid = turn_id or act_mod.get_current_turn() or ""
|
|
699
|
+
status_map = {
|
|
700
|
+
STATE_QUEUED: act_mod.STATUS_PENDING,
|
|
701
|
+
STATE_RUNNING: act_mod.STATUS_RUNNING,
|
|
702
|
+
STATE_WAITING: act_mod.STATUS_WAITING,
|
|
703
|
+
STATE_SUCCEEDED: act_mod.STATUS_COMPLETED,
|
|
704
|
+
STATE_FAILED: act_mod.STATUS_FAILED,
|
|
705
|
+
STATE_CANCELLED: act_mod.STATUS_CANCELLED,
|
|
706
|
+
STATE_RETRYING: act_mod.STATUS_RUNNING,
|
|
707
|
+
STATE_BLOCKED: act_mod.STATUS_WAITING_PERMISSION,
|
|
708
|
+
STATE_VERIFYING: act_mod.STATUS_RUNNING,
|
|
709
|
+
}
|
|
710
|
+
mapped_status = status_map.get(event.status, act_mod.STATUS_RUNNING)
|
|
711
|
+
|
|
712
|
+
category = act_mod.CAT_ANALYSIS
|
|
713
|
+
phase = act_mod.PHASE_ANALYSIS
|
|
714
|
+
if "browser" in event.event_type:
|
|
715
|
+
category = act_mod.CAT_TERMINAL
|
|
716
|
+
phase = act_mod.PHASE_IMPLEMENTATION
|
|
717
|
+
elif "filesystem" in event.event_type:
|
|
718
|
+
category = act_mod.CAT_FILE_READ if "read" in event.event_type else act_mod.CAT_EDIT
|
|
719
|
+
phase = act_mod.PHASE_DISCOVERY if "read" in event.event_type else act_mod.PHASE_IMPLEMENTATION
|
|
720
|
+
elif "test" in event.event_type or "verification" in event.event_type:
|
|
721
|
+
category = act_mod.CAT_TEST
|
|
722
|
+
phase = act_mod.PHASE_VALIDATION
|
|
723
|
+
elif "generation" in event.tool or event.tool == "model" or "completed" in event.event_type:
|
|
724
|
+
category = act_mod.CAT_MODEL
|
|
725
|
+
phase = act_mod.PHASE_COMPLETE
|
|
726
|
+
elif "planning" in event.event_type or "started" in event.event_type:
|
|
727
|
+
category = act_mod.CAT_PLANNING
|
|
728
|
+
phase = act_mod.PHASE_DISCOVERY
|
|
729
|
+
|
|
730
|
+
meta = event.metadata or {}
|
|
731
|
+
target_path = meta.get("path") or meta.get("file") or meta.get("target_path") or ""
|
|
732
|
+
command = meta.get("command") or meta.get("cmd") or ""
|
|
733
|
+
exit_code = meta.get("exit_code")
|
|
734
|
+
lines_added = int(meta.get("lines_added", 0))
|
|
735
|
+
lines_removed = int(meta.get("lines_removed", 0))
|
|
736
|
+
diff_summary = meta.get("diff_summary", "")
|
|
737
|
+
|
|
738
|
+
act_mod.manager.create(
|
|
739
|
+
id=event.event_id,
|
|
740
|
+
type=act_mod.TYPE_TOOL,
|
|
741
|
+
action=event.event_type,
|
|
742
|
+
title=event.description,
|
|
743
|
+
status=mapped_status,
|
|
744
|
+
turn_id=tid,
|
|
745
|
+
tool=event.tool,
|
|
746
|
+
details=event.description,
|
|
747
|
+
category=category,
|
|
748
|
+
phase=phase,
|
|
749
|
+
duration_ms=event.duration_ms,
|
|
750
|
+
progress=event.progress,
|
|
751
|
+
parent_id=event.parent_id or "",
|
|
752
|
+
parent_event_id=event.parent_id or "",
|
|
753
|
+
operation_id=event.event_id,
|
|
754
|
+
event_type=event.event_type,
|
|
755
|
+
source=event.source or "workflow.engine",
|
|
756
|
+
target_path=target_path,
|
|
757
|
+
file=target_path,
|
|
758
|
+
command=command,
|
|
759
|
+
exit_code=exit_code,
|
|
760
|
+
lines_added=lines_added,
|
|
761
|
+
lines_removed=lines_removed,
|
|
762
|
+
diff_summary=diff_summary,
|
|
763
|
+
metadata=meta,
|
|
764
|
+
)
|
|
765
|
+
except Exception as e:
|
|
766
|
+
logger.debug(f"Could not bridge workflow event to activity manager: {e}")
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
workflow_engine = WorkflowEngine()
|