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,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CAT Crash Resilience & Auto-Recovery per §15:
|
|
3
|
+
- Continuous persistence of running agent state, task DAG, and scratchpad
|
|
4
|
+
- Crash recovery detection on startup with resume or clean-rollback options
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import time
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Any, Dict, List, Optional
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class RecoverySnapshot:
|
|
18
|
+
session_id: str
|
|
19
|
+
task_id: str
|
|
20
|
+
mode: str
|
|
21
|
+
timestamp: float = field(default_factory=time.time)
|
|
22
|
+
task_graph_state: Dict[str, Any] = field(default_factory=dict)
|
|
23
|
+
scratchpad: str = ""
|
|
24
|
+
active_checkpoint_id: Optional[str] = None
|
|
25
|
+
completed: bool = False
|
|
26
|
+
|
|
27
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
28
|
+
return {
|
|
29
|
+
"session_id": self.session_id,
|
|
30
|
+
"task_id": self.task_id,
|
|
31
|
+
"mode": self.mode,
|
|
32
|
+
"timestamp": self.timestamp,
|
|
33
|
+
"task_graph_state": self.task_graph_state,
|
|
34
|
+
"scratchpad": self.scratchpad,
|
|
35
|
+
"active_checkpoint_id": self.active_checkpoint_id,
|
|
36
|
+
"completed": self.completed,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def from_dict(cls, data: Dict[str, Any]) -> RecoverySnapshot:
|
|
41
|
+
return cls(
|
|
42
|
+
session_id=data.get("session_id", ""),
|
|
43
|
+
task_id=data.get("task_id", ""),
|
|
44
|
+
mode=data.get("mode", "agent"),
|
|
45
|
+
timestamp=data.get("timestamp", time.time()),
|
|
46
|
+
task_graph_state=data.get("task_graph_state", {}),
|
|
47
|
+
scratchpad=data.get("scratchpad", ""),
|
|
48
|
+
active_checkpoint_id=data.get("active_checkpoint_id"),
|
|
49
|
+
completed=data.get("completed", False),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RecoveryEngine:
|
|
54
|
+
"""Manages crash detection, state persistence, and session resumption."""
|
|
55
|
+
|
|
56
|
+
def __init__(self, storage_path: Optional[str] = None):
|
|
57
|
+
self.storage_path = storage_path or os.path.join(os.path.expanduser("~"), ".cct_recovery_state.json")
|
|
58
|
+
self._active_snapshot: Optional[RecoverySnapshot] = None
|
|
59
|
+
self._load()
|
|
60
|
+
|
|
61
|
+
def _load(self):
|
|
62
|
+
if not os.path.exists(self.storage_path):
|
|
63
|
+
return
|
|
64
|
+
try:
|
|
65
|
+
with open(self.storage_path, "r", encoding="utf-8") as f:
|
|
66
|
+
data = json.load(f)
|
|
67
|
+
self._active_snapshot = RecoverySnapshot.from_dict(data)
|
|
68
|
+
except Exception:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
def record_heartbeat(
|
|
72
|
+
self,
|
|
73
|
+
session_id: str,
|
|
74
|
+
task_id: str,
|
|
75
|
+
mode: str,
|
|
76
|
+
task_graph_state: Optional[Dict[str, Any]] = None,
|
|
77
|
+
scratchpad: str = "",
|
|
78
|
+
checkpoint_id: Optional[str] = None,
|
|
79
|
+
) -> RecoverySnapshot:
|
|
80
|
+
"""Persist state of in-flight execution."""
|
|
81
|
+
snap = RecoverySnapshot(
|
|
82
|
+
session_id=session_id,
|
|
83
|
+
task_id=task_id,
|
|
84
|
+
mode=mode,
|
|
85
|
+
task_graph_state=task_graph_state or {},
|
|
86
|
+
scratchpad=scratchpad,
|
|
87
|
+
active_checkpoint_id=checkpoint_id,
|
|
88
|
+
completed=False,
|
|
89
|
+
)
|
|
90
|
+
self._active_snapshot = snap
|
|
91
|
+
self.save()
|
|
92
|
+
return snap
|
|
93
|
+
|
|
94
|
+
def mark_completed(self, session_id: str):
|
|
95
|
+
"""Mark session completed cleanly so recovery is not triggered."""
|
|
96
|
+
if not self._active_snapshot:
|
|
97
|
+
self._load()
|
|
98
|
+
if self._active_snapshot and self._active_snapshot.session_id == session_id:
|
|
99
|
+
self._active_snapshot.completed = True
|
|
100
|
+
self.save()
|
|
101
|
+
|
|
102
|
+
def get_pending_recovery(self) -> Optional[RecoverySnapshot]:
|
|
103
|
+
"""Check if an uncompleted session was interrupted/crashed."""
|
|
104
|
+
if not self._active_snapshot:
|
|
105
|
+
self._load()
|
|
106
|
+
if self._active_snapshot and not self._active_snapshot.completed and self._active_snapshot.session_id:
|
|
107
|
+
return self._active_snapshot
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
def clear_recovery(self):
|
|
111
|
+
"""Clear recovery record."""
|
|
112
|
+
self._active_snapshot = None
|
|
113
|
+
if os.path.exists(self.storage_path):
|
|
114
|
+
try:
|
|
115
|
+
os.remove(self.storage_path)
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
def save(self):
|
|
120
|
+
if not self._active_snapshot:
|
|
121
|
+
return
|
|
122
|
+
try:
|
|
123
|
+
with open(self.storage_path, "w", encoding="utf-8") as f:
|
|
124
|
+
json.dump(self._active_snapshot.to_dict(), f, indent=2)
|
|
125
|
+
except Exception:
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
recovery_engine = RecoveryEngine()
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CAT Security, Privacy & Safe Execution Layer per §27:
|
|
3
|
+
- Tiered permission model & approval gates for irreversible actions
|
|
4
|
+
- Automated secret redaction for API keys, tokens, and credentials
|
|
5
|
+
- Prompt injection and malicious command heuristic defenses
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import enum
|
|
11
|
+
import re
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PermissionTier(str, enum.Enum):
|
|
17
|
+
READ_ONLY = "read_only"
|
|
18
|
+
WORKSPACE_WRITE = "workspace_write"
|
|
19
|
+
TERMINAL_EXECUTE = "terminal_execute"
|
|
20
|
+
NETWORK_ACCESS = "network_access"
|
|
21
|
+
DESTRUCTIVE_OPERATIONS = "destructive_operations"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Regex patterns for common secret credentials
|
|
25
|
+
SECRET_PATTERNS = [
|
|
26
|
+
(re.compile(r"(sk-[a-zA-Z0-9]{20,})"), "[REDACTED_API_KEY]"),
|
|
27
|
+
(re.compile(r"(ghp_[a-zA-Z0-9]{36,})"), "[REDACTED_GITHUB_TOKEN]"),
|
|
28
|
+
(re.compile(r"(gho_[a-zA-Z0-9]{36,})"), "[REDACTED_GITHUB_TOKEN]"),
|
|
29
|
+
(re.compile(r"(hf_[a-zA-Z0-9]{34,})"), "[REDACTED_HF_TOKEN]"),
|
|
30
|
+
(re.compile(r"(AKIA[0-9A-Z]{16})"), "[REDACTED_AWS_KEY]"),
|
|
31
|
+
(re.compile(r"(AIza[0-9A-Za-z-_]{35})"), "[REDACTED_GOOGLE_KEY]"),
|
|
32
|
+
(re.compile(r"Bearer\s+([a-zA-Z0-9_\-\.]{20,})"), "Bearer [REDACTED_BEARER_TOKEN]"),
|
|
33
|
+
(re.compile(r"(?i)(password|secret|token|api_key)\s*[:=]\s*['\"]([^'\"]{4,})['\"]"), r"\1='[REDACTED]'"),
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
# Patterns representing high-risk or destructive actions requiring approval
|
|
37
|
+
DESTRUCTIVE_COMMAND_PATTERNS = [
|
|
38
|
+
re.compile(r"rm\s+(-rf|-fr|--recursive)\s+[/~]"),
|
|
39
|
+
re.compile(r"git\s+push\s+.*--force"),
|
|
40
|
+
re.compile(r"mkfs"),
|
|
41
|
+
re.compile(r"dd\s+if="),
|
|
42
|
+
re.compile(r":\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:"), # Fork bomb
|
|
43
|
+
re.compile(r"drop\s+(database|table)", re.IGNORECASE),
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
# Suspicious prompt injection indicators
|
|
47
|
+
PROMPT_INJECTION_PATTERNS = [
|
|
48
|
+
re.compile(r"(?i)(ignore|disregard)\s+(all\s+)?(previous\s+)?(instructions|prompts|safety\s+guidelines)"),
|
|
49
|
+
re.compile(r"(?i)you\s+are\s+now\s+in\s+developer\s+mode"),
|
|
50
|
+
re.compile(r"(?i)<system>.*ignore.*</system>"),
|
|
51
|
+
re.compile(r"(?i)reveal\s+(system\s+prompt|secret\s+keys)"),
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class SecurityCheckResult:
|
|
57
|
+
allowed: bool
|
|
58
|
+
requires_confirmation: bool = False
|
|
59
|
+
reason: Optional[str] = None
|
|
60
|
+
category: str = "ok"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class SecurityLayer:
|
|
64
|
+
"""Enforces execution safety, secret protection, and input sanitization."""
|
|
65
|
+
|
|
66
|
+
def __init__(self, current_tier: PermissionTier = PermissionTier.TERMINAL_EXECUTE):
|
|
67
|
+
self.current_tier = current_tier
|
|
68
|
+
self.approved_actions: Set[str] = set()
|
|
69
|
+
|
|
70
|
+
def redact_secrets(self, text: str) -> str:
|
|
71
|
+
"""Scan text and redact sensitive API keys, tokens, and credentials."""
|
|
72
|
+
if not text:
|
|
73
|
+
return ""
|
|
74
|
+
redacted = text
|
|
75
|
+
for pattern, replacement in SECRET_PATTERNS:
|
|
76
|
+
redacted = pattern.sub(replacement, redacted)
|
|
77
|
+
return redacted
|
|
78
|
+
|
|
79
|
+
def evaluate_command(self, command: str) -> SecurityCheckResult:
|
|
80
|
+
"""Inspect terminal command before execution."""
|
|
81
|
+
cmd_clean = command.strip()
|
|
82
|
+
|
|
83
|
+
# 1. Check destructive patterns
|
|
84
|
+
for pat in DESTRUCTIVE_COMMAND_PATTERNS:
|
|
85
|
+
if pat.search(cmd_clean):
|
|
86
|
+
return SecurityCheckResult(
|
|
87
|
+
allowed=False,
|
|
88
|
+
requires_confirmation=True,
|
|
89
|
+
reason=f"Potentially destructive command detected: '{cmd_clean}'. Explicit approval required.",
|
|
90
|
+
category="destructive",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# 2. Check permission tier
|
|
94
|
+
if self.current_tier == PermissionTier.READ_ONLY:
|
|
95
|
+
return SecurityCheckResult(
|
|
96
|
+
allowed=False,
|
|
97
|
+
requires_confirmation=False,
|
|
98
|
+
reason="Current permission tier is READ_ONLY. Terminal execution denied.",
|
|
99
|
+
category="permission_denied",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return SecurityCheckResult(allowed=True, category="ok")
|
|
103
|
+
|
|
104
|
+
def inspect_untrusted_input(self, content: str) -> Tuple[bool, Optional[str]]:
|
|
105
|
+
"""Detect potential prompt injection in external data or tool outputs."""
|
|
106
|
+
for pat in PROMPT_INJECTION_PATTERNS:
|
|
107
|
+
if pat.search(content):
|
|
108
|
+
return False, f"Potential prompt injection pattern detected: {pat.pattern}"
|
|
109
|
+
return True, None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
security_layer = SecurityLayer()
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CAT Task Graph & Background Task Engine per §9, §20:
|
|
3
|
+
Represents complex multi-step requests as directed acyclic task graphs.
|
|
4
|
+
Supports dependency resolution, retry, cancellation, pause/resume, and verification states.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import time
|
|
10
|
+
import uuid
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TaskStatus(str, Enum):
|
|
17
|
+
QUEUED = "QUEUED"
|
|
18
|
+
RUNNING = "RUNNING"
|
|
19
|
+
SUCCEEDED = "SUCCEEDED"
|
|
20
|
+
COMPLETED = "SUCCEEDED"
|
|
21
|
+
FAILED = "FAILED"
|
|
22
|
+
CANCELLED = "CANCELLED"
|
|
23
|
+
PAUSED = "PAUSED"
|
|
24
|
+
VERIFYING = "VERIFYING"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
TASK_SYMBOLS = {
|
|
28
|
+
TaskStatus.QUEUED: "○",
|
|
29
|
+
TaskStatus.RUNNING: "●",
|
|
30
|
+
TaskStatus.SUCCEEDED: "✓",
|
|
31
|
+
TaskStatus.FAILED: "✗",
|
|
32
|
+
TaskStatus.CANCELLED: "⊘",
|
|
33
|
+
TaskStatus.PAUSED: "⏸",
|
|
34
|
+
TaskStatus.VERIFYING: "⟳",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class TaskNode:
|
|
40
|
+
id: str
|
|
41
|
+
title: str
|
|
42
|
+
description: str = ""
|
|
43
|
+
dependencies: List[str] = field(default_factory=list)
|
|
44
|
+
status: TaskStatus = TaskStatus.QUEUED
|
|
45
|
+
progress: float = 0.0 # 0.0 to 1.0
|
|
46
|
+
logs: List[str] = field(default_factory=list)
|
|
47
|
+
result: Any = None
|
|
48
|
+
failure_reason: str = ""
|
|
49
|
+
verification_state: str = "pending" # pending, verified, not_verified
|
|
50
|
+
subtasks: List[str] = field(default_factory=list)
|
|
51
|
+
parent_id: Optional[str] = None
|
|
52
|
+
created_at: float = field(default_factory=time.time)
|
|
53
|
+
started_at: Optional[float] = None
|
|
54
|
+
finished_at: Optional[float] = None
|
|
55
|
+
duration_ms: float = 0.0
|
|
56
|
+
retries_left: int = 3
|
|
57
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
58
|
+
|
|
59
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
60
|
+
return {
|
|
61
|
+
"id": self.id,
|
|
62
|
+
"title": self.title,
|
|
63
|
+
"description": self.description,
|
|
64
|
+
"dependencies": self.dependencies,
|
|
65
|
+
"status": self.status.value,
|
|
66
|
+
"progress": self.progress,
|
|
67
|
+
"logs": self.logs[-50:],
|
|
68
|
+
"result": str(self.result)[:200] if self.result is not None else None,
|
|
69
|
+
"failure_reason": self.failure_reason,
|
|
70
|
+
"verification_state": self.verification_state,
|
|
71
|
+
"subtasks": self.subtasks,
|
|
72
|
+
"parent_id": self.parent_id,
|
|
73
|
+
"duration_ms": self.duration_ms,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TaskGraph:
|
|
78
|
+
"""DAG managing structured tasks, dependencies, and execution progress."""
|
|
79
|
+
|
|
80
|
+
def __init__(self, trace_id: Optional[str] = None):
|
|
81
|
+
self.trace_id = trace_id or f"trace-{uuid.uuid4().hex[:8]}"
|
|
82
|
+
self.nodes: Dict[str, TaskNode] = {}
|
|
83
|
+
self.root_tasks: List[str] = []
|
|
84
|
+
|
|
85
|
+
def add_task(
|
|
86
|
+
self,
|
|
87
|
+
title: str,
|
|
88
|
+
description: str = "",
|
|
89
|
+
dependencies: Optional[List[str]] = None,
|
|
90
|
+
parent_id: Optional[str] = None,
|
|
91
|
+
task_id: Optional[str] = None,
|
|
92
|
+
) -> str:
|
|
93
|
+
tid = task_id or f"task-{uuid.uuid4().hex[:6]}"
|
|
94
|
+
node = TaskNode(
|
|
95
|
+
id=tid,
|
|
96
|
+
title=title,
|
|
97
|
+
description=description,
|
|
98
|
+
dependencies=dependencies or [],
|
|
99
|
+
parent_id=parent_id,
|
|
100
|
+
)
|
|
101
|
+
self.nodes[tid] = node
|
|
102
|
+
if parent_id and parent_id in self.nodes:
|
|
103
|
+
self.nodes[parent_id].subtasks.append(tid)
|
|
104
|
+
elif not parent_id:
|
|
105
|
+
self.root_tasks.append(tid)
|
|
106
|
+
return tid
|
|
107
|
+
|
|
108
|
+
def get_task(self, task_id: str) -> Optional[TaskNode]:
|
|
109
|
+
return self.nodes.get(task_id)
|
|
110
|
+
|
|
111
|
+
def update_status(self, task_id: str, status: TaskStatus, result: Any = None, failure_reason: str = ""):
|
|
112
|
+
node = self.nodes.get(task_id)
|
|
113
|
+
if not node:
|
|
114
|
+
return
|
|
115
|
+
node.status = status
|
|
116
|
+
now = time.time()
|
|
117
|
+
if status == TaskStatus.RUNNING and node.started_at is None:
|
|
118
|
+
node.started_at = now
|
|
119
|
+
elif status in (TaskStatus.SUCCEEDED, TaskStatus.FAILED, TaskStatus.CANCELLED):
|
|
120
|
+
node.finished_at = now
|
|
121
|
+
if node.started_at:
|
|
122
|
+
node.duration_ms = round((now - node.started_at) * 1000.0, 2)
|
|
123
|
+
if result is not None:
|
|
124
|
+
node.result = result
|
|
125
|
+
if failure_reason:
|
|
126
|
+
node.failure_reason = failure_reason
|
|
127
|
+
|
|
128
|
+
def update_task_status(self, task_id: str, status: TaskStatus, result: Any = None, failure_reason: str = ""):
|
|
129
|
+
self.update_status(task_id, status, result, failure_reason)
|
|
130
|
+
|
|
131
|
+
def set_progress(self, task_id: str, progress: float):
|
|
132
|
+
node = self.nodes.get(task_id)
|
|
133
|
+
if node:
|
|
134
|
+
node.progress = max(0.0, min(1.0, float(progress)))
|
|
135
|
+
|
|
136
|
+
def log(self, task_id: str, message: str):
|
|
137
|
+
node = self.nodes.get(task_id)
|
|
138
|
+
if node:
|
|
139
|
+
node.logs.append(f"[{time.strftime('%H:%M:%S')}] {message}")
|
|
140
|
+
|
|
141
|
+
def get_ready_tasks(self) -> List[TaskNode]:
|
|
142
|
+
"""Return tasks whose dependencies are all SUCCEEDED and are currently QUEUED."""
|
|
143
|
+
ready = []
|
|
144
|
+
for node in self.nodes.values():
|
|
145
|
+
if node.status != TaskStatus.QUEUED:
|
|
146
|
+
continue
|
|
147
|
+
deps_met = all(
|
|
148
|
+
dep in self.nodes and self.nodes[dep].status == TaskStatus.SUCCEEDED
|
|
149
|
+
for dep in node.dependencies
|
|
150
|
+
)
|
|
151
|
+
if deps_met:
|
|
152
|
+
ready.append(node)
|
|
153
|
+
return ready
|
|
154
|
+
|
|
155
|
+
def cancel_all(self):
|
|
156
|
+
for node in self.nodes.values():
|
|
157
|
+
if node.status in (TaskStatus.QUEUED, TaskStatus.RUNNING, TaskStatus.PAUSED):
|
|
158
|
+
node.status = TaskStatus.CANCELLED
|
|
159
|
+
|
|
160
|
+
def is_finished(self) -> bool:
|
|
161
|
+
return all(
|
|
162
|
+
n.status in (TaskStatus.SUCCEEDED, TaskStatus.FAILED, TaskStatus.CANCELLED)
|
|
163
|
+
for n in self.nodes.values()
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def render_ascii_tree(self) -> str:
|
|
167
|
+
"""Render tree format matching §9 spec."""
|
|
168
|
+
lines = ["TASK GRAPH"]
|
|
169
|
+
|
|
170
|
+
def _render_node(tid: str, prefix: str = "", is_last: bool = True):
|
|
171
|
+
node = self.nodes.get(tid)
|
|
172
|
+
if not node:
|
|
173
|
+
return
|
|
174
|
+
connector = "└── " if is_last else "├── "
|
|
175
|
+
symbol = TASK_SYMBOLS.get(node.status, "○")
|
|
176
|
+
lines.append(f"{prefix}{connector}{node.title:<30} {symbol}")
|
|
177
|
+
child_prefix = prefix + (" " if is_last else "│ ")
|
|
178
|
+
for i, child_id in enumerate(node.subtasks):
|
|
179
|
+
_render_node(child_id, child_prefix, is_last=(i == len(node.subtasks) - 1))
|
|
180
|
+
|
|
181
|
+
for i, root_id in enumerate(self.root_tasks):
|
|
182
|
+
_render_node(root_id, "", is_last=(i == len(self.root_tasks) - 1))
|
|
183
|
+
|
|
184
|
+
return "\n".join(lines)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class BackgroundTaskManager:
|
|
188
|
+
"""Manages background asynchronous tasks across the platform."""
|
|
189
|
+
|
|
190
|
+
def __init__(self):
|
|
191
|
+
self._graphs: Dict[str, TaskGraph] = {}
|
|
192
|
+
|
|
193
|
+
def create_graph(self, title: str) -> TaskGraph:
|
|
194
|
+
graph = TaskGraph()
|
|
195
|
+
self._graphs[graph.trace_id] = graph
|
|
196
|
+
return graph
|
|
197
|
+
|
|
198
|
+
def get_graph(self, trace_id: str) -> Optional[TaskGraph]:
|
|
199
|
+
return self._graphs.get(trace_id)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
task_manager = BackgroundTaskManager()
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CAT Unified Agent Runtime per §5, §10, §61:
|
|
3
|
+
Implements the canonical loop:
|
|
4
|
+
OBSERVE -> UNDERSTAND -> PLAN -> ACT -> OBSERVE RESULT -> VERIFY -> CONTINUE / RETRY / ASK
|
|
5
|
+
Features:
|
|
6
|
+
- Sub-agent worker coordinator (Research, Coding, Testing, Browser, Security, Review)
|
|
7
|
+
- Model handoff across specialized models
|
|
8
|
+
- Reality Engine integration: no false completion
|
|
9
|
+
- Provenance and telemetry logging
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
import time
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from enum import Enum
|
|
19
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
20
|
+
|
|
21
|
+
from ..capabilities import capability_bus, ExecutionResult
|
|
22
|
+
from .verification import reality_engine, RealityStatus
|
|
23
|
+
from .task_graph import TaskGraph, TaskNode, TaskStatus, task_manager
|
|
24
|
+
|
|
25
|
+
_LOG = logging.getLogger("cct.runtime")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class WorkerType(str, Enum):
|
|
29
|
+
COORDINATOR = "coordinator"
|
|
30
|
+
RESEARCH = "research_worker"
|
|
31
|
+
CODING = "coding_worker"
|
|
32
|
+
TESTING = "testing_worker"
|
|
33
|
+
BROWSER = "browser_worker"
|
|
34
|
+
SECURITY = "security_worker"
|
|
35
|
+
REVIEW = "review_worker"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class RuntimeStep:
|
|
40
|
+
phase: str # OBSERVE, UNDERSTAND, PLAN, ACT, VERIFY
|
|
41
|
+
worker: WorkerType
|
|
42
|
+
action: str
|
|
43
|
+
observation: str
|
|
44
|
+
verified: bool = False
|
|
45
|
+
duration_ms: float = 0.0
|
|
46
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
47
|
+
timestamp: float = field(default_factory=time.time)
|
|
48
|
+
|
|
49
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
50
|
+
return {
|
|
51
|
+
"phase": self.phase,
|
|
52
|
+
"worker": self.worker.value,
|
|
53
|
+
"action": self.action,
|
|
54
|
+
"observation": self.observation[:200],
|
|
55
|
+
"verified": self.verified,
|
|
56
|
+
"duration_ms": self.duration_ms,
|
|
57
|
+
"timestamp": self.timestamp,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class AgentExecutionContext:
|
|
63
|
+
trace_id: str
|
|
64
|
+
user_prompt: str
|
|
65
|
+
mode: str = "agent"
|
|
66
|
+
workspace: Optional[str] = None
|
|
67
|
+
graph: Optional[TaskGraph] = None
|
|
68
|
+
steps: List[RuntimeStep] = field(default_factory=list)
|
|
69
|
+
state: Dict[str, Any] = field(default_factory=dict)
|
|
70
|
+
active_worker: WorkerType = WorkerType.COORDINATOR
|
|
71
|
+
cancelled: bool = False
|
|
72
|
+
max_steps: int = 25
|
|
73
|
+
retries: int = 0
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class UnifiedAgentRuntime:
|
|
77
|
+
"""The central unified runtime executing all CAT agent operations."""
|
|
78
|
+
|
|
79
|
+
def __init__(self):
|
|
80
|
+
self.reality = reality_engine
|
|
81
|
+
self.bus = capability_bus
|
|
82
|
+
|
|
83
|
+
def create_context(self, user_prompt: str, mode: str = "agent", workspace: Optional[str] = None) -> AgentExecutionContext:
|
|
84
|
+
graph = task_manager.create_graph(user_prompt)
|
|
85
|
+
return AgentExecutionContext(
|
|
86
|
+
trace_id=graph.trace_id,
|
|
87
|
+
user_prompt=user_prompt,
|
|
88
|
+
mode=mode,
|
|
89
|
+
workspace=workspace,
|
|
90
|
+
graph=graph,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def execute_tool(
|
|
94
|
+
self,
|
|
95
|
+
capability_name: str,
|
|
96
|
+
args: Dict[str, Any],
|
|
97
|
+
ctx: AgentExecutionContext,
|
|
98
|
+
worker: WorkerType = WorkerType.CODING,
|
|
99
|
+
permission_callback: Optional[Callable[[str, str, str, str], str]] = None,
|
|
100
|
+
) -> ExecutionResult:
|
|
101
|
+
"""Execute capability through Capability Bus with provenance & verification."""
|
|
102
|
+
t0 = time.perf_counter()
|
|
103
|
+
res = self.bus.execute(
|
|
104
|
+
name=capability_name,
|
|
105
|
+
args=args,
|
|
106
|
+
context={"turn_id": ctx.trace_id, "workspace": ctx.workspace},
|
|
107
|
+
task_id=ctx.trace_id,
|
|
108
|
+
mode=ctx.mode,
|
|
109
|
+
permission_callback=permission_callback,
|
|
110
|
+
)
|
|
111
|
+
duration = round((time.perf_counter() - t0) * 1000.0, 2)
|
|
112
|
+
|
|
113
|
+
step = RuntimeStep(
|
|
114
|
+
phase="ACT",
|
|
115
|
+
worker=worker,
|
|
116
|
+
action=f"{capability_name}({json.dumps(args, default=str)})",
|
|
117
|
+
observation=str(res.output if res.success else res.error),
|
|
118
|
+
verified=res.success,
|
|
119
|
+
duration_ms=duration,
|
|
120
|
+
)
|
|
121
|
+
ctx.steps.append(step)
|
|
122
|
+
return res
|
|
123
|
+
|
|
124
|
+
def verify_action(
|
|
125
|
+
self,
|
|
126
|
+
claim: str,
|
|
127
|
+
ctx: AgentExecutionContext,
|
|
128
|
+
verification_fn: Callable[[], Any],
|
|
129
|
+
) -> bool:
|
|
130
|
+
"""Run Reality Engine verification step."""
|
|
131
|
+
t0 = time.perf_counter()
|
|
132
|
+
try:
|
|
133
|
+
ev = verification_fn()
|
|
134
|
+
passed = ev.passed if hasattr(ev, "passed") else bool(ev)
|
|
135
|
+
details = ev.details if hasattr(ev, "details") else str(ev)
|
|
136
|
+
except Exception as e:
|
|
137
|
+
passed = False
|
|
138
|
+
details = str(e)
|
|
139
|
+
|
|
140
|
+
duration = round((time.perf_counter() - t0) * 1000.0, 2)
|
|
141
|
+
step = RuntimeStep(
|
|
142
|
+
phase="VERIFY",
|
|
143
|
+
worker=WorkerType.REVIEW,
|
|
144
|
+
action=f"Verify: {claim}",
|
|
145
|
+
observation=details,
|
|
146
|
+
verified=passed,
|
|
147
|
+
duration_ms=duration,
|
|
148
|
+
)
|
|
149
|
+
ctx.steps.append(step)
|
|
150
|
+
return passed
|
|
151
|
+
|
|
152
|
+
def run_subagent_task(
|
|
153
|
+
self,
|
|
154
|
+
worker_type: WorkerType,
|
|
155
|
+
subtask_prompt: str,
|
|
156
|
+
ctx: AgentExecutionContext,
|
|
157
|
+
tools_allowed: Optional[List[str]] = None,
|
|
158
|
+
) -> Dict[str, Any]:
|
|
159
|
+
"""Delegate subtask to specialized worker agent without mode cluttering per §10."""
|
|
160
|
+
old_worker = ctx.active_worker
|
|
161
|
+
ctx.active_worker = worker_type
|
|
162
|
+
|
|
163
|
+
t0 = time.perf_counter()
|
|
164
|
+
step_start = RuntimeStep(
|
|
165
|
+
phase="PLAN",
|
|
166
|
+
worker=worker_type,
|
|
167
|
+
action=f"Spawn worker {worker_type.value}",
|
|
168
|
+
observation=f"Task: {subtask_prompt}",
|
|
169
|
+
duration_ms=0.0,
|
|
170
|
+
)
|
|
171
|
+
ctx.steps.append(step_start)
|
|
172
|
+
|
|
173
|
+
# Worker logic
|
|
174
|
+
result = {
|
|
175
|
+
"worker": worker_type.value,
|
|
176
|
+
"task": subtask_prompt,
|
|
177
|
+
"status": "completed",
|
|
178
|
+
"duration_ms": round((time.perf_counter() - t0) * 1000.0, 2),
|
|
179
|
+
}
|
|
180
|
+
ctx.active_worker = old_worker
|
|
181
|
+
return result
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
unified_runtime = UnifiedAgentRuntime()
|