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.
- yee88/__init__.py +1 -0
- yee88/api.py +116 -0
- yee88/backends.py +25 -0
- yee88/backends_helpers.py +14 -0
- yee88/cli/__init__.py +228 -0
- yee88/cli/config.py +320 -0
- yee88/cli/doctor.py +173 -0
- yee88/cli/init.py +113 -0
- yee88/cli/onboarding_cmd.py +126 -0
- yee88/cli/plugins.py +196 -0
- yee88/cli/run.py +419 -0
- yee88/cli/topic.py +355 -0
- yee88/commands.py +134 -0
- yee88/config.py +142 -0
- yee88/config_migrations.py +124 -0
- yee88/config_watch.py +146 -0
- yee88/context.py +9 -0
- yee88/directives.py +146 -0
- yee88/engines.py +53 -0
- yee88/events.py +170 -0
- yee88/ids.py +17 -0
- yee88/lockfile.py +158 -0
- yee88/logging.py +283 -0
- yee88/markdown.py +298 -0
- yee88/model.py +77 -0
- yee88/plugins.py +312 -0
- yee88/presenter.py +25 -0
- yee88/progress.py +99 -0
- yee88/router.py +113 -0
- yee88/runner.py +712 -0
- yee88/runner_bridge.py +619 -0
- yee88/runners/__init__.py +1 -0
- yee88/runners/claude.py +483 -0
- yee88/runners/codex.py +656 -0
- yee88/runners/mock.py +221 -0
- yee88/runners/opencode.py +505 -0
- yee88/runners/pi.py +523 -0
- yee88/runners/run_options.py +39 -0
- yee88/runners/tool_actions.py +90 -0
- yee88/runtime_loader.py +207 -0
- yee88/scheduler.py +159 -0
- yee88/schemas/__init__.py +1 -0
- yee88/schemas/claude.py +238 -0
- yee88/schemas/codex.py +169 -0
- yee88/schemas/opencode.py +51 -0
- yee88/schemas/pi.py +117 -0
- yee88/settings.py +360 -0
- yee88/telegram/__init__.py +20 -0
- yee88/telegram/api_models.py +37 -0
- yee88/telegram/api_schemas.py +152 -0
- yee88/telegram/backend.py +163 -0
- yee88/telegram/bridge.py +425 -0
- yee88/telegram/chat_prefs.py +242 -0
- yee88/telegram/chat_sessions.py +112 -0
- yee88/telegram/client.py +409 -0
- yee88/telegram/client_api.py +539 -0
- yee88/telegram/commands/__init__.py +12 -0
- yee88/telegram/commands/agent.py +196 -0
- yee88/telegram/commands/cancel.py +116 -0
- yee88/telegram/commands/dispatch.py +111 -0
- yee88/telegram/commands/executor.py +449 -0
- yee88/telegram/commands/file_transfer.py +586 -0
- yee88/telegram/commands/handlers.py +45 -0
- yee88/telegram/commands/media.py +143 -0
- yee88/telegram/commands/menu.py +139 -0
- yee88/telegram/commands/model.py +215 -0
- yee88/telegram/commands/overrides.py +159 -0
- yee88/telegram/commands/parse.py +30 -0
- yee88/telegram/commands/plan.py +16 -0
- yee88/telegram/commands/reasoning.py +234 -0
- yee88/telegram/commands/reply.py +23 -0
- yee88/telegram/commands/topics.py +332 -0
- yee88/telegram/commands/trigger.py +143 -0
- yee88/telegram/context.py +140 -0
- yee88/telegram/engine_defaults.py +86 -0
- yee88/telegram/engine_overrides.py +105 -0
- yee88/telegram/files.py +178 -0
- yee88/telegram/loop.py +1822 -0
- yee88/telegram/onboarding.py +1088 -0
- yee88/telegram/outbox.py +177 -0
- yee88/telegram/parsing.py +239 -0
- yee88/telegram/render.py +198 -0
- yee88/telegram/state_store.py +88 -0
- yee88/telegram/topic_state.py +334 -0
- yee88/telegram/topics.py +256 -0
- yee88/telegram/trigger_mode.py +68 -0
- yee88/telegram/types.py +63 -0
- yee88/telegram/voice.py +110 -0
- yee88/transport.py +53 -0
- yee88/transport_runtime.py +323 -0
- yee88/transports.py +76 -0
- yee88/utils/__init__.py +1 -0
- yee88/utils/git.py +87 -0
- yee88/utils/json_state.py +21 -0
- yee88/utils/paths.py +47 -0
- yee88/utils/streams.py +44 -0
- yee88/utils/subprocess.py +86 -0
- yee88/worktrees.py +135 -0
- yee88-0.3.0.dist-info/METADATA +116 -0
- yee88-0.3.0.dist-info/RECORD +103 -0
- yee88-0.3.0.dist-info/WHEEL +4 -0
- yee88-0.3.0.dist-info/entry_points.txt +11 -0
- yee88-0.3.0.dist-info/licenses/LICENSE +21 -0
yee88/progress.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
|
|
6
|
+
from .model import Action, ActionEvent, ResumeToken, StartedEvent, TakopiEvent
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True, slots=True)
|
|
10
|
+
class ActionState:
|
|
11
|
+
action: Action
|
|
12
|
+
phase: str
|
|
13
|
+
ok: bool | None
|
|
14
|
+
display_phase: str
|
|
15
|
+
completed: bool
|
|
16
|
+
first_seen: int
|
|
17
|
+
last_update: int
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True, slots=True)
|
|
21
|
+
class ProgressState:
|
|
22
|
+
engine: str
|
|
23
|
+
action_count: int
|
|
24
|
+
actions: tuple[ActionState, ...]
|
|
25
|
+
resume: ResumeToken | None
|
|
26
|
+
resume_line: str | None
|
|
27
|
+
context_line: str | None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ProgressTracker:
|
|
31
|
+
def __init__(self, *, engine: str) -> None:
|
|
32
|
+
self.engine = engine
|
|
33
|
+
self.resume: ResumeToken | None = None
|
|
34
|
+
self.action_count = 0
|
|
35
|
+
self._actions: dict[str, ActionState] = {}
|
|
36
|
+
self._seq = 0
|
|
37
|
+
|
|
38
|
+
def note_event(self, event: TakopiEvent) -> bool:
|
|
39
|
+
match event:
|
|
40
|
+
case StartedEvent(resume=resume):
|
|
41
|
+
self.resume = resume
|
|
42
|
+
return True
|
|
43
|
+
case ActionEvent(action=action, phase=phase, ok=ok):
|
|
44
|
+
if action.kind == "turn":
|
|
45
|
+
return False
|
|
46
|
+
action_id = str(action.id or "")
|
|
47
|
+
if not action_id:
|
|
48
|
+
return False
|
|
49
|
+
completed = phase == "completed"
|
|
50
|
+
existing = self._actions.get(action_id)
|
|
51
|
+
has_open = existing is not None and not existing.completed
|
|
52
|
+
is_update = phase == "updated" or (phase == "started" and has_open)
|
|
53
|
+
display_phase = "updated" if is_update and not completed else phase
|
|
54
|
+
|
|
55
|
+
self._seq += 1
|
|
56
|
+
seq = self._seq
|
|
57
|
+
|
|
58
|
+
if existing is None:
|
|
59
|
+
self.action_count += 1
|
|
60
|
+
first_seen = seq
|
|
61
|
+
else:
|
|
62
|
+
first_seen = existing.first_seen
|
|
63
|
+
self._actions[action_id] = ActionState(
|
|
64
|
+
action=action,
|
|
65
|
+
phase=phase,
|
|
66
|
+
ok=ok,
|
|
67
|
+
display_phase=display_phase,
|
|
68
|
+
completed=completed,
|
|
69
|
+
first_seen=first_seen,
|
|
70
|
+
last_update=seq,
|
|
71
|
+
)
|
|
72
|
+
return True
|
|
73
|
+
case _:
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
def set_resume(self, resume: ResumeToken | None) -> None:
|
|
77
|
+
if resume is not None:
|
|
78
|
+
self.resume = resume
|
|
79
|
+
|
|
80
|
+
def snapshot(
|
|
81
|
+
self,
|
|
82
|
+
*,
|
|
83
|
+
resume_formatter: Callable[[ResumeToken], str] | None = None,
|
|
84
|
+
context_line: str | None = None,
|
|
85
|
+
) -> ProgressState:
|
|
86
|
+
resume_line: str | None = None
|
|
87
|
+
if self.resume is not None and resume_formatter is not None:
|
|
88
|
+
resume_line = resume_formatter(self.resume)
|
|
89
|
+
actions = tuple(
|
|
90
|
+
sorted(self._actions.values(), key=lambda item: item.first_seen)
|
|
91
|
+
)
|
|
92
|
+
return ProgressState(
|
|
93
|
+
engine=self.engine,
|
|
94
|
+
action_count=self.action_count,
|
|
95
|
+
actions=actions,
|
|
96
|
+
resume=self.resume,
|
|
97
|
+
resume_line=resume_line,
|
|
98
|
+
context_line=context_line,
|
|
99
|
+
)
|
yee88/router.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Literal
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
|
|
7
|
+
from .model import EngineId, ResumeToken
|
|
8
|
+
from .runner import Runner
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RunnerUnavailableError(RuntimeError):
|
|
12
|
+
def __init__(self, engine: EngineId, issue: str | None = None) -> None:
|
|
13
|
+
message = f"engine {engine!r} is unavailable"
|
|
14
|
+
if issue:
|
|
15
|
+
message = f"{message}: {issue}"
|
|
16
|
+
super().__init__(message)
|
|
17
|
+
self.engine = engine
|
|
18
|
+
self.issue = issue
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
type EngineStatus = Literal["ok", "missing_cli", "bad_config", "load_error"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True, slots=True)
|
|
25
|
+
class RunnerEntry:
|
|
26
|
+
engine: EngineId
|
|
27
|
+
runner: Runner
|
|
28
|
+
status: EngineStatus = "ok"
|
|
29
|
+
issue: str | None = None
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def available(self) -> bool:
|
|
33
|
+
# "bad_config" means we ignored user config and built the runner with defaults.
|
|
34
|
+
# The engine is still runnable, but a warning should be surfaced to the user.
|
|
35
|
+
return self.status in {"ok", "bad_config"}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AutoRouter:
|
|
39
|
+
def __init__(
|
|
40
|
+
self, entries: Iterable[RunnerEntry], default_engine: EngineId
|
|
41
|
+
) -> None:
|
|
42
|
+
self._entries = tuple(entries)
|
|
43
|
+
if not self._entries:
|
|
44
|
+
raise ValueError("AutoRouter requires at least one runner.")
|
|
45
|
+
by_engine: dict[EngineId, RunnerEntry] = {}
|
|
46
|
+
for entry in self._entries:
|
|
47
|
+
if entry.engine in by_engine:
|
|
48
|
+
raise ValueError(f"duplicate runner engine: {entry.engine}")
|
|
49
|
+
by_engine[entry.engine] = entry
|
|
50
|
+
if default_engine not in by_engine:
|
|
51
|
+
raise ValueError(f"default engine {default_engine!r} is not configured")
|
|
52
|
+
self._by_engine = by_engine
|
|
53
|
+
self.default_engine = default_engine
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def entries(self) -> tuple[RunnerEntry, ...]:
|
|
57
|
+
return self._entries
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def available_entries(self) -> tuple[RunnerEntry, ...]:
|
|
61
|
+
return tuple(entry for entry in self._entries if entry.available)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def engine_ids(self) -> tuple[EngineId, ...]:
|
|
65
|
+
return tuple(entry.engine for entry in self._entries)
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def default_entry(self) -> RunnerEntry:
|
|
69
|
+
return self._by_engine[self.default_engine]
|
|
70
|
+
|
|
71
|
+
def entry_for_engine(self, engine: EngineId | None) -> RunnerEntry:
|
|
72
|
+
engine = self.default_engine if engine is None else engine
|
|
73
|
+
entry = self._by_engine.get(engine)
|
|
74
|
+
if entry is None:
|
|
75
|
+
raise RunnerUnavailableError(engine, "engine not configured")
|
|
76
|
+
return entry
|
|
77
|
+
|
|
78
|
+
def entry_for(self, resume: ResumeToken | None) -> RunnerEntry:
|
|
79
|
+
if resume is None:
|
|
80
|
+
return self.entry_for_engine(None)
|
|
81
|
+
return self.entry_for_engine(resume.engine)
|
|
82
|
+
|
|
83
|
+
def runner_for(self, resume: ResumeToken | None) -> Runner:
|
|
84
|
+
entry = self.entry_for(resume)
|
|
85
|
+
if not entry.available:
|
|
86
|
+
raise RunnerUnavailableError(entry.engine, entry.issue)
|
|
87
|
+
return entry.runner
|
|
88
|
+
|
|
89
|
+
def format_resume(self, token: ResumeToken) -> str:
|
|
90
|
+
entry = self.entry_for(token)
|
|
91
|
+
return entry.runner.format_resume(token)
|
|
92
|
+
|
|
93
|
+
def extract_resume(self, text: str | None) -> ResumeToken | None:
|
|
94
|
+
if not text:
|
|
95
|
+
return None
|
|
96
|
+
for entry in self._entries:
|
|
97
|
+
token = entry.runner.extract_resume(text)
|
|
98
|
+
if token is not None:
|
|
99
|
+
return token
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
def resolve_resume(
|
|
103
|
+
self, text: str | None, reply_text: str | None
|
|
104
|
+
) -> ResumeToken | None:
|
|
105
|
+
token = self.extract_resume(text)
|
|
106
|
+
if token is not None:
|
|
107
|
+
return token
|
|
108
|
+
if reply_text is None:
|
|
109
|
+
return None
|
|
110
|
+
return self.extract_resume(reply_text)
|
|
111
|
+
|
|
112
|
+
def is_resume_line(self, line: str) -> bool:
|
|
113
|
+
return any(entry.runner.is_resume_line(line) for entry in self._entries)
|