forgeoptimizer 1.0.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.
Files changed (156) hide show
  1. forgecli/__init__.py +4 -0
  2. forgecli/build/__init__.py +135 -0
  3. forgecli/build/apply.py +218 -0
  4. forgecli/build/caveman_optimize.py +37 -0
  5. forgecli/build/diff_extract.py +218 -0
  6. forgecli/build/llm.py +167 -0
  7. forgecli/build/optimize.py +37 -0
  8. forgecli/build/pipeline.py +76 -0
  9. forgecli/build/retrieval.py +157 -0
  10. forgecli/build/summarize.py +76 -0
  11. forgecli/build/test_run.py +75 -0
  12. forgecli/builder/__init__.py +11 -0
  13. forgecli/builder/builder.py +53 -0
  14. forgecli/builder/editor.py +36 -0
  15. forgecli/builder/formatter.py +31 -0
  16. forgecli/cli/__init__.py +5 -0
  17. forgecli/cli/bootstrap.py +281 -0
  18. forgecli/cli/commands_graph.py +137 -0
  19. forgecli/cli/commands_wrappers.py +63 -0
  20. forgecli/cli/main.py +122 -0
  21. forgecli/cli/ui.py +56 -0
  22. forgecli/config/__init__.py +28 -0
  23. forgecli/config/loader.py +85 -0
  24. forgecli/config/settings.py +204 -0
  25. forgecli/config/writer.py +90 -0
  26. forgecli/core/__init__.py +35 -0
  27. forgecli/core/container.py +68 -0
  28. forgecli/core/context.py +54 -0
  29. forgecli/core/credentials.py +114 -0
  30. forgecli/core/errors.py +27 -0
  31. forgecli/core/events.py +67 -0
  32. forgecli/core/logging.py +47 -0
  33. forgecli/core/models.py +159 -0
  34. forgecli/core/plugins.py +56 -0
  35. forgecli/core/service.py +22 -0
  36. forgecli/docs/__init__.py +5 -0
  37. forgecli/docs/generator.py +119 -0
  38. forgecli/engine/__init__.py +105 -0
  39. forgecli/engine/context.py +171 -0
  40. forgecli/engine/defaults.py +89 -0
  41. forgecli/engine/events.py +185 -0
  42. forgecli/engine/execution.py +526 -0
  43. forgecli/engine/plugins.py +146 -0
  44. forgecli/engine/runner.py +155 -0
  45. forgecli/engine/stages/__init__.py +33 -0
  46. forgecli/engine/stages/caveman_optimizer.py +47 -0
  47. forgecli/engine/stages/context_optimizer.py +44 -0
  48. forgecli/engine/stages/execution_engine_stage.py +108 -0
  49. forgecli/engine/stages/git_engine.py +30 -0
  50. forgecli/engine/stages/intent_analyzer.py +38 -0
  51. forgecli/engine/stages/model_router.py +66 -0
  52. forgecli/engine/stages/planning_engine.py +45 -0
  53. forgecli/engine/stages/repository_analyzer.py +54 -0
  54. forgecli/engine/stages/validation_engine.py +89 -0
  55. forgecli/git/__init__.py +9 -0
  56. forgecli/git/repo.py +55 -0
  57. forgecli/git/service.py +45 -0
  58. forgecli/graph/__init__.py +56 -0
  59. forgecli/graph/backend_graphify.py +453 -0
  60. forgecli/graph/edge.py +19 -0
  61. forgecli/graph/graph.py +85 -0
  62. forgecli/graph/graphify.py +412 -0
  63. forgecli/graph/indexer.py +82 -0
  64. forgecli/graph/node.py +47 -0
  65. forgecli/graph/repository.py +164 -0
  66. forgecli/memory/__init__.py +12 -0
  67. forgecli/memory/cache.py +61 -0
  68. forgecli/memory/history.py +138 -0
  69. forgecli/memory/store.py +87 -0
  70. forgecli/optimizer/__init__.py +14 -0
  71. forgecli/optimizer/caveman/__init__.py +173 -0
  72. forgecli/optimizer/caveman/cli.py +64 -0
  73. forgecli/optimizer/caveman/decorator.py +67 -0
  74. forgecli/optimizer/caveman/factory.py +51 -0
  75. forgecli/optimizer/caveman/ruleset.py +156 -0
  76. forgecli/optimizer/caveman/state.py +52 -0
  77. forgecli/optimizer/chunker.py +85 -0
  78. forgecli/optimizer/optimizer.py +81 -0
  79. forgecli/optimizer/ponytail/__init__.py +183 -0
  80. forgecli/optimizer/ponytail/cli.py +168 -0
  81. forgecli/optimizer/ponytail/decorator.py +70 -0
  82. forgecli/optimizer/ponytail/factory.py +51 -0
  83. forgecli/optimizer/ponytail/ruleset.py +168 -0
  84. forgecli/optimizer/ponytail/state.py +53 -0
  85. forgecli/optimizer/ranker.py +37 -0
  86. forgecli/optimizer/summarizer.py +44 -0
  87. forgecli/orchestrator/__init__.py +707 -0
  88. forgecli/planner/__init__.py +56 -0
  89. forgecli/planner/agent.py +61 -0
  90. forgecli/planner/plan.py +65 -0
  91. forgecli/planner/planner.py +17 -0
  92. forgecli/planner/render.py +271 -0
  93. forgecli/planner/serialize.py +106 -0
  94. forgecli/planner/software.py +834 -0
  95. forgecli/platform/__init__.py +91 -0
  96. forgecli/platform/core.py +201 -0
  97. forgecli/platform/deps.py +354 -0
  98. forgecli/platform/paths.py +236 -0
  99. forgecli/platform/shell.py +176 -0
  100. forgecli/platform/update.py +252 -0
  101. forgecli/plugins/__init__.py +251 -0
  102. forgecli/prompts/__init__.py +11 -0
  103. forgecli/prompts/loader.py +28 -0
  104. forgecli/prompts/registry.py +32 -0
  105. forgecli/prompts/renderer.py +23 -0
  106. forgecli/providers/__init__.py +39 -0
  107. forgecli/providers/anthropic.py +206 -0
  108. forgecli/providers/base.py +206 -0
  109. forgecli/providers/builtin.py +26 -0
  110. forgecli/providers/conversation.py +78 -0
  111. forgecli/providers/google.py +295 -0
  112. forgecli/providers/http_base.py +211 -0
  113. forgecli/providers/mock.py +113 -0
  114. forgecli/providers/openai.py +202 -0
  115. forgecli/providers/openai_compatible.py +728 -0
  116. forgecli/providers/router.py +345 -0
  117. forgecli/providers/router_state.py +89 -0
  118. forgecli/review/__init__.py +45 -0
  119. forgecli/review/analyzer.py +124 -0
  120. forgecli/review/analyzers/__init__.py +1 -0
  121. forgecli/review/analyzers/architecture.py +258 -0
  122. forgecli/review/analyzers/complexity.py +167 -0
  123. forgecli/review/analyzers/dead_code.py +262 -0
  124. forgecli/review/analyzers/duplicates.py +163 -0
  125. forgecli/review/analyzers/performance.py +165 -0
  126. forgecli/review/analyzers/security.py +251 -0
  127. forgecli/review/finding.py +68 -0
  128. forgecli/review/report.py +311 -0
  129. forgecli/review/repository.py +131 -0
  130. forgecli/review/suggestions.py +98 -0
  131. forgecli/runtime/__init__.py +6 -0
  132. forgecli/runtime/cache_store.py +77 -0
  133. forgecli/runtime/prepare.py +199 -0
  134. forgecli/runtime/wrappers.py +152 -0
  135. forgecli/sdk/__init__.py +132 -0
  136. forgecli/sdk/events.py +206 -0
  137. forgecli/sdk/interfaces.py +282 -0
  138. forgecli/sdk/loader.py +243 -0
  139. forgecli/sdk/manager.py +693 -0
  140. forgecli/sdk/manifest.py +397 -0
  141. forgecli/sdk/sandbox.py +197 -0
  142. forgecli/sdk/version.py +316 -0
  143. forgecli/templates/__init__.py +9 -0
  144. forgecli/templates/engine.py +37 -0
  145. forgecli/templates/registry.py +32 -0
  146. forgecli/utils/__init__.py +19 -0
  147. forgecli/utils/fs.py +91 -0
  148. forgecli/utils/ids.py +11 -0
  149. forgecli/utils/io.py +27 -0
  150. forgecli/utils/paths.py +70 -0
  151. forgecli/utils/timing.py +25 -0
  152. forgeoptimizer-1.0.0.dist-info/METADATA +169 -0
  153. forgeoptimizer-1.0.0.dist-info/RECORD +156 -0
  154. forgeoptimizer-1.0.0.dist-info/WHEEL +4 -0
  155. forgeoptimizer-1.0.0.dist-info/entry_points.txt +2 -0
  156. forgeoptimizer-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,171 @@
