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
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from ...context import RunContext
|
|
6
|
+
from ...directives import DirectiveError
|
|
7
|
+
from ..chat_prefs import ChatPrefsStore
|
|
8
|
+
from ..engine_defaults import resolve_engine_for_message
|
|
9
|
+
from ..engine_overrides import resolve_override_value
|
|
10
|
+
from ..files import split_command_args
|
|
11
|
+
from ..topic_state import TopicStateStore
|
|
12
|
+
from ..topics import _topic_key
|
|
13
|
+
from ..types import TelegramIncomingMessage
|
|
14
|
+
from .reply import make_reply
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from ..bridge import TelegramBridgeConfig
|
|
18
|
+
|
|
19
|
+
AGENT_USAGE = "usage: `/agent`, `/agent set <engine>`, or `/agent clear`"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def _check_agent_permissions(
|
|
23
|
+
cfg: TelegramBridgeConfig, msg: TelegramIncomingMessage
|
|
24
|
+
) -> bool:
|
|
25
|
+
reply = make_reply(cfg, msg)
|
|
26
|
+
sender_id = msg.sender_id
|
|
27
|
+
if sender_id is None:
|
|
28
|
+
await reply(text="cannot verify sender for engine defaults.")
|
|
29
|
+
return False
|
|
30
|
+
if msg.is_private:
|
|
31
|
+
return True
|
|
32
|
+
member = await cfg.bot.get_chat_member(msg.chat_id, sender_id)
|
|
33
|
+
if member is None:
|
|
34
|
+
await reply(text="failed to verify engine permissions.")
|
|
35
|
+
return False
|
|
36
|
+
if member.status in {"creator", "administrator"}:
|
|
37
|
+
return True
|
|
38
|
+
await reply(text="changing default engines is restricted to group admins.")
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
async def _handle_agent_command(
|
|
43
|
+
cfg: TelegramBridgeConfig,
|
|
44
|
+
msg: TelegramIncomingMessage,
|
|
45
|
+
args_text: str,
|
|
46
|
+
ambient_context: RunContext | None,
|
|
47
|
+
topic_store: TopicStateStore | None,
|
|
48
|
+
chat_prefs: ChatPrefsStore | None,
|
|
49
|
+
*,
|
|
50
|
+
resolved_scope: str | None = None,
|
|
51
|
+
scope_chat_ids: frozenset[int] | None = None,
|
|
52
|
+
) -> None:
|
|
53
|
+
reply = make_reply(cfg, msg)
|
|
54
|
+
tkey = (
|
|
55
|
+
_topic_key(msg, cfg, scope_chat_ids=scope_chat_ids)
|
|
56
|
+
if topic_store is not None
|
|
57
|
+
else None
|
|
58
|
+
)
|
|
59
|
+
tokens = split_command_args(args_text)
|
|
60
|
+
action = tokens[0].lower() if tokens else "show"
|
|
61
|
+
|
|
62
|
+
if action in {"show", ""}:
|
|
63
|
+
try:
|
|
64
|
+
resolved = cfg.runtime.resolve_message(
|
|
65
|
+
text="",
|
|
66
|
+
reply_text=msg.reply_to_text,
|
|
67
|
+
ambient_context=ambient_context,
|
|
68
|
+
chat_id=msg.chat_id,
|
|
69
|
+
)
|
|
70
|
+
except DirectiveError as exc:
|
|
71
|
+
await reply(text=f"error:\n{exc}")
|
|
72
|
+
return
|
|
73
|
+
selection = await resolve_engine_for_message(
|
|
74
|
+
runtime=cfg.runtime,
|
|
75
|
+
context=resolved.context,
|
|
76
|
+
explicit_engine=None,
|
|
77
|
+
chat_id=msg.chat_id,
|
|
78
|
+
topic_key=tkey,
|
|
79
|
+
topic_store=topic_store,
|
|
80
|
+
chat_prefs=chat_prefs,
|
|
81
|
+
)
|
|
82
|
+
source_labels = {
|
|
83
|
+
"directive": "directive",
|
|
84
|
+
"topic_default": "topic default",
|
|
85
|
+
"chat_default": "chat default",
|
|
86
|
+
"project_default": "project default",
|
|
87
|
+
"global_default": "global default",
|
|
88
|
+
}
|
|
89
|
+
agent_line = f"engine: {selection.engine} ({source_labels[selection.source]})"
|
|
90
|
+
topic_override = None
|
|
91
|
+
if tkey is not None and topic_store is not None:
|
|
92
|
+
topic_override = await topic_store.get_engine_override(
|
|
93
|
+
tkey[0], tkey[1], selection.engine
|
|
94
|
+
)
|
|
95
|
+
chat_override = None
|
|
96
|
+
if chat_prefs is not None:
|
|
97
|
+
chat_override = await chat_prefs.get_engine_override(
|
|
98
|
+
msg.chat_id, selection.engine
|
|
99
|
+
)
|
|
100
|
+
override_labels = {
|
|
101
|
+
"topic_override": "topic override",
|
|
102
|
+
"chat_default": "chat default",
|
|
103
|
+
"default": "no override",
|
|
104
|
+
}
|
|
105
|
+
model_resolution = resolve_override_value(
|
|
106
|
+
topic_override=topic_override,
|
|
107
|
+
chat_override=chat_override,
|
|
108
|
+
field="model",
|
|
109
|
+
)
|
|
110
|
+
reasoning_resolution = resolve_override_value(
|
|
111
|
+
topic_override=topic_override,
|
|
112
|
+
chat_override=chat_override,
|
|
113
|
+
field="reasoning",
|
|
114
|
+
)
|
|
115
|
+
model_value = model_resolution.value or "default"
|
|
116
|
+
model_line = (
|
|
117
|
+
f"model: {model_value} ({override_labels[model_resolution.source]})"
|
|
118
|
+
)
|
|
119
|
+
reasoning_value = reasoning_resolution.value or "default"
|
|
120
|
+
reasoning_line = (
|
|
121
|
+
"reasoning: "
|
|
122
|
+
f"{reasoning_value} ({override_labels[reasoning_resolution.source]})"
|
|
123
|
+
)
|
|
124
|
+
topic_default = selection.topic_default or "none"
|
|
125
|
+
if tkey is None:
|
|
126
|
+
topic_default = "none"
|
|
127
|
+
if chat_prefs is None:
|
|
128
|
+
chat_default = "unavailable"
|
|
129
|
+
else:
|
|
130
|
+
chat_default = selection.chat_default or "none"
|
|
131
|
+
project_default = (
|
|
132
|
+
selection.project_default
|
|
133
|
+
if selection.project_default is not None
|
|
134
|
+
else "none"
|
|
135
|
+
)
|
|
136
|
+
defaults_line = (
|
|
137
|
+
"defaults: "
|
|
138
|
+
f"topic: {topic_default}, "
|
|
139
|
+
f"chat: {chat_default}, "
|
|
140
|
+
f"project: {project_default}, "
|
|
141
|
+
f"global: {cfg.runtime.default_engine}"
|
|
142
|
+
)
|
|
143
|
+
available = ", ".join(cfg.runtime.engine_ids)
|
|
144
|
+
available_line = f"available: {available}"
|
|
145
|
+
await reply(
|
|
146
|
+
text="\n\n".join(
|
|
147
|
+
[agent_line, model_line, reasoning_line, defaults_line, available_line]
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
if action == "set":
|
|
153
|
+
if len(tokens) < 2:
|
|
154
|
+
await reply(text=AGENT_USAGE)
|
|
155
|
+
return
|
|
156
|
+
if not await _check_agent_permissions(cfg, msg):
|
|
157
|
+
return
|
|
158
|
+
engine = tokens[1].strip().lower()
|
|
159
|
+
if engine not in cfg.runtime.engine_ids:
|
|
160
|
+
available = ", ".join(cfg.runtime.engine_ids)
|
|
161
|
+
await reply(
|
|
162
|
+
text=f"unknown engine `{engine}`.\navailable engines: `{available}`",
|
|
163
|
+
)
|
|
164
|
+
return
|
|
165
|
+
if tkey is not None:
|
|
166
|
+
if topic_store is None:
|
|
167
|
+
await reply(text="topic defaults are unavailable.")
|
|
168
|
+
return
|
|
169
|
+
await topic_store.set_default_engine(tkey[0], tkey[1], engine)
|
|
170
|
+
await reply(text=f"topic default engine set to `{engine}`")
|
|
171
|
+
return
|
|
172
|
+
if chat_prefs is None:
|
|
173
|
+
await reply(text="chat defaults are unavailable (no config path).")
|
|
174
|
+
return
|
|
175
|
+
await chat_prefs.set_default_engine(msg.chat_id, engine)
|
|
176
|
+
await reply(text=f"chat default engine set to `{engine}`")
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
if action == "clear":
|
|
180
|
+
if not await _check_agent_permissions(cfg, msg):
|
|
181
|
+
return
|
|
182
|
+
if tkey is not None:
|
|
183
|
+
if topic_store is None:
|
|
184
|
+
await reply(text="topic defaults are unavailable.")
|
|
185
|
+
return
|
|
186
|
+
await topic_store.clear_default_engine(tkey[0], tkey[1])
|
|
187
|
+
await reply(text="topic default engine cleared.")
|
|
188
|
+
return
|
|
189
|
+
if chat_prefs is None:
|
|
190
|
+
await reply(text="chat defaults are unavailable (no config path).")
|
|
191
|
+
return
|
|
192
|
+
await chat_prefs.clear_default_engine(msg.chat_id)
|
|
193
|
+
await reply(text="chat default engine cleared.")
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
await reply(text=AGENT_USAGE)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from ...logging import get_logger
|
|
6
|
+
from ...progress import ProgressTracker
|
|
7
|
+
from ...runner_bridge import RunningTasks
|
|
8
|
+
from ...scheduler import ThreadJob, ThreadScheduler
|
|
9
|
+
from ...transport import MessageRef
|
|
10
|
+
from ..types import TelegramCallbackQuery, TelegramIncomingMessage
|
|
11
|
+
from .reply import make_reply
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from ..bridge import TelegramBridgeConfig
|
|
15
|
+
|
|
16
|
+
logger = get_logger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def handle_cancel(
|
|
20
|
+
cfg: TelegramBridgeConfig,
|
|
21
|
+
msg: TelegramIncomingMessage,
|
|
22
|
+
running_tasks: RunningTasks,
|
|
23
|
+
scheduler: ThreadScheduler | None = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
reply = make_reply(cfg, msg)
|
|
26
|
+
chat_id = msg.chat_id
|
|
27
|
+
reply_id = msg.reply_to_message_id
|
|
28
|
+
|
|
29
|
+
if reply_id is None:
|
|
30
|
+
if msg.reply_to_text:
|
|
31
|
+
await reply(text="nothing is currently running for that message.")
|
|
32
|
+
return
|
|
33
|
+
await reply(text="reply to the progress message to cancel.")
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
progress_ref = MessageRef(channel_id=chat_id, message_id=reply_id)
|
|
37
|
+
running_task = running_tasks.get(progress_ref)
|
|
38
|
+
if running_task is None:
|
|
39
|
+
if scheduler is not None:
|
|
40
|
+
job = await scheduler.cancel_queued(chat_id, reply_id)
|
|
41
|
+
if job is not None:
|
|
42
|
+
logger.info(
|
|
43
|
+
"cancel.queued",
|
|
44
|
+
chat_id=chat_id,
|
|
45
|
+
progress_message_id=reply_id,
|
|
46
|
+
resume=job.resume_token.value,
|
|
47
|
+
)
|
|
48
|
+
await _edit_cancelled_message(cfg, progress_ref, job)
|
|
49
|
+
return
|
|
50
|
+
await reply(text="nothing is currently running for that message.")
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
logger.info(
|
|
54
|
+
"cancel.requested",
|
|
55
|
+
chat_id=chat_id,
|
|
56
|
+
progress_message_id=reply_id,
|
|
57
|
+
)
|
|
58
|
+
running_task.cancel_requested.set()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async def handle_callback_cancel(
|
|
62
|
+
cfg: TelegramBridgeConfig,
|
|
63
|
+
query: TelegramCallbackQuery,
|
|
64
|
+
running_tasks: RunningTasks,
|
|
65
|
+
scheduler: ThreadScheduler | None = None,
|
|
66
|
+
) -> None:
|
|
67
|
+
progress_ref = MessageRef(channel_id=query.chat_id, message_id=query.message_id)
|
|
68
|
+
running_task = running_tasks.get(progress_ref)
|
|
69
|
+
if running_task is None:
|
|
70
|
+
if scheduler is not None:
|
|
71
|
+
job = await scheduler.cancel_queued(query.chat_id, query.message_id)
|
|
72
|
+
if job is not None:
|
|
73
|
+
logger.info(
|
|
74
|
+
"cancel.queued",
|
|
75
|
+
chat_id=query.chat_id,
|
|
76
|
+
progress_message_id=query.message_id,
|
|
77
|
+
resume=job.resume_token.value,
|
|
78
|
+
)
|
|
79
|
+
await _edit_cancelled_message(cfg, progress_ref, job)
|
|
80
|
+
await cfg.bot.answer_callback_query(
|
|
81
|
+
callback_query_id=query.callback_query_id,
|
|
82
|
+
text="dropped from queue.",
|
|
83
|
+
)
|
|
84
|
+
return
|
|
85
|
+
await cfg.bot.answer_callback_query(
|
|
86
|
+
callback_query_id=query.callback_query_id,
|
|
87
|
+
text="nothing is currently running for that message.",
|
|
88
|
+
)
|
|
89
|
+
return
|
|
90
|
+
logger.info(
|
|
91
|
+
"cancel.requested",
|
|
92
|
+
chat_id=query.chat_id,
|
|
93
|
+
progress_message_id=query.message_id,
|
|
94
|
+
)
|
|
95
|
+
running_task.cancel_requested.set()
|
|
96
|
+
await cfg.bot.answer_callback_query(
|
|
97
|
+
callback_query_id=query.callback_query_id,
|
|
98
|
+
text="cancelling...",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def _edit_cancelled_message(
|
|
103
|
+
cfg: TelegramBridgeConfig,
|
|
104
|
+
progress_ref: MessageRef,
|
|
105
|
+
job: ThreadJob,
|
|
106
|
+
) -> None:
|
|
107
|
+
tracker = ProgressTracker(engine=job.resume_token.engine)
|
|
108
|
+
tracker.set_resume(job.resume_token)
|
|
109
|
+
context_line = cfg.runtime.format_context_line(job.context)
|
|
110
|
+
state = tracker.snapshot(context_line=context_line)
|
|
111
|
+
message = cfg.exec_cfg.presenter.render_progress(
|
|
112
|
+
state,
|
|
113
|
+
elapsed_s=0.0,
|
|
114
|
+
label="`cancelled`",
|
|
115
|
+
)
|
|
116
|
+
await cfg.exec_cfg.transport.edit(ref=progress_ref, message=message)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Awaitable, Callable
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
import anyio
|
|
7
|
+
|
|
8
|
+
from ...commands import CommandContext, get_command
|
|
9
|
+
from ...config import ConfigError
|
|
10
|
+
from ...logging import get_logger
|
|
11
|
+
from ...model import EngineId, ResumeToken
|
|
12
|
+
from ...runners.run_options import EngineRunOptions
|
|
13
|
+
from ...runner_bridge import RunningTasks
|
|
14
|
+
from ...scheduler import ThreadScheduler
|
|
15
|
+
from ...transport import MessageRef
|
|
16
|
+
from ..files import split_command_args
|
|
17
|
+
from ..types import TelegramIncomingMessage
|
|
18
|
+
from .executor import _TelegramCommandExecutor
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from ..bridge import TelegramBridgeConfig
|
|
22
|
+
|
|
23
|
+
logger = get_logger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def _dispatch_command(
|
|
27
|
+
cfg: TelegramBridgeConfig,
|
|
28
|
+
msg: TelegramIncomingMessage,
|
|
29
|
+
text: str,
|
|
30
|
+
command_id: str,
|
|
31
|
+
args_text: str,
|
|
32
|
+
running_tasks: RunningTasks,
|
|
33
|
+
scheduler: ThreadScheduler,
|
|
34
|
+
on_thread_known: Callable[[ResumeToken, anyio.Event], Awaitable[None]] | None,
|
|
35
|
+
stateful_mode: bool,
|
|
36
|
+
default_engine_override: EngineId | None,
|
|
37
|
+
engine_overrides_resolver: Callable[[EngineId], Awaitable[EngineRunOptions | None]]
|
|
38
|
+
| None,
|
|
39
|
+
) -> None:
|
|
40
|
+
allowlist = cfg.runtime.allowlist
|
|
41
|
+
chat_id = msg.chat_id
|
|
42
|
+
user_msg_id = msg.message_id
|
|
43
|
+
reply_ref = (
|
|
44
|
+
MessageRef(
|
|
45
|
+
channel_id=chat_id,
|
|
46
|
+
message_id=msg.reply_to_message_id,
|
|
47
|
+
thread_id=msg.thread_id,
|
|
48
|
+
)
|
|
49
|
+
if msg.reply_to_message_id is not None
|
|
50
|
+
else None
|
|
51
|
+
)
|
|
52
|
+
executor = _TelegramCommandExecutor(
|
|
53
|
+
exec_cfg=cfg.exec_cfg,
|
|
54
|
+
runtime=cfg.runtime,
|
|
55
|
+
running_tasks=running_tasks,
|
|
56
|
+
scheduler=scheduler,
|
|
57
|
+
on_thread_known=on_thread_known,
|
|
58
|
+
engine_overrides_resolver=engine_overrides_resolver,
|
|
59
|
+
chat_id=chat_id,
|
|
60
|
+
user_msg_id=user_msg_id,
|
|
61
|
+
thread_id=msg.thread_id,
|
|
62
|
+
show_resume_line=cfg.show_resume_line,
|
|
63
|
+
stateful_mode=stateful_mode,
|
|
64
|
+
default_engine_override=default_engine_override,
|
|
65
|
+
)
|
|
66
|
+
message_ref = MessageRef(
|
|
67
|
+
channel_id=chat_id,
|
|
68
|
+
message_id=user_msg_id,
|
|
69
|
+
thread_id=msg.thread_id,
|
|
70
|
+
sender_id=msg.sender_id,
|
|
71
|
+
raw=msg.raw,
|
|
72
|
+
)
|
|
73
|
+
try:
|
|
74
|
+
backend = get_command(command_id, allowlist=allowlist, required=False)
|
|
75
|
+
except ConfigError as exc:
|
|
76
|
+
await executor.send(f"error:\n{exc}", reply_to=message_ref, notify=True)
|
|
77
|
+
return
|
|
78
|
+
if backend is None:
|
|
79
|
+
return
|
|
80
|
+
try:
|
|
81
|
+
plugin_config = cfg.runtime.plugin_config(command_id)
|
|
82
|
+
except ConfigError as exc:
|
|
83
|
+
await executor.send(f"error:\n{exc}", reply_to=message_ref, notify=True)
|
|
84
|
+
return
|
|
85
|
+
ctx = CommandContext(
|
|
86
|
+
command=command_id,
|
|
87
|
+
text=text,
|
|
88
|
+
args_text=args_text,
|
|
89
|
+
args=split_command_args(args_text),
|
|
90
|
+
message=message_ref,
|
|
91
|
+
reply_to=reply_ref,
|
|
92
|
+
reply_text=msg.reply_to_text,
|
|
93
|
+
config_path=cfg.runtime.config_path,
|
|
94
|
+
plugin_config=plugin_config,
|
|
95
|
+
runtime=cfg.runtime,
|
|
96
|
+
executor=executor,
|
|
97
|
+
)
|
|
98
|
+
try:
|
|
99
|
+
result = await backend.handle(ctx)
|
|
100
|
+
except Exception as exc:
|
|
101
|
+
logger.exception(
|
|
102
|
+
"command.failed",
|
|
103
|
+
command=command_id,
|
|
104
|
+
error=str(exc),
|
|
105
|
+
error_type=exc.__class__.__name__,
|
|
106
|
+
)
|
|
107
|
+
await executor.send(f"error:\n{exc}", reply_to=message_ref, notify=True)
|
|
108
|
+
return
|
|
109
|
+
if result is not None:
|
|
110
|
+
reply_to = message_ref if result.reply_to is None else result.reply_to
|
|
111
|
+
await executor.send(result.text, reply_to=reply_to, notify=result.notify)
|