yee88 0.3.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 (103) hide show
  1. yee88/__init__.py +1 -0
  2. yee88/api.py +116 -0
  3. yee88/backends.py +25 -0
  4. yee88/backends_helpers.py +14 -0
  5. yee88/cli/__init__.py +228 -0
  6. yee88/cli/config.py +320 -0
  7. yee88/cli/doctor.py +173 -0
  8. yee88/cli/init.py +113 -0
  9. yee88/cli/onboarding_cmd.py +126 -0
  10. yee88/cli/plugins.py +196 -0
  11. yee88/cli/run.py +419 -0
  12. yee88/cli/topic.py +355 -0
  13. yee88/commands.py +134 -0
  14. yee88/config.py +142 -0
  15. yee88/config_migrations.py +124 -0
  16. yee88/config_watch.py +146 -0
  17. yee88/context.py +9 -0
  18. yee88/directives.py +146 -0
  19. yee88/engines.py +53 -0
  20. yee88/events.py +170 -0
  21. yee88/ids.py +17 -0
  22. yee88/lockfile.py +158 -0
  23. yee88/logging.py +283 -0
  24. yee88/markdown.py +298 -0
  25. yee88/model.py +77 -0
  26. yee88/plugins.py +312 -0
  27. yee88/presenter.py +25 -0
  28. yee88/progress.py +99 -0
  29. yee88/router.py +113 -0
  30. yee88/runner.py +712 -0
  31. yee88/runner_bridge.py +619 -0
  32. yee88/runners/__init__.py +1 -0
  33. yee88/runners/claude.py +483 -0
  34. yee88/runners/codex.py +656 -0
  35. yee88/runners/mock.py +221 -0
  36. yee88/runners/opencode.py +505 -0
  37. yee88/runners/pi.py +523 -0
  38. yee88/runners/run_options.py +39 -0
  39. yee88/runners/tool_actions.py +90 -0
  40. yee88/runtime_loader.py +207 -0
  41. yee88/scheduler.py +159 -0
  42. yee88/schemas/__init__.py +1 -0
  43. yee88/schemas/claude.py +238 -0
  44. yee88/schemas/codex.py +169 -0
  45. yee88/schemas/opencode.py +51 -0
  46. yee88/schemas/pi.py +117 -0
  47. yee88/settings.py +360 -0
  48. yee88/telegram/__init__.py +20 -0
  49. yee88/telegram/api_models.py +37 -0
  50. yee88/telegram/api_schemas.py +152 -0
  51. yee88/telegram/backend.py +163 -0
  52. yee88/telegram/bridge.py +425 -0
  53. yee88/telegram/chat_prefs.py +242 -0
  54. yee88/telegram/chat_sessions.py +112 -0
  55. yee88/telegram/client.py +409 -0
  56. yee88/telegram/client_api.py +539 -0
  57. yee88/telegram/commands/__init__.py +12 -0
  58. yee88/telegram/commands/agent.py +196 -0
  59. yee88/telegram/commands/cancel.py +116 -0
  60. yee88/telegram/commands/dispatch.py +111 -0
  61. yee88/telegram/commands/executor.py +449 -0
  62. yee88/telegram/commands/file_transfer.py +586 -0
  63. yee88/telegram/commands/handlers.py +45 -0
  64. yee88/telegram/commands/media.py +143 -0
  65. yee88/telegram/commands/menu.py +139 -0
  66. yee88/telegram/commands/model.py +215 -0
  67. yee88/telegram/commands/overrides.py +159 -0
  68. yee88/telegram/commands/parse.py +30 -0
  69. yee88/telegram/commands/plan.py +16 -0
  70. yee88/telegram/commands/reasoning.py +234 -0
  71. yee88/telegram/commands/reply.py +23 -0
  72. yee88/telegram/commands/topics.py +332 -0
  73. yee88/telegram/commands/trigger.py +143 -0
  74. yee88/telegram/context.py +140 -0
  75. yee88/telegram/engine_defaults.py +86 -0
  76. yee88/telegram/engine_overrides.py +105 -0
  77. yee88/telegram/files.py +178 -0
  78. yee88/telegram/loop.py +1822 -0
  79. yee88/telegram/onboarding.py +1088 -0
  80. yee88/telegram/outbox.py +177 -0
  81. yee88/telegram/parsing.py +239 -0
  82. yee88/telegram/render.py +198 -0
  83. yee88/telegram/state_store.py +88 -0
  84. yee88/telegram/topic_state.py +334 -0
  85. yee88/telegram/topics.py +256 -0
  86. yee88/telegram/trigger_mode.py +68 -0
  87. yee88/telegram/types.py +63 -0
  88. yee88/telegram/voice.py +110 -0
  89. yee88/transport.py +53 -0
  90. yee88/transport_runtime.py +323 -0
  91. yee88/transports.py +76 -0
  92. yee88/utils/__init__.py +1 -0
  93. yee88/utils/git.py +87 -0
  94. yee88/utils/json_state.py +21 -0
  95. yee88/utils/paths.py +47 -0
  96. yee88/utils/streams.py +44 -0
  97. yee88/utils/subprocess.py +86 -0
  98. yee88/worktrees.py +135 -0
  99. yee88-0.3.0.dist-info/METADATA +116 -0
  100. yee88-0.3.0.dist-info/RECORD +103 -0
  101. yee88-0.3.0.dist-info/WHEEL +4 -0
  102. yee88-0.3.0.dist-info/entry_points.txt +11 -0
  103. yee88-0.3.0.dist-info/licenses/LICENSE +21 -0