1
+ """Typed context that flows through the engine's eight stages.
2
+
3
+ Each stage reads from the context, mutates one or two fields, and
4
+ returns a :class:`~forgecli.engine.execution.StageResult`. The
5
+ context is the *only* mechanism stages use to share data — there
6
+ are no hidden globals.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import time
12
+ import uuid
13
+ from collections.abc import Mapping
14
+ from dataclasses import dataclass, field
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ from forgecli.plugins import Intent
19
+
20
+ # ---------------------------------------------------------------------------
21
+ # Per-stage payloads
22
+ # ---------------------------------------------------------------------------
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class IntentAnalysis:
27
+ """The output of the Intent Analyzer stage."""
28
+
29
+ intent: Intent
30
+ confidence: float
31
+ rationale: tuple[str, ...] = ()
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class RetrievalResult:
36
+ """The output of the Repository Analyzer stage."""
37
+
38
+ query: str
39
+ matched_nodes: tuple[Mapping[str, Any], ...] = ()
40
+ context_text: str = ""
41
+ artifacts: dict[str, Path] = field(default_factory=dict)
42
+ notes: tuple[str, ...] = ()
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class ModelSelection:
47
+ """The output of the Model Router stage."""
48
+
49
+ provider: str
50
+ model: str
51
+ mode: str = "explicit" # "explicit" | "cheapest" | "fallback"
52
+ cost_in: float = 0.0
53
+ cost_out: float = 0.0
54
+
55
+
56
+ @dataclass(frozen=True)
57
+ class StageLog:
58
+ """A single record of how a stage performed.
59
+
60
+ The engine accumulates these on :class:`EngineContext` so
61
+ callers can render a final report.
62
+ """
63
+
64
+ stage: str
65
+ status: str
66
+ started_at: float
67
+ finished_at: float | None = None
68
+ notes: tuple[str, ...] = ()
69
+ error: str | None = None
70
+
71
+ @property
72
+ def duration_seconds(self) -> float:
73
+ if self.finished_at is None:
74
+ return 0.0
75
+ return max(0.0, self.finished_at - self.started_at)
76
+
77
+
78
+ # ---------------------------------------------------------------------------
79
+ # Engine context
80
+ # ---------------------------------------------------------------------------
81
+
82
+
83
+ @dataclass
84
+ class EngineContext:
85
+ """Mutable state shared by every stage of the engine.
86
+
87
+ The context is constructed by :class:`EngineBuilder` and
88
+ passed to each :class:`Stage`. Stages may set the
89
+ ``intent_analysis`` / ``retrieval`` / ``model_selection`` /
90
+ ``plan`` / ``response`` / ``diff_text`` / ``applied_files`` /
91
+ ``test_*`` fields; downstream stages read them.
92
+ """
93
+
94
+ prompt: str
95
+ cwd: Path
96
+ run_id: str = field(default_factory=lambda: uuid.uuid4().hex[:12])
97
+ started_at: float = field(default_factory=time.time)
98
+
99
+ # Stage 1: Intent
100
+ intent_analysis: IntentAnalysis | None = None
101
+ # Stage 2: Retrieval
102
+ retrieval: RetrievalResult | None = None
103
+ # Stage 3: Caveman optimization (runs before context optimization)
104
+ caveman_optimized_request: Any = None
105
+ caveman_optimized_notes: tuple[str, ...] = ()
106
+ # Stage 4: Context optimization
107
+ optimized_request: Any = None
108
+ optimized_notes: tuple[str, ...] = ()
109
+ # Stage 5: Plan
110
+ plan: Any = None # SoftwarePlan; kept loose to avoid an import cycle
111
+ # Stage 5: Model
112
+ model_selection: ModelSelection | None = None
113
+ # Stage 6: Execution
114
+ response: Any = None
115
+ # Stage 7: Validation
116
+ diff_text: str = ""
117
+ applied_files: list[Path] = field(default_factory=list)
118
+ test_stdout: str = ""
119
+ test_stderr: str = ""
120
+ test_returncode: int | None = None
121
+ fix_attempts: int = 0
122
+ # Stage 8: Git
123
+ staged: bool = False
124
+ pushed: bool = False
125
+
126
+ # Free-form extras for plugins / advanced stages.
127
+ extras: dict[str, Any] = field(default_factory=dict)
128
+
129
+ # Per-stage log (append-only).
130
+ log: list[StageLog] = field(default_factory=list)
131
+
132
+ def to_log_dict(self) -> dict[str, Any]:
133
+ """Return a JSON-serializable snapshot of the context (no Paths)."""
134
+ return {
135
+ "run_id": self.run_id,
136
+ "started_at": self.started_at,
137
+ "intent": self.intent_analysis.intent.value
138
+ if self.intent_analysis
139
+ else None,
140
+ "intent_confidence": self.intent_analysis.confidence
141
+ if self.intent_analysis
142
+ else 0.0,
143
+ "retrieval_query": self.retrieval.query if self.retrieval else None,
144
+ "retrieval_match_count": len(self.retrieval.matched_nodes)
145
+ if self.retrieval
146
+ else 0,
147
+ "caveman_optimized_notes": list(self.caveman_optimized_notes),
148
+ "optimized_notes": list(self.optimized_notes),
149
+ "has_plan": self.plan is not None,
150
+ "model": self.model_selection.model if self.model_selection else None,
151
+ "provider": self.model_selection.provider
152
+ if self.model_selection
153
+ else None,
154
+ "response_present": self.response is not None,
155
+ "diff_length": len(self.diff_text),
156
+ "applied_files": [str(p) for p in self.applied_files],
157
+ "test_returncode": self.test_returncode,
158
+ "fix_attempts": self.fix_attempts,
159
+ "staged": self.staged,
160
+ "pushed": self.pushed,
161
+ "stage_count": len(self.log),
162
+ }
163
+
164
+
165
+ __all__ = [
166
+ "EngineContext",
167
+ "IntentAnalysis",
168
+ "ModelSelection",
169
+ "RetrievalResult",
170
+ "StageLog",
171
+ ]
@@ -0,0 +1,89 @@
1
+ """Default stage wiring for the Execution Engine.
2
+
3
+ Provides :func:`default_registry` which populates a
4
+ :class:`~forgecli.engine.execution.StageRegistry` with all eight
5
+ DEFAULT_PIPELINE stages. Callers can then build an engine with::
6
+
7
+ registry = default_registry()
8
+ engine = ExecutionEngine.from_registry(registry)
9
+ result = await engine.run(context)
10
+
11
+ Individual stages can be overridden via
12
+ :meth:`StageRegistry.replace` before building the engine.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import Any
18
+
19
+ from forgecli.engine.execution import Stage, StageRegistry
20
+ from forgecli.engine.stages import (
21
+ CavemanOptimizerStage,
22
+ ContextOptimizerStage,
23
+ ExecutionEngineStage,
24
+ GitEngineStage,
25
+ IntentAnalyzerStage,
26
+ ModelRouterStage,
27
+ PlanningEngineStage,
28
+ RepositoryAnalyzerStage,
29
+ ValidationEngineStage,
30
+ )
31
+
32
+
33
+ def default_registry(
34
+ *,
35
+ provider: Any = None,
36
+ optimizer: Any = None,
37
+ caveman_optimizer: Any = None,
38
+ graph: Any = None,
39
+ classifier: Any = None,
40
+ router: Any = None,
41
+ test_command: str | None = None,
42
+ plugin_registry: Any = None,
43
+ **stage_kwargs: Any,
44
+ ) -> StageRegistry:
45
+ """Create a :class:`StageRegistry` with all nine default pipeline stages.
46
+
47
+ Keyword arguments are forwarded to stage constructors:
48
+
49
+ * ``provider`` → :class:`ExecutionEngineStage`
50
+ * ``optimizer`` → :class:`ContextOptimizerStage`
51
+ * ``caveman_optimizer`` → :class:`CavemanOptimizerStage`
52
+ * ``graph`` → :class:`RepositoryAnalyzerStage`
53
+ * ``classifier`` → :class:`IntentAnalyzerStage`
54
+ * ``router`` → :class:`ModelRouterStage`
55
+ * ``test_command`` → :class:`ValidationEngineStage`
56
+ * ``plugin_registry`` → a :class:`~forgecli.plugins.PluginRegistry` whose
57
+ stages are bulk-loaded into the registry after default stages.
58
+ The plugin registry is also linked so future
59
+ :meth:`~forgecli.plugins.PluginRegistry.register_stage` /
60
+ :meth:`~forgecli.plugins.PluginRegistry.replace_stage` calls
61
+ are mirrored into this registry.
62
+
63
+ Remaining kwargs are passed to stages that accept them by name
64
+ (e.g. ``auto_commit`` for :class:`GitEngineStage`).
65
+ """
66
+ registry = StageRegistry()
67
+
68
+ stages: list[Stage] = [
69
+ IntentAnalyzerStage(classifier=classifier),
70
+ RepositoryAnalyzerStage(graph=graph),
71
+ CavemanOptimizerStage(optimizer=caveman_optimizer),
72
+ ContextOptimizerStage(optimizer=optimizer),
73
+ PlanningEngineStage(**{k: v for k, v in stage_kwargs.items() if k in ("enabled",)}),
74
+ ModelRouterStage(router=router),
75
+ ExecutionEngineStage(provider=provider),
76
+ ValidationEngineStage(test_command=test_command),
77
+ GitEngineStage(),
78
+ ]
79
+
80
+ for stage in stages:
81
+ registry.register(stage)
82
+
83
+ if plugin_registry is not None:
84
+ plugin_registry.link_engine_registry(registry)
85
+
86
+ return registry
87
+
88
+
89
+ __all__ = ["default_registry"]
@@ -0,0 +1,185 @@
1
+ """Structured event bus for the Execution Engine.
2
+
3
+ The :class:`EventBus` is a tiny pub/sub dispatcher for the four
4
+ event kinds the engine emits:
5
+
6
+ * :class:`StageEvent` — a stage transitioned to a new state.
7
+ * :class:`ProgressEvent` — fractional progress within a stage.
8
+ * :class:`TextLogEvent` — a free-form log line (the engine's
9
+ "streaming log" surface).
10
+ * :class:`EngineEvent` — the abstract base; every concrete event
11
+ inherits from it.
12
+
13
+ Subscribers can be sync or async; the engine handles fan-out
14
+ sequentially. A single :class:`asyncio.Event` (the cancellation
15
+ token) is checked between stages and during long operations so a
16
+ caller can abort cleanly.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import asyncio
22
+ import contextlib
23
+ from collections.abc import Awaitable, Callable
24
+ from dataclasses import dataclass, field
25
+ from datetime import UTC, datetime
26
+ from enum import Enum
27
+
28
+
29
+ class LogLevel(str, Enum):
30
+ """Severity for :class:`TextLogEvent`."""
31
+
32
+ DEBUG = "debug"
33
+ INFO = "info"
34
+ WARN = "warn"
35
+ ERROR = "error"
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class EngineEvent:
40
+ """Abstract base of every event the engine emits."""
41
+
42
+ timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
43
+ run_id: str = ""
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class StageEvent(EngineEvent):
48
+ """A stage transitioned to a new state."""
49
+
50
+ stage: str = ""
51
+ status: str = "running" # "running" | "succeeded" | "failed" | "skipped" | "retrying"
52
+ attempt: int = 1
53
+ note: str | None = None
54
+
55
+
56
+ @dataclass(frozen=True)
57
+ class ProgressEvent(EngineEvent):
58
+ """Fractional progress within a stage (0.0 - 1.0)."""
59
+
60
+ stage: str = ""
61
+ progress: float = 0.0
62
+ message: str | None = None
63
+
64
+
65
+ @dataclass(frozen=True)
66
+ class TextLogEvent(EngineEvent):
67
+ """A free-form log line emitted by a stage."""
68
+
69
+ level: LogLevel = LogLevel.INFO
70
+ source: str = ""
71
+ message: str = ""
72
+
73
+
74
+ EventHandler = Callable[[EngineEvent], "None | Awaitable[None]"]
75
+
76
+
77
+ class EventBus:
78
+ """In-process pub/sub bus for engine events.
79
+
80
+ Subscribers are registered per event class. Handlers may be sync
81
+ or async; the engine awaits any coroutines it gets back. A single
82
+ :class:`asyncio.Event` (the cancellation token) is checked between
83
+ stages so the caller can abort cleanly.
84
+ """
85
+
86
+ def __init__(self) -> None:
87
+ self._subscribers: dict[type, list[EventHandler]] = {}
88
+ self.cancellation = asyncio.Event()
89
+ self.history: list[EngineEvent] = []
90
+
91
+ # ------------------------------------------------------------------
92
+ # Subscription
93
+ # ------------------------------------------------------------------
94
+
95
+ def subscribe(self, event_cls: type[EngineEvent], handler: EventHandler) -> None:
96
+ self._subscribers.setdefault(event_cls, []).append(handler)
97
+
98
+ def unsubscribe(self, event_cls: type[EngineEvent], handler: EventHandler) -> None:
99
+ bucket = self._subscribers.get(event_cls)
100
+ if not bucket:
101
+ return
102
+ with contextlib.suppress(ValueError):
103
+ bucket.remove(handler)
104
+
105
+ # ------------------------------------------------------------------
106
+ # Cancellation
107
+ # ------------------------------------------------------------------
108
+
109
+ def cancel(self) -> None:
110
+ """Request cancellation. The engine checks ``cancellation`` between stages."""
111
+ self.cancellation.set()
112
+
113
+ def reset_cancellation(self) -> None:
114
+ self.cancellation.clear()
115
+
116
+ def is_cancelled(self) -> bool:
117
+ return self.cancellation.is_set()
118
+
119
+ # ------------------------------------------------------------------
120
+ # Publishing
121
+ # ------------------------------------------------------------------
122
+
123
+ def publish(self, event: EngineEvent) -> None:
124
+ """Publish synchronously; async handlers are scheduled."""
125
+ self.history.append(event)
126
+ for handler in list(self._subscribers.get(type(event), ())):
127
+ try:
128
+ result = handler(event)
129
+ except Exception:
130
+ continue
131
+ if asyncio.iscoroutine(result):
132
+ # Best-effort: schedule on the running loop if any.
133
+ try:
134
+ loop = asyncio.get_running_loop()
135
+ task = loop.create_task(result)
136
+ task.add_done_callback(self._discard_task)
137
+ except RuntimeError:
138
+ pass
139
+
140
+ @staticmethod
141
+ def _discard_task(task: asyncio.Task) -> None:
142
+ # Placeholder to keep a strong reference to the task until it
143
+ # completes. Without this, asyncio may garbage-collect the
144
+ # task mid-flight and emit "Task was destroyed but it is
145
+ # pending" warnings.
146
+ return None
147
+
148
+ async def publish_and_drain(self, event: EngineEvent) -> None:
149
+ """Publish and await any async handler results."""
150
+ self.history.append(event)
151
+ for handler in list(self._subscribers.get(type(event), ())):
152
+ try:
153
+ result = handler(event)
154
+ except Exception:
155
+ continue
156
+ if asyncio.iscoroutine(result):
157
+ await result
158
+
159
+ # ------------------------------------------------------------------
160
+ # Helpers
161
+ # ------------------------------------------------------------------
162
+
163
+ def drain(self) -> list[EngineEvent]:
164
+ return list(self.history)
165
+
166
+
167
+ # ---------------------------------------------------------------------------
168
+ # Cancellation sentinel exception
169
+ # ---------------------------------------------------------------------------
170
+
171
+
172
+ class EngineCancelledError(Exception):
173
+ """Raised by the engine when the cancellation token is set."""
174
+
175
+
176
+ __all__ = [
177
+ "EngineCancelledError",
178
+ "EngineEvent",
179
+ "EventBus",
180
+ "EventHandler",
181
+ "LogLevel",
182
+ "ProgressEvent",
183
+ "StageEvent",
184
+ "TextLogEvent",
185
+ ]