ariadex 0.1.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.
ariadex/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Ariadex: human-supervised runtime for long-running AI coding workflows."""
2
+
3
+ __version__ = "0.1.0"
ariadex/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Run the Ariadex CLI: `python -m ariadex <command>`."""
2
+
3
+ from .cli import main
4
+
5
+ if __name__ == "__main__":
6
+ raise SystemExit(main())
ariadex/adapters.py ADDED
@@ -0,0 +1,190 @@
1
+ """Provider-neutral agent lifecycle contract and capability model.
2
+
3
+ `AgentAdapter` owns provider command construction and lifecycle mapping:
4
+ start, send, interrupt, new session, output capture, idle detection, and
5
+ terminate. It never embeds terminal transport details (those belong to
6
+ `TerminalDriver`) and never calls an LLM API.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import abc
12
+ import dataclasses
13
+ from pathlib import Path
14
+
15
+ from . import terminal as terminal_mod
16
+
17
+
18
+ @dataclasses.dataclass(frozen=True)
19
+ class Capabilities:
20
+ """Inspectable adapter capabilities consumed by the runner."""
21
+
22
+ interactive: bool = True
23
+ soft_reset: bool = False
24
+ hard_reset: bool = True
25
+ token_usage: bool = False
26
+ structured_output: bool = False
27
+ interrupt: bool = True
28
+ manual_takeover: bool = True
29
+
30
+
31
+ class AdapterError(Exception):
32
+ """Base class for typed adapter failures."""
33
+
34
+
35
+ class UnsupportedOperation(AdapterError):
36
+ """The adapter explicitly does not support the requested operation."""
37
+
38
+
39
+ class StartupError(AdapterError):
40
+ """Provider startup failed."""
41
+
42
+
43
+ class TransportError(AdapterError):
44
+ """Input delivery to a running provider failed."""
45
+
46
+
47
+ class CaptureError(AdapterError):
48
+ """Output capture from a running provider failed."""
49
+
50
+
51
+ class TerminationError(AdapterError):
52
+ """Provider termination failed."""
53
+
54
+
55
+ def select_reset(reset_mode: str, capabilities: Capabilities) -> str:
56
+ """Choose `soft` or `hard` reset from declared capabilities.
57
+
58
+ `auto` selects the strongest supported operation, preferring soft reset
59
+ when available. Raises UnsupportedOperation when the requested strength
60
+ is not declared.
61
+ """
62
+ if reset_mode == "soft":
63
+ if not capabilities.soft_reset:
64
+ raise UnsupportedOperation("adapter lacks `soft_reset` capability")
65
+ return "soft"
66
+ if reset_mode == "hard":
67
+ if not capabilities.hard_reset:
68
+ raise UnsupportedOperation("adapter lacks `hard_reset` capability")
69
+ return "hard"
70
+ if reset_mode == "auto":
71
+ if capabilities.soft_reset:
72
+ return "soft"
73
+ if capabilities.hard_reset:
74
+ return "hard"
75
+ raise UnsupportedOperation(
76
+ "adapter declares neither `soft_reset` nor `hard_reset`"
77
+ )
78
+ raise AdapterError(
79
+ f"invalid reset mode `{reset_mode}`: expected soft, hard, or auto"
80
+ )
81
+
82
+
83
+ class AgentAdapter(abc.ABC):
84
+ """One lifecycle contract implemented per Coding CLI provider."""
85
+
86
+ provider_name: str = "unknown"
87
+ launch_command: tuple[str, ...] = ()
88
+ new_session_input: str | None = None
89
+
90
+ def __init__(
91
+ self,
92
+ driver: terminal_mod.TerminalDriver,
93
+ session_name: str,
94
+ workdir: Path | str,
95
+ ) -> None:
96
+ self.driver = driver
97
+ self.session_name = session_name
98
+ self.workdir = workdir
99
+
100
+ @property
101
+ @abc.abstractmethod
102
+ def capabilities(self) -> Capabilities:
103
+ """Declared capabilities; the runner must consult these."""
104
+
105
+ def start(self) -> str:
106
+ """Create or connect the session and launch the provider.
107
+
108
+ Returns `created` or `connected`. Wraps driver failures so callers
109
+ only handle AdapterError.
110
+ """
111
+ try:
112
+ return self.driver.create_or_connect(
113
+ self.session_name, self.workdir, list(self.launch_command)
114
+ )
115
+ except terminal_mod.TerminalError as exc:
116
+ raise StartupError(f"{self.provider_name} startup failed: {exc}") from exc
117
+
118
+ def send(self, text: str) -> None:
119
+ try:
120
+ self.driver.send_input(self.session_name, text)
121
+ except terminal_mod.TmuxNotAvailable as exc:
122
+ raise TransportError(str(exc)) from exc
123
+ except terminal_mod.SessionMissing as exc:
124
+ raise TransportError(str(exc)) from exc
125
+ except terminal_mod.TerminalError as exc:
126
+ raise TransportError(
127
+ f"failed to deliver input to {self.provider_name}: {exc}"
128
+ ) from exc
129
+
130
+ def interrupt(self) -> None:
131
+ if not self.capabilities.interrupt:
132
+ raise UnsupportedOperation(
133
+ f"{self.provider_name} lacks `interrupt` capability"
134
+ )
135
+ try:
136
+ self.driver.interrupt(self.session_name)
137
+ except terminal_mod.TerminalError as exc:
138
+ raise TransportError(
139
+ f"failed to interrupt {self.provider_name}: {exc}"
140
+ ) from exc
141
+
142
+ def new_session(self) -> None:
143
+ """Soft reset via the provider's new-session input.
144
+
145
+ Reports the missing capability so the runner can fall back to hard
146
+ reset (terminate and restart) instead of assuming `/new` works.
147
+ """
148
+ if not self.capabilities.soft_reset or self.new_session_input is None:
149
+ raise UnsupportedOperation(
150
+ f"{self.provider_name} lacks `soft_reset` capability; "
151
+ "use hard reset (terminate and restart)"
152
+ )
153
+ try:
154
+ self.driver.send_input(self.session_name, self.new_session_input)
155
+ except terminal_mod.TerminalError as exc:
156
+ raise TransportError(
157
+ f"failed to reset {self.provider_name} session: {exc}"
158
+ ) from exc
159
+
160
+ def capture_output(self) -> str:
161
+ """Return raw pane output verbatim for the logger.
162
+
163
+ Never interprets provider text as completion.
164
+ """
165
+ try:
166
+ return self.driver.capture(self.session_name)
167
+ except terminal_mod.TerminalError as exc:
168
+ raise CaptureError(
169
+ f"failed to capture {self.provider_name} output: {exc}"
170
+ ) from exc
171
+
172
+ def is_idle(self) -> bool:
173
+ """Conservative default: never claim idle without evidence."""
174
+ return False
175
+
176
+ def get_usage(self) -> dict | None:
177
+ """Provider token/cost usage, or None when not exposed.
178
+
179
+ Metrics must record `usage: unavailable` in that case and never
180
+ invent an estimate.
181
+ """
182
+ return None
183
+
184
+ def terminate(self) -> None:
185
+ try:
186
+ self.driver.terminate(self.session_name)
187
+ except terminal_mod.TerminalError as exc:
188
+ raise TerminationError(
189
+ f"failed to terminate {self.provider_name}: {exc}"
190
+ ) from exc