yee88/runners/mock.py ADDED
@@ -0,0 +1,221 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import uuid
5
+ from collections.abc import AsyncIterator, Awaitable, Callable, Iterable
6
+ from dataclasses import dataclass, replace
7
+
8
+ import anyio
9
+
10
+ from ..model import (
11
+ ActionEvent,
12
+ CompletedEvent,
13
+ EngineId,
14
+ ResumeToken,
15
+ StartedEvent,
16
+ TakopiEvent,
17
+ )
18
+ from ..runner import ResumeTokenMixin, Runner, SessionLockMixin
19
+
20
+ ENGINE: EngineId = "mock"
21
+
22
+
23
+ @dataclass(frozen=True, slots=True)
24
+ class Emit:
25
+ event: TakopiEvent
26
+ at: float | None = None
27
+
28
+
29
+ @dataclass(frozen=True, slots=True)
30
+ class Advance:
31
+ now: float
32
+
33
+
34
+ @dataclass(frozen=True, slots=True)
35
+ class Sleep:
36
+ seconds: float
37
+
38
+
39
+ @dataclass(frozen=True, slots=True)
40
+ class Wait:
41
+ event: anyio.Event
42
+
43
+
44
+ @dataclass(frozen=True, slots=True)
45
+ class Return:
46
+ answer: str
47
+
48
+
49
+ @dataclass(frozen=True, slots=True)
50
+ class Raise:
51
+ error: Exception
52
+
53
+
54
+ type ScriptStep = Emit | Advance | Sleep | Wait | Return | Raise
55
+
56
+
57
+ def _resume_token(engine: EngineId, value: str | None) -> ResumeToken:
58
+ return ResumeToken(engine=engine, value=value or uuid.uuid4().hex)
59
+
60
+
61
+ class MockRunner(SessionLockMixin, ResumeTokenMixin, Runner):
62
+ engine: EngineId
63
+
64
+ def __init__(
65
+ self,
66
+ *,
67
+ events: Iterable[TakopiEvent] | None = None,
68
+ answer: str = "",
69
+ engine: EngineId = ENGINE,
70
+ resume_value: str | None = None,
71
+ title: str | None = None,
72
+ ) -> None:
73
+ self.engine = engine
74
+ self._events = list(events or [])
75
+ self._answer = answer
76
+ self._resume_value = resume_value
77
+ self.title = title or str(engine).title()
78
+ engine_name = re.escape(str(engine))
79
+ self.resume_re = re.compile(
80
+ rf"(?im)^\s*`?{engine_name}\s+resume\s+(?P<token>[^`\s]+)`?\s*$"
81
+ )
82
+
83
+ async def run(
84
+ self, prompt: str, resume: ResumeToken | None
85
+ ) -> AsyncIterator[TakopiEvent]:
86
+ token_value = None
87
+ if resume is not None:
88
+ if resume.engine != self.engine:
89
+ raise RuntimeError(
90
+ f"resume token is for engine {resume.engine!r}, not {self.engine!r}"
91
+ )
92
+ token_value = resume.value
93
+ if token_value is None:
94
+ token_value = self._resume_value
95
+ token = _resume_token(self.engine, token_value)
96
+ session_evt = StartedEvent(
97
+ engine=self.engine,
98
+ resume=token,
99
+ title=self.title,
100
+ )
101
+ lock = self.lock_for(token)
102
+ async with lock:
103
+ yield session_evt
104
+
105
+ for event in self._events:
106
+ event_out: TakopiEvent = event
107
+ if (
108
+ isinstance(event_out, ActionEvent)
109
+ and event_out.phase == "completed"
110
+ and event_out.ok is None
111
+ ):
112
+ event_out = replace(event_out, ok=True)
113
+ yield event_out
114
+ await anyio.sleep(0)
115
+
116
+ yield CompletedEvent(
117
+ engine=self.engine,
118
+ resume=token,
119
+ ok=True,
120
+ answer=self._answer,
121
+ )
122
+
123
+
124
+ class ScriptRunner(MockRunner):
125
+ def __init__(
126
+ self,
127
+ script: Iterable[ScriptStep],
128
+ *,
129
+ engine: EngineId = ENGINE,
130
+ resume_value: str | None = None,
131
+ emit_session_start: bool = True,
132
+ sleep: Callable[[float], Awaitable[None]] = anyio.sleep,
133
+ advance: Callable[[float], None] | None = None,
134
+ default_answer: str = "",
135
+ title: str | None = None,
136
+ ) -> None:
137
+ super().__init__(
138
+ events=[],
139
+ answer=default_answer,
140
+ engine=engine,
141
+ resume_value=resume_value,
142
+ title=title,
143
+ )
144
+ self.calls: list[tuple[str, ResumeToken | None]] = []
145
+ self._script = list(script)
146
+ self._emit_session_start = emit_session_start
147
+ self._sleep = sleep
148
+ self._advance = advance
149
+
150
+ def _advance_to(self, now: float) -> None:
151
+ if self._advance is None:
152
+ raise RuntimeError("ScriptRunner advance callback is not configured.")
153
+ self._advance(now)
154
+
155
+ async def run(
156
+ self, prompt: str, resume: ResumeToken | None
157
+ ) -> AsyncIterator[TakopiEvent]:
158
+ self.calls.append((prompt, resume))
159
+ token_value = None
160
+ if resume is not None:
161
+ if resume.engine != self.engine:
162
+ raise RuntimeError(
163
+ f"resume token is for engine {resume.engine!r}, not {self.engine!r}"
164
+ )
165
+ token_value = resume.value
166
+ if token_value is None:
167
+ token_value = self._resume_value
168
+ token = _resume_token(self.engine, token_value)
169
+ session_evt = StartedEvent(
170
+ engine=self.engine,
171
+ resume=token,
172
+ title=self.title,
173
+ )
174
+ lock = self.lock_for(token)
175
+
176
+ async with lock:
177
+ if self._emit_session_start:
178
+ yield session_evt
179
+ await anyio.sleep(0)
180
+
181
+ for step in self._script:
182
+ if isinstance(step, Emit):
183
+ if step.at is not None:
184
+ self._advance_to(step.at)
185
+ event_out: TakopiEvent = step.event
186
+ if (
187
+ isinstance(event_out, ActionEvent)
188
+ and event_out.phase == "completed"
189
+ and event_out.ok is None
190
+ ):
191
+ event_out = replace(event_out, ok=True)
192
+ yield event_out
193
+ await anyio.sleep(0)
194
+ continue
195
+ if isinstance(step, Advance):
196
+ self._advance_to(step.now)
197
+ continue
198
+ if isinstance(step, Sleep):
199
+ await self._sleep(step.seconds)
200
+ continue
201
+ if isinstance(step, Wait):
202
+ await step.event.wait()
203
+ continue
204
+ if isinstance(step, Raise):
205
+ raise step.error
206
+ if isinstance(step, Return):
207
+ yield CompletedEvent(
208
+ engine=self.engine,
209
+ resume=token,
210
+ ok=True,
211
+ answer=step.answer,
212
+ )
213
+ return
214
+ raise RuntimeError(f"Unhandled script step: {step!r}")
215
+
216
+ yield CompletedEvent(
217
+ engine=self.engine,
218
+ resume=token,
219
+ ok=True,
220
+ answer=self._answer,
221
+ )