alvin-bot 5.7.0 β†’ 5.8.0

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 (136) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/claude.js +1 -102
  3. package/dist/config.js +1 -96
  4. package/dist/engine.js +1 -90
  5. package/dist/find-claude-binary.js +1 -98
  6. package/dist/handlers/async-agent-chunk-handler.js +1 -50
  7. package/dist/handlers/background-bypass.js +1 -75
  8. package/dist/handlers/commands.js +1 -2336
  9. package/dist/handlers/cron-progress.js +1 -52
  10. package/dist/handlers/document.js +1 -194
  11. package/dist/handlers/message.js +1 -959
  12. package/dist/handlers/photo.js +1 -154
  13. package/dist/handlers/platform-message.js +1 -360
  14. package/dist/handlers/stuck-timer.js +1 -54
  15. package/dist/handlers/video.js +1 -237
  16. package/dist/handlers/voice.js +1 -148
  17. package/dist/i18n.js +1 -805
  18. package/dist/index.js +1 -697
  19. package/dist/init-data-dir.js +1 -98
  20. package/dist/middleware/auth.js +1 -233
  21. package/dist/migrate.js +1 -162
  22. package/dist/paths.js +1 -146
  23. package/dist/platforms/discord.js +1 -175
  24. package/dist/platforms/index.js +1 -130
  25. package/dist/platforms/signal.js +1 -205
  26. package/dist/platforms/slack-slash-parser.js +1 -32
  27. package/dist/platforms/slack.js +1 -501
  28. package/dist/platforms/telegram.js +1 -111
  29. package/dist/platforms/types.js +1 -8
  30. package/dist/platforms/whatsapp-auth-helpers.js +1 -53
  31. package/dist/platforms/whatsapp.js +1 -707
  32. package/dist/providers/claude-sdk-provider.js +1 -565
  33. package/dist/providers/codex-cli-provider.js +1 -134
  34. package/dist/providers/index.js +1 -7
  35. package/dist/providers/ollama-provider.js +1 -32
  36. package/dist/providers/openai-compatible.js +1 -406
  37. package/dist/providers/registry.js +1 -352
  38. package/dist/providers/runtime-header.js +1 -45
  39. package/dist/providers/tool-executor.js +1 -475
  40. package/dist/providers/types.js +1 -227
  41. package/dist/services/access.js +1 -144
  42. package/dist/services/allowed-users-gate.js +1 -56
  43. package/dist/services/alvin-dispatch.js +1 -174
  44. package/dist/services/alvin-mcp-tools.js +1 -104
  45. package/dist/services/asset-index.js +1 -224
  46. package/dist/services/async-agent-parser.js +1 -418
  47. package/dist/services/async-agent-watcher.js +1 -583
  48. package/dist/services/auto-diagnostic.js +1 -228
  49. package/dist/services/broadcast.js +1 -52
  50. package/dist/services/browser-manager.js +1 -562
  51. package/dist/services/browser-webfetch.js +1 -127
  52. package/dist/services/browser.js +1 -121
  53. package/dist/services/cdp-bootstrap.js +1 -357
  54. package/dist/services/compaction.js +1 -144
  55. package/dist/services/critical-notify.js +1 -203
  56. package/dist/services/cron-resolver.js +1 -58
  57. package/dist/services/cron-scheduling.js +1 -310
  58. package/dist/services/cron.js +1 -861
  59. package/dist/services/custom-tools.js +1 -317
  60. package/dist/services/delivery-queue.js +1 -173
  61. package/dist/services/delivery-registry.js +1 -21
  62. package/dist/services/disk-cleanup.js +1 -203
  63. package/dist/services/elevenlabs.js +1 -58
  64. package/dist/services/embeddings/auto-detect.js +1 -74
  65. package/dist/services/embeddings/fts5.js +1 -108
  66. package/dist/services/embeddings/gemini.js +1 -65
  67. package/dist/services/embeddings/index.js +1 -496
  68. package/dist/services/embeddings/ollama.js +1 -78
  69. package/dist/services/embeddings/openai.js +1 -49
  70. package/dist/services/embeddings/provider.js +1 -22
  71. package/dist/services/embeddings/vector-base.js +1 -113
  72. package/dist/services/embeddings-migration.js +1 -193
  73. package/dist/services/embeddings.js +1 -9
  74. package/dist/services/env-file.js +1 -50
  75. package/dist/services/exec-guard.js +1 -71
  76. package/dist/services/fallback-order.js +1 -154
  77. package/dist/services/file-permissions.js +1 -93
  78. package/dist/services/heartbeat-file.js +1 -65
  79. package/dist/services/heartbeat.js +1 -313
  80. package/dist/services/hooks.js +1 -44
  81. package/dist/services/imagegen.js +1 -72
  82. package/dist/services/language-detect.js +1 -154
  83. package/dist/services/markdown.js +1 -63
  84. package/dist/services/mcp.js +1 -263
  85. package/dist/services/memory-extractor.js +1 -178
  86. package/dist/services/memory-inject-mode.js +1 -43
  87. package/dist/services/memory-layers.js +1 -156
  88. package/dist/services/memory.js +1 -146
  89. package/dist/services/ollama-manager.js +1 -339
  90. package/dist/services/permissions-wizard.js +1 -291
  91. package/dist/services/personality.js +1 -376
  92. package/dist/services/plugins.js +1 -171
  93. package/dist/services/preflight.js +1 -292
  94. package/dist/services/process-manager.js +1 -291
  95. package/dist/services/release-highlights.js +1 -79
  96. package/dist/services/reminders.js +1 -97
  97. package/dist/services/restart.js +1 -48
  98. package/dist/services/security-audit.js +1 -74
  99. package/dist/services/self-diagnosis.js +1 -272
  100. package/dist/services/self-search.js +1 -129
  101. package/dist/services/session-persistence.js +1 -237
  102. package/dist/services/session.js +1 -282
  103. package/dist/services/skills.js +1 -290
  104. package/dist/services/ssrf-guard.js +1 -162
  105. package/dist/services/standing-orders.js +1 -29
  106. package/dist/services/steer-channel.js +1 -46
  107. package/dist/services/stop-controller.js +1 -52
  108. package/dist/services/subagent-dedup.js +1 -86
  109. package/dist/services/subagent-delivery.js +1 -452
  110. package/dist/services/subagent-stats.js +1 -123
  111. package/dist/services/subagents.js +1 -814
  112. package/dist/services/sudo.js +1 -329
  113. package/dist/services/telegram.js +1 -158
  114. package/dist/services/timing-safe-bearer.js +1 -51
  115. package/dist/services/tool-discovery.js +1 -214
  116. package/dist/services/trends.js +1 -580
  117. package/dist/services/updater.js +1 -291
  118. package/dist/services/usage-tracker.js +1 -144
  119. package/dist/services/users.js +1 -271
  120. package/dist/services/voice.js +1 -104
  121. package/dist/services/watchdog-brake.js +1 -154
  122. package/dist/services/watchdog.js +1 -311
  123. package/dist/services/workspaces.js +1 -276
  124. package/dist/tui/index.js +1 -667
  125. package/dist/util/console-formatter.js +1 -109
  126. package/dist/util/debounce.js +1 -24
  127. package/dist/util/telegram-error-filter.js +1 -62
  128. package/dist/version.js +1 -24
  129. package/dist/web/bind-strategy.js +1 -42
  130. package/dist/web/canvas.js +1 -30
  131. package/dist/web/doctor-api.js +1 -604
  132. package/dist/web/openai-compat.js +1 -252
  133. package/dist/web/server.js +1 -1902
  134. package/dist/web/setup-api.js +1 -1101
  135. package/package.json +5 -2
  136. package/dist/.metadata_never_index +0 -0
@@ -1,959 +1 @@
1
- import { InputFile, InlineKeyboard } from "grammy";
2
- import fs from "fs";
3
- import crypto from "crypto";
4
- import { getSession, addToHistory, trackProviderUsage, buildSessionKey, getTelegramWorkspace, markSessionDirty } from "../services/session.js";
5
- import { resolveWorkspaceOrDefault, getWorkspace } from "../services/workspaces.js";
6
- import { TelegramStreamer } from "../services/telegram.js";
7
- import { getRegistry } from "../engine.js";
8
- import { textToSpeech } from "../services/voice.js";
9
- import { buildSmartSystemPrompt } from "../services/personality.js";
10
- import { buildSkillContext } from "../services/skills.js";
11
- import { isForwardingAllowed } from "../services/access.js";
12
- import { touchProfile } from "../services/users.js";
13
- import { trackAndAdapt } from "../services/language-detect.js";
14
- import { shouldCompact, compactSession } from "../services/compaction.js";
15
- import { emit } from "../services/hooks.js";
16
- import { trackUsage } from "../services/usage-tracker.js";
17
- import { emitUserMessage as broadcastUserMessage, emitResponseStart as broadcastResponseStart, emitResponseDelta as broadcastResponseDelta, emitResponseDone as broadcastResponseDone, } from "../services/broadcast.js";
18
- import { t } from "../i18n.js";
19
- import { isHarmlessTelegramError } from "../util/telegram-error-filter.js";
20
- import { handleToolResultChunk } from "./async-agent-chunk-handler.js";
21
- import { createStuckTimer } from "./stuck-timer.js";
22
- import { shouldBypassQueue, shouldBypassSdkResume, waitUntilProcessingFalse, } from "./background-bypass.js";
23
- import { SteerChannel } from "../services/steer-channel.js";
24
- import { isSteeringEnabled } from "../config.js";
25
- /**
26
- * Stuck-only timeout β€” NO absolute cap.
27
- *
28
- * Alvin is designed to work as long as it needs to, including overnight
29
- * on multi-hour tasks. The ONLY condition under which we abort a running
30
- * query is when Claude produces no chunks at all for STUCK_TIMEOUT_MINUTES
31
- * β€” that's a genuine hang, not legitimate work. Every text chunk and
32
- * tool_use chunk resets this timer, so an actively-progressing task will
33
- * never be cut off regardless of total duration.
34
- *
35
- * Previous design had an additional 30-minute absolute cap that violated
36
- * this "work as long as needed" character. Removed entirely β€” only the
37
- * stuck detector remains.
38
- *
39
- * Configurable via ALVIN_STUCK_TIMEOUT_MINUTES env var. Default 10 minutes,
40
- * which is generous for normal work (Claude typically streams chunks every
41
- * few seconds) but still catches real deadlocks quickly.
42
- */
43
- const STUCK_TIMEOUT_MINUTES = Number(process.env.ALVIN_STUCK_TIMEOUT_MINUTES) || 10;
44
- const STUCK_TIMEOUT_MS = STUCK_TIMEOUT_MINUTES * 60 * 1000;
45
- /**
46
- * v4.12.1 β€” Task-aware stuck timeout for sync Task/Agent tool calls.
47
- *
48
- * When Claude calls the Task/Agent tool WITHOUT run_in_background: true,
49
- * the Claude Agent SDK runs the sub-agent synchronously inside the tool
50
- * call. The parent stream emits NO intermediate chunks during that time
51
- * β€” it's silent until the sub-agent finishes and the final tool_result
52
- * arrives. With the normal STUCK_TIMEOUT_MS (10 min), this triggered a
53
- * false abort on legitimate long-running sub-agents.
54
- *
55
- * The new approach: track pending sync Task/Agent tool calls by their
56
- * toolUseId, and while any are active, escalate the idle timeout to
57
- * SYNC_AGENT_IDLE_TIMEOUT_MS (default 120 min, env-configurable). After
58
- * the matching tool_result arrives, revert to the normal timeout.
59
- *
60
- * The normal 10-min timeout still applies for genuine SDK hangs (no
61
- * sync tool call active, no chunks arriving).
62
- */
63
- const SYNC_AGENT_IDLE_TIMEOUT_MINUTES = Number(process.env.ALVIN_SYNC_AGENT_IDLE_TIMEOUT_MINUTES) || 120;
64
- const SYNC_AGENT_IDLE_TIMEOUT_MS = SYNC_AGENT_IDLE_TIMEOUT_MINUTES * 60 * 1000;
65
- /** Checkpoint reminder thresholds β€” kept in sync with
66
- * src/providers/claude-sdk-provider.ts (where the actual hint injection
67
- * happens). We mirror the check here so the session telemetry knows
68
- * when the SDK provider would have injected a reminder. */
69
- const CHECKPOINT_TOOL_THRESHOLD = 15;
70
- const CHECKPOINT_MSG_THRESHOLD = 10;
71
- /** Maximum characters in the bridge-message preamble that gets prepended
72
- * to the first post-recovery SDK query. Oldest gap-turns get truncated. */
73
- const BRIDGE_MAX_CHARS = 2500;
74
- /** Maximum characters per individual message in the bridge preamble. */
75
- const BRIDGE_MSG_MAX_CHARS = 500;
76
- /**
77
- * Build a "catch-up" preamble summarising turns that happened while the
78
- * SDK was not the active provider (i.e., during a failover to Ollama or
79
- * a manual /model switch). This gets prepended to the first post-recovery
80
- * prompt so the SDK sees what its alter-ego did.
81
- */
82
- function buildBridgeMessage(fallbackTurns) {
83
- if (fallbackTurns.length === 0)
84
- return "";
85
- const renderTurn = (m) => {
86
- const label = m.role === "user" ? "User" : "Assistant (Fallback)";
87
- const content = m.content.length > BRIDGE_MSG_MAX_CHARS
88
- ? m.content.slice(0, BRIDGE_MSG_MAX_CHARS) + "…"
89
- : m.content;
90
- return `${label}: ${content}`;
91
- };
92
- // Start with all turns rendered, then trim from the oldest if we exceed budget.
93
- let lines = fallbackTurns.map(renderTurn);
94
- let body = lines.join("\n\n");
95
- let truncatedOldest = 0;
96
- while (body.length > BRIDGE_MAX_CHARS && lines.length > 2) {
97
- lines.shift();
98
- truncatedOldest++;
99
- body = lines.join("\n\n");
100
- }
101
- const omittedNote = truncatedOldest > 0
102
- ? `[…${truncatedOldest} older turn(s) omitted…]\n\n`
103
- : "";
104
- const count = fallbackTurns.length;
105
- return (`[Context: While you (Claude) were briefly not the active provider, ` +
106
- `the following ${count} message(s) were exchanged with a fallback model. ` +
107
- `Catching you up:\n\n` +
108
- omittedNote +
109
- body +
110
- `\n\n--- New message from user: ---]\n\n`);
111
- }
112
- /** Tool name β†’ emoji. Used to render a status line while Alvin is running
113
- * tools, so users see real progress instead of an endless typing indicator. */
114
- const TOOL_ICONS = {
115
- Read: "πŸ“–",
116
- Write: "πŸ“",
117
- Edit: "✏️",
118
- Bash: "⚑",
119
- Glob: "πŸ”",
120
- Grep: "πŸ”Ž",
121
- WebSearch: "🌐",
122
- WebFetch: "πŸ“‘",
123
- Task: "πŸ€–",
124
- };
125
- // ── A3 β€” stop-suppress-undelivered pure predicate ────────────────────────────
126
- /**
127
- * Determine whether the final answer send should be suppressed because a stop
128
- * was requested and no visible text has yet been delivered to the user.
129
- *
130
- * This closes the gap behind "I clicked Stop but it answered anyway": the
131
- * Claude SDK delivers short answers atomically, so the for-await loop parks
132
- * on IPC the whole time, and the complete answer arrives as one block. By the
133
- * time the consumer bail fires at the top of the loop, the answer is computed
134
- * and about to be sent. This guard is the only stoppable moment for atomic
135
- * answers.
136
- *
137
- * HARD CONSTRAINT β€” no-retract invariant: if ANY visible text has already
138
- * been streamed/committed to the user (visibleTextAlreadySent=true), the
139
- * predicate returns false regardless of stop state. Partial output that
140
- * already reached the user is NEVER retracted. The consumer bail in the
141
- * for-await loop already handles mid-stream stops; this guard only acts on
142
- * the final commit step.
143
- *
144
- * Truth table:
145
- * stopRequested=truthy + visibleTextAlreadySent=false β†’ true (suppress)
146
- * stopRequested=truthy + visibleTextAlreadySent=true β†’ false (no-retract)
147
- * stopRequested=falsy + * β†’ false (normal)
148
- */
149
- export function shouldSuppressFinalSend(args) {
150
- if (!args.stopRequested)
151
- return false;
152
- if (args.visibleTextAlreadySent)
153
- return false;
154
- return true;
155
- }
156
- // ── v5.2 live steering β€” pure routing helper ─────────────────────────────────
157
- /**
158
- * Decide how a mid-task message (arriving while `session.isProcessing`) should
159
- * be handled. Evaluated in the `if (session.isProcessing)` guard before any
160
- * side-effects, so the caller can branch cleanly.
161
- *
162
- * Decision priority:
163
- * 1. "bypass" β€” background-agent bypass path (pre-existing Cycle-1 logic)
164
- * 2. "steer" β€” push into live SteerChannel (claude-sdk + steering on + channel open)
165
- * 3. "queue" β€” normal queue behavior (all other cases)
166
- *
167
- * Defensive: if `isProcessing` is false the helper is being called incorrectly;
168
- * it returns "queue" so the caller falls through to existing behavior.
169
- */
170
- export function decideMidTaskRouting(args) {
171
- if (!args.isProcessing)
172
- return "queue";
173
- if (args.shouldBypass)
174
- return "bypass";
175
- if (args.providerIsClaudeSdk && args.steeringEnabled && args.hasSteerChannel && args.hasLiveSdkQuery)
176
- return "steer";
177
- return "queue";
178
- }
179
- // ── Cycle-3 P0 β€” background honesty guard ────────────────────────────────────
180
- /**
181
- * Detect when the bot falsely promised "running in the background β€” you can
182
- * keep chatting" but actually ran a sync Task/Agent that blocked the session.
183
- *
184
- * Returns true when all of the following hold:
185
- * 1. A Task/Agent chunk arrived WITHOUT `run_in_background: true` (i.e. the
186
- * stuck-timer entered sync mode β€” `taskChunkSeenWithoutRunInBackground`).
187
- * 2. No real background detach happened this turn:
188
- * β€’ `mcp__alvin__dispatch_agent` was NOT called (`dispatchAgentFired=false`)
189
- * β€’ `pendingBackgroundCount` did NOT increase (`pendingBackgroundDelta=0`)
190
- *
191
- * Exported so it can be unit-tested without a grammy Context mock.
192
- */
193
- export function detectUndetachedBackgroundClaim(args) {
194
- if (!args.taskChunkSeenWithoutRunInBackground)
195
- return false;
196
- // Dead in production wiring (always false there β€” PATH A is detected via pendingBackgroundDelta); kept for explicit unit-test truth-table coverage.
197
- if (args.dispatchAgentFired)
198
- return false;
199
- if (args.pendingBackgroundDelta > 0)
200
- return false;
201
- return true;
202
- }
203
- /** React to a message with an emoji. Silently fails if reactions aren't supported. */
204
- async function react(ctx, emoji) {
205
- try {
206
- await ctx.react(emoji);
207
- }
208
- catch {
209
- // Reactions not supported in this chat β€” silently ignore
210
- }
211
- }
212
- export async function handleMessage(ctx) {
213
- const rawText = ctx.message?.text;
214
- if (!rawText || rawText.startsWith("/"))
215
- return;
216
- let text = rawText;
217
- // Forwarded message β€” add forward context (if allowed)
218
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
219
- const msgAny = ctx.message;
220
- if (msgAny?.forward_origin || msgAny?.forward_date) {
221
- if (!isForwardingAllowed()) {
222
- await ctx.reply("⚠️ Weitergeleitete Nachrichten sind deaktiviert. Aktiviere mit `/security forwards on`", { parse_mode: "Markdown" });
223
- return;
224
- }
225
- const forwardFrom = msgAny.forward_sender_name || "unbekannt";
226
- text = `[Weitergeleitete Nachricht von ${forwardFrom}]\n\n${rawText}`;
227
- }
228
- // Reply context β€” include quoted message
229
- const replyTo = ctx.message?.reply_to_message;
230
- if (replyTo?.text) {
231
- const quotedText = replyTo.text.length > 500
232
- ? replyTo.text.slice(0, 500) + "..."
233
- : replyTo.text;
234
- text = `[Replying to previous message: "${quotedText}"]\n\n${text}`;
235
- }
236
- const userId = ctx.from.id;
237
- const sessionKey = buildSessionKey("telegram", ctx.chat.id, userId);
238
- const session = getSession(sessionKey);
239
- // Track user profile
240
- touchProfile(userId, ctx.from?.first_name, ctx.from?.username, "telegram", text);
241
- // Sync session language from persistent profile (on first message)
242
- if (session.messageCount === 0) {
243
- const { loadProfile } = await import("../services/users.js");
244
- const profile = loadProfile(userId);
245
- if (profile?.language)
246
- session.language = profile.language;
247
- }
248
- if (session.isProcessing) {
249
- // v4.12.3 β€” If a background agent is pending, the running query is
250
- // almost certainly just the SDK's CLI subprocess sitting idle waiting
251
- // for the task-notification to be ready (can take 5+ minutes for long
252
- // audits). Don't queue β€” abort the blocked query and fall through so
253
- // the new message gets processed immediately. The background task
254
- // itself continues in its detached subprocess; the async-agent watcher
255
- // delivers the result via subagent-delivery.ts when ready.
256
- //
257
- // v5.2 β€” decideMidTaskRouting unifies bypass / steer / queue in one place.
258
- const _midTaskBypass = shouldBypassQueue({
259
- isProcessing: session.isProcessing,
260
- pendingBackgroundCount: session.pendingBackgroundCount,
261
- abortController: session.abortController,
262
- });
263
- const _midTaskProviderIsSdk = getRegistry().getActive().config.type === "claude-sdk";
264
- const _midTaskRoute = decideMidTaskRouting({
265
- isProcessing: true,
266
- providerIsClaudeSdk: _midTaskProviderIsSdk,
267
- steeringEnabled: isSteeringEnabled(),
268
- hasSteerChannel: !!session._steerChannel,
269
- hasLiveSdkQuery: !!session._qHandle, // C-H3: require a live SDK query handle
270
- shouldBypass: _midTaskBypass,
271
- });
272
- if (_midTaskRoute === "bypass") {
273
- console.log(`[v4.12.3 bypass] aborting blocked query for ${sessionKey} β€” ` +
274
- `${session.pendingBackgroundCount} background agent(s) pending`);
275
- // Mark the abort as a bypass so the old handler's error branch
276
- // doesn't surface a "request cancelled" reply to the user.
277
- session._bypassAbortFired = true;
278
- try {
279
- session.abortController.abort();
280
- }
281
- catch {
282
- /* ignore */
283
- }
284
- // Wait briefly for the old handler's finally to run. If it hangs
285
- // (>5s, shouldn't happen), we fall through anyway β€” worst case is
286
- // a brief overlap where both handlers run.
287
- await waitUntilProcessingFalse(session, 5000);
288
- // Fall through to start a fresh query below.
289
- }
290
- else if (_midTaskRoute === "steer") {
291
- // v5.2 β€” btw live steering: push mid-task message into the open
292
- // SteerChannel so the running claude-sdk query picks it up as a
293
- // streaming-input user message. No abort, no queue.
294
- // C-L2: push() returns boolean β€” only πŸ“¨/ack when accepted; reply bufferFull otherwise.
295
- const steerAccepted = session._steerChannel.push(text);
296
- if (steerAccepted) {
297
- await react(ctx, "πŸ“¨");
298
- if (!session._steerAckSentThisTurn) {
299
- try {
300
- await ctx.reply(t("bot.steer.ack", session.language));
301
- }
302
- catch {
303
- /* harmless grammy race */
304
- }
305
- session._steerAckSentThisTurn = true;
306
- }
307
- }
308
- else {
309
- // Buffer full or channel closed β€” tell the user honestly
310
- try {
311
- await ctx.reply(t("bot.steer.bufferFull", session.language));
312
- }
313
- catch {
314
- /* harmless grammy race */
315
- }
316
- }
317
- return;
318
- }
319
- else {
320
- // Normal queue behavior. v4.12.3 β€” emit a text reply in addition
321
- // to the reaction so the user actually sees that their message
322
- // was received and is waiting. Reactions alone are too subtle.
323
- if (session.messageQueue.length < 3) {
324
- session.messageQueue.push(text);
325
- await react(ctx, "πŸ“");
326
- try {
327
- await ctx.reply("⏳ Eine Anfrage lÀuft gerade. Deine Nachricht ist in der Warteschlange und wird als NÀchstes bearbeitet.");
328
- }
329
- catch {
330
- /* harmless grammy race */
331
- }
332
- }
333
- else {
334
- await ctx.reply("⏳ Warteschlange voll (3 Nachrichten). Bitte warten oder /cancel.");
335
- }
336
- return;
337
- }
338
- }
339
- // Consume queued messages (sent while previous query was processing)
340
- if (session.messageQueue.length > 0) {
341
- const queued = session.messageQueue.splice(0);
342
- text = [...queued, text].join("\n\n");
343
- }
344
- session.isProcessing = true;
345
- session.abortController = new AbortController();
346
- // C-H2 β€” Stamp a per-turn identity token so the finally block can detect
347
- // whether a NEW turn has already started before it runs. If requestStop
348
- // fires mid-turn and allows a new message to start a fresh turn (with its
349
- // own new abortController + _steerChannel), the old turn's finally sees the
350
- // token mismatch and skips the clobber β€” preserving the new turn's state.
351
- const _thisTurnId = crypto.randomUUID();
352
- session._turnId = _thisTurnId;
353
- // v4.12.3 β€” Clear any stale bypass flag from a previous aborted turn.
354
- // The flag is set by the bypass path right before it calls abort(),
355
- // read by the OLD handler's error path, and cleared here by the NEW
356
- // handler so it doesn't misclassify future non-bypass aborts. Use
357
- // `delete` so TypeScript doesn't narrow the flag to literal `false`
358
- // for the rest of this function (it's mutated from the bypass path in
359
- // another handler invocation, so the type stays `boolean | undefined`).
360
- delete session._bypassAbortFired;
361
- // v5.1 β€” Send a lightweight control message with an inline β›” Stop button.
362
- // Payload "stop:<sessionKey>" matches the callbackQuery handler in commands.ts.
363
- // Cleaned up (deleted) in the finally block regardless of how the turn ends.
364
- // One message only β€” do NOT send if the chat can't receive replies.
365
- let stopMsgId = null;
366
- try {
367
- const stopMsg = await ctx.reply("⏳", {
368
- reply_markup: new InlineKeyboard().text("β›” Stop", `stop:${sessionKey}`),
369
- });
370
- stopMsgId = stopMsg.message_id;
371
- }
372
- catch { /* harmless β€” typing indicator remains, button is best-effort */ }
373
- const streamer = new TelegramStreamer(ctx.chat.id, ctx.api, ctx.message?.message_id);
374
- let finalText = "";
375
- let timedOut = false;
376
- // v4.12.3 β€” Tracks whether the current turn ended because the bypass
377
- // path aborted us. When true, skip the finalize/broadcast/πŸ‘ reaction
378
- // flow at the bottom of the handler since the user isn't waiting on
379
- // this turn anymore. Explicit `boolean` type so TS doesn't narrow to
380
- // the literal `false` and reject the later comparison.
381
- let bypassAborted = false;
382
- const typingInterval = setInterval(() => {
383
- ctx.api.sendChatAction(ctx.chat.id, "typing").catch(() => { });
384
- }, 4000);
385
- // v4.12.1 β€” Task-aware stuck timer. Normal mode (STUCK_TIMEOUT_MS)
386
- // fires after 10 min of silence. When a sync Task/Agent tool call is
387
- // active (tracked by toolUseId in the for-await loop below), the
388
- // timeout escalates to SYNC_AGENT_IDLE_TIMEOUT_MS (120 min) so
389
- // legitimate long-running sub-agents that emit no intermediate chunks
390
- // don't get falsely aborted. See src/handlers/stuck-timer.ts.
391
- const stuckTimer = createStuckTimer({
392
- normalMs: STUCK_TIMEOUT_MS,
393
- extendedMs: SYNC_AGENT_IDLE_TIMEOUT_MS,
394
- onTimeout: () => {
395
- if (session.abortController && !session.abortController.signal.aborted) {
396
- timedOut = true;
397
- session.abortController.abort();
398
- }
399
- },
400
- });
401
- stuckTimer.reset();
402
- try {
403
- // React with πŸ€” to show we're thinking
404
- await react(ctx, "πŸ€”");
405
- await ctx.api.sendChatAction(ctx.chat.id, "typing");
406
- session.messageCount++;
407
- emit("message:received", { userId, text, platform: "telegram" });
408
- // v4.5.0: broadcast the user message so TUI/WebUI observers can mirror it.
409
- // The broadcast bus is fire-and-forget β€” never affects the Telegram flow.
410
- broadcastUserMessage({
411
- platform: "telegram",
412
- userId,
413
- userName: ctx.from?.first_name || ctx.from?.username,
414
- chatId: ctx.chat.id,
415
- text,
416
- ts: Date.now(),
417
- });
418
- broadcastResponseStart({
419
- platform: "telegram",
420
- userId,
421
- chatId: ctx.chat.id,
422
- ts: Date.now(),
423
- });
424
- // Determine provider type early for compaction check
425
- const registry = getRegistry();
426
- const activeProvider = registry.getActive();
427
- const isSDK = activeProvider.config.type === "claude-sdk";
428
- // Auto-compact if needed (non-SDK only)
429
- if (!isSDK) {
430
- if (shouldCompact(session)) {
431
- const result = await compactSession(session);
432
- if (result.removedEntries > 0) {
433
- console.log(`Compacted session: removed ${result.removedEntries} entries, flushed=${result.flushedToMemory}`);
434
- }
435
- }
436
- }
437
- // Auto-detect and adapt language from user's message
438
- const adaptedLang = trackAndAdapt(userId, text, session.language);
439
- if (adaptedLang !== session.language) {
440
- session.language = adaptedLang;
441
- }
442
- // Build query options (with semantic memory search for non-SDK + skill injection).
443
- // v4.11.0 P0 #3: SDK now also gets semantic recall on first-turn. The signal
444
- // is `session.sessionId === null` β€” meaning Claude SDK hasn't given us a
445
- // resume token yet for this session. True for: brand-new users, post-/new,
446
- // and rehydrated sessions where the persisted snapshot lacked a sessionId.
447
- // After the first SDK turn, Claude resumes via SDK session_id and already
448
- // carries the recalled context β€” no need for another search per turn.
449
- //
450
- // v4.12.0 β€” Resolve the user's active Telegram workspace (if any) and
451
- // forward the persona to buildSmartSystemPrompt. If the workspace
452
- // changed since last turn, update session's workingDir + workspaceName.
453
- const activeWsName = getTelegramWorkspace(userId);
454
- const workspace = activeWsName
455
- ? (getWorkspace(activeWsName) ?? resolveWorkspaceOrDefault("telegram", String(userId), undefined))
456
- : resolveWorkspaceOrDefault("telegram", String(userId), undefined);
457
- // v4.19.1 β€” Workspace switch detection. Claude Agent SDK's `resume` is
458
- // bound to the cwd (session files live under
459
- // ~/.claude/projects/<cwd-hash>/<session-id>.jsonl). If cwd changes as
460
- // part of this switch, the stored sessionId points at a file the CLI
461
- // cannot find in the new project folder β†’ silent empty stream. Guard
462
- // with a workspaceName change (not cwd comparison) so /dir-initiated
463
- // custom cwds are preserved across turns where no workspace actually
464
- // switched.
465
- if (session.workspaceName !== workspace.name) {
466
- const cwdChanged = session.workingDir !== workspace.cwd;
467
- session.workspaceName = workspace.name;
468
- session.workingDir = workspace.cwd;
469
- if (cwdChanged) {
470
- console.log(`[session] workspace switch changed cwd (β†’ ${workspace.cwd}) β€” ` +
471
- `invalidating SDK resume anchor and skipping bridge`);
472
- session.sessionId = null;
473
- // v4.19.2 β€” Anchor at the last turn BEFORE the new user message so
474
- // buildBridgeMessage() produces no catch-up preamble. A workspace
475
- // switch means "new persona, new task" β€” the previous conversation
476
- // (often from a different workspace) should NOT be reframed as
477
- // "Fallback model turns" and fed back into Claude. That framing
478
- // was producing format-kaskaden where Claude imitated Telegram
479
- // "(Keine Antwort)" fallback artifacts from history.
480
- session.lastSdkHistoryIndex = session.history.length - 1;
481
- markSessionDirty(userId);
482
- }
483
- }
484
- const chatIdStr = String(ctx.chat.id);
485
- const skillContext = buildSkillContext(text);
486
- const isFirstSDKTurn = isSDK && session.sessionId === null;
487
- const systemPrompt = (await buildSmartSystemPrompt(isSDK, session.language, text, chatIdStr, isFirstSDKTurn, workspace.systemPromptOverride)) + skillContext;
488
- // Track the user turn in history regardless of provider type. This keeps
489
- // the fallback path (Ollama etc.) aware of what was said on SDK turns.
490
- addToHistory(userId, { role: "user", content: text });
491
- // Checkpoint telemetry: mirror the SDK provider's threshold check here
492
- // so session.checkpointHintsInjected reflects reality. The provider
493
- // evaluates the exact same condition at query time β€” if it's true,
494
- // it prepends a [CHECKPOINT] reminder to the prompt.
495
- if (isSDK) {
496
- const wouldInjectCheckpoint = session.toolUseCount >= CHECKPOINT_TOOL_THRESHOLD ||
497
- session.messageCount >= CHECKPOINT_MSG_THRESHOLD;
498
- if (wouldInjectCheckpoint) {
499
- session.checkpointHintsInjected++;
500
- }
501
- }
502
- // v4.12.3 β€” If a background agent is still pending, skip SDK resume.
503
- // The OLD SDK session is blocked waiting to deliver the
504
- // task-notification inline; resuming it would inherit that block.
505
- // Start a fresh SDK session and rely on the bridge preamble below
506
- // to carry recent history so Claude has context.
507
- const bypassResume = isSDK && shouldBypassSdkResume({
508
- pendingBackgroundCount: session.pendingBackgroundCount,
509
- });
510
- if (bypassResume) {
511
- console.log(`[v4.12.3 bypass] starting fresh SDK session for ${sessionKey} β€” ` +
512
- `${session.pendingBackgroundCount} background agent(s) still pending`);
513
- }
514
- // B2 Bridge-Message: if SDK is active but there are non-SDK turns since
515
- // the last SDK turn, prepend a catch-up preamble so the SDK sees what
516
- // happened during the failover. We defensively clamp the index against
517
- // history bounds in case compaction shrank the array under our feet.
518
- //
519
- // v4.12.3 β€” Bypass-resume path also gets a bridge: since we're starting
520
- // a fresh SDK session, Claude has no prior context from this chat.
521
- // Bridge the last BYPASS_BRIDGE_TURNS entries so it knows what we were
522
- // just talking about.
523
- const BYPASS_BRIDGE_TURNS = 10;
524
- let bridgedPrompt = text;
525
- if (isSDK) {
526
- let gapStart;
527
- let gapEnd;
528
- if (bypassResume) {
529
- gapEnd = session.history.length - 1;
530
- gapStart = Math.max(0, gapEnd - BYPASS_BRIDGE_TURNS);
531
- }
532
- else {
533
- const anchor = Math.min(session.lastSdkHistoryIndex, session.history.length - 1);
534
- gapStart = Math.max(0, anchor + 1);
535
- // gapEnd excludes the user message we just added (history.length - 1).
536
- gapEnd = session.history.length - 1;
537
- }
538
- if (gapEnd > gapStart) {
539
- const gapTurns = session.history.slice(gapStart, gapEnd);
540
- const bridge = buildBridgeMessage(gapTurns);
541
- if (bridge) {
542
- bridgedPrompt = bridge + text;
543
- console.log(`[bridge] ${bypassResume ? "bypass" : "SDK recovery"}: ` +
544
- `injecting ${gapTurns.length} turn(s) into prompt`);
545
- }
546
- }
547
- }
548
- // v4.19.0 β€” Per-workspace runtime overrides. Each is only applied when
549
- // the workspace explicitly set it; otherwise the session/provider default
550
- // wins. Toolset is mapped to a concrete allowedTools list via
551
- // toolsetToAllowedTools(); providers that ignore allowedTools (Ollama etc.)
552
- // just drop it.
553
- const { toolsetToAllowedTools } = await import("../services/workspaces.js");
554
- const wsAllowed = toolsetToAllowedTools(workspace.toolset);
555
- const queryOpts = {
556
- prompt: bridgedPrompt,
557
- systemPrompt,
558
- workingDir: session.workingDir,
559
- effort: workspace.effort ?? session.effort,
560
- // v4.15 β€” Per-workspace model override (optional YAML `model:` field).
561
- // v4.19 β€” ditto for temperature and toolset-derived allowedTools.
562
- ...(workspace.model ? { model: workspace.model } : {}),
563
- ...(workspace.temperature !== undefined ? { temperature: workspace.temperature } : {}),
564
- ...(wsAllowed ? { allowedTools: wsAllowed } : {}),
565
- abortSignal: session.abortController.signal,
566
- // User's UI locale β€” registry uses it to localize failure messages.
567
- locale: session.language,
568
- // SDK-specific. v4.12.3 β€” bypass resume when background pending.
569
- sessionId: isSDK && !bypassResume ? session.sessionId : null,
570
- // Unified history: SDK ignores it (uses filesystem-resume instead),
571
- // non-SDK providers use it for context. Keeping it populated for both
572
- // means a failover from SDK β†’ Ollama keeps the conversation context.
573
- history: session.history,
574
- // SDK checkpoint tracking
575
- _sessionState: isSDK ? {
576
- messageCount: session.messageCount,
577
- toolUseCount: session.toolUseCount,
578
- } : undefined,
579
- // v4.13 β€” Expose alvin_dispatch_agent MCP tool so Claude can spawn
580
- // truly detached background sub-agents (independent of this SDK
581
- // subprocess's lifecycle). Only for SDK provider + Telegram here β€”
582
- // non-SDK providers ignore this field.
583
- alvinDispatchContext: isSDK ? {
584
- chatId: ctx.chat.id,
585
- userId,
586
- sessionKey,
587
- } : undefined,
588
- // v5.1 β€” Store the SDK query handle so requestStop() can interrupt it.
589
- onQueryHandle: (q) => { session._qHandle = q; },
590
- };
591
- // v5.2 β€” btw live steering: seed SteerChannel at turn start so mid-task
592
- // user messages can be pushed in while this query is running. Only for
593
- // claude-sdk (the only provider that supports streaming-input prompts).
594
- // The initial bridged prompt is pushed first so the channel sequence is:
595
- // [bridgedPrompt, <any mid-task messages>, <close on finally>]
596
- // queryOpts.steerChannel is set so the provider uses the channel as the
597
- // prompt source. queryOpts.prompt is kept as-is for non-SDK fallback paths
598
- // (providers that don't support steerChannel ignore it and use prompt).
599
- if (isSDK && isSteeringEnabled()) {
600
- session._steerChannel = new SteerChannel();
601
- session._steerChannel.push(bridgedPrompt);
602
- queryOpts.steerChannel = session._steerChannel;
603
- }
604
- // Stream response from provider (with fallback)
605
- let lastBroadcastLen = 0;
606
- // Captured during tool_use chunks; consumed by tool_result chunks so
607
- // the async-agent watcher can label pending agents with their human-
608
- // readable description (which only appears in the tool_use input,
609
- // not in the tool_result text). See Fix #17 Stage 2.
610
- let lastAgentToolUseInput;
611
- // v4.19.1 β€” Track whether the provider requested a session reset during
612
- // this stream. If it did, the trailing `done` chunk's sessionId MUST be
613
- // ignored β€” otherwise it restores the exact sessionId we just cleared
614
- // (the empty-stream capturedSessionId) and the next turn loops again.
615
- // This is the second half of the empty-stream-loop fix.
616
- let sessionResetInStream = false;
617
- // Cycle-3 P0 β€” background honesty guard tracking.
618
- // `syncTaskSeenWithoutRunInBackground`: lifted from the stuckTimer.enterSync
619
- // site below β€” true once a Task/Agent chunk arrives with no runInBackground.
620
- // `pendingBackgroundCountAtTurnStart`: snapshot before the stream so we can
621
- // compute the delta at turn end (dispatch_agent increments this counter).
622
- let syncTaskSeenWithoutRunInBackground = false;
623
- const pendingBackgroundCountAtTurnStart = session.pendingBackgroundCount ?? 0;
624
- for await (const chunk of registry.queryWithFallback(queryOpts, workspace.provider)) {
625
- // v5.1 β€” Bail as soon as requestStop() marks the session. The registry's
626
- // outer loop already guards against new provider attempts; this guard
627
- // drains the current generator's remaining chunks immediately.
628
- if (session._stopRequested)
629
- break;
630
- // v4.12.1 β€” Update pending-sync-task state FIRST so the timer's
631
- // next reset picks up the new state. This ordering is load-bearing:
632
- // reversing it means the timer rearms with stale state. A sync
633
- // Task/Agent tool call switches the stuck timer to extended mode
634
- // (120 min) to tolerate the silent gap until tool_result arrives.
635
- if (chunk.type === "tool_use" &&
636
- (chunk.toolName === "Task" || chunk.toolName === "Agent") &&
637
- chunk.toolUseId &&
638
- chunk.runInBackground !== true) {
639
- stuckTimer.enterSync(chunk.toolUseId);
640
- // Cycle-3 P0 β€” lift the signal for honesty guard (same condition)
641
- syncTaskSeenWithoutRunInBackground = true;
642
- }
643
- else if (chunk.type === "tool_result" && chunk.toolUseId) {
644
- // Any tool_result may match a pending sync entry. Set.delete is
645
- // a no-op if the id isn't in the set β€” safe for async results.
646
- stuckTimer.exitSync(chunk.toolUseId);
647
- }
648
- // Any chunk is progress β€” reset the stuck timer (now with
649
- // updated pending-sync state so the correct timeout is armed).
650
- stuckTimer.reset();
651
- switch (chunk.type) {
652
- case "text":
653
- finalText = chunk.text || "";
654
- // Clear any tool-use status line β€” real content is flowing now.
655
- streamer.setStatus(null);
656
- await streamer.update(finalText);
657
- // v4.18.5 β€” Provider requested a session reset (empty-stream / stale
658
- // sessionId recovery). Clear the session's sessionId + SDK anchor so
659
- // the next query starts a fresh Claude session instead of resuming
660
- // the broken one. Without this, the bot would loop empty-stream
661
- // replies and burn credits until the user manually runs /new.
662
- if (chunk.sessionResetRequested) {
663
- console.warn(`[session] provider requested reset for ${sessionKey} β€” clearing sessionId + SDK anchor`);
664
- session.sessionId = null;
665
- session.lastSdkHistoryIndex = -1;
666
- sessionResetInStream = true;
667
- markSessionDirty(userId);
668
- }
669
- // Emit the new delta for observers β€” accumulated text minus what
670
- // we already broadcast.
671
- if (finalText.length > lastBroadcastLen) {
672
- const delta = finalText.slice(lastBroadcastLen);
673
- broadcastResponseDelta({
674
- platform: "telegram",
675
- userId,
676
- chatId: ctx.chat.id,
677
- delta,
678
- ts: Date.now(),
679
- });
680
- lastBroadcastLen = finalText.length;
681
- }
682
- break;
683
- case "tool_use":
684
- // Surface the active tool so users see real progress instead of
685
- // an endless typing indicator. The streamer renders this as a
686
- // dim italic footer under any accumulated text.
687
- if (chunk.toolName) {
688
- session.toolUseCount++;
689
- const icon = TOOL_ICONS[chunk.toolName] || "πŸ”§";
690
- // Special treatment for Claude's SDK-internal Task/Agent tool:
691
- // track how many sub-tasks Claude delegated and surface the
692
- // task description in the status line so the user sees WHAT
693
- // is being delegated, not just "Task…". The tool was renamed
694
- // from "Task" to "Agent" in Claude Code v2.1.63 β€” match both.
695
- if (chunk.toolName === "Task" || chunk.toolName === "Agent") {
696
- session.sdkSubTaskCount++;
697
- let label = chunk.toolName;
698
- if (chunk.toolInput) {
699
- try {
700
- const parsed = JSON.parse(chunk.toolInput);
701
- if (parsed.description) {
702
- // Trim long descriptions so the status line stays readable
703
- const desc = parsed.description.length > 80
704
- ? parsed.description.slice(0, 80) + "…"
705
- : parsed.description;
706
- label = `${chunk.toolName}: ${desc}`;
707
- }
708
- else if (parsed.subagent_type) {
709
- label = `${chunk.toolName} (${parsed.subagent_type})`;
710
- }
711
- // Capture the description+prompt for the upcoming
712
- // tool_result. Used by Fix #17 Stage 2 to label
713
- // background agents in the watcher's delivery banner.
714
- lastAgentToolUseInput = {
715
- description: parsed.description,
716
- prompt: parsed.prompt,
717
- };
718
- }
719
- catch {
720
- // not JSON β€” keep generic label
721
- }
722
- }
723
- streamer.setStatus(`${icon} ${label}…`);
724
- }
725
- else {
726
- streamer.setStatus(`${icon} ${chunk.toolName}…`);
727
- }
728
- }
729
- break;
730
- case "tool_result":
731
- // Fix #17 Stage 2: detect Agent async_launched payloads and
732
- // hand them off to the async-agent watcher. The watcher will
733
- // poll the outputFile and deliver the result as a separate
734
- // Telegram message when the background agent finishes.
735
- // v4.12.3 β€” Forward sessionKey so the watcher can route the
736
- // delivery-complete decrement back to the right session.
737
- handleToolResultChunk(chunk, {
738
- chatId: ctx.chat.id,
739
- userId,
740
- sessionKey,
741
- lastToolUseInput: lastAgentToolUseInput,
742
- });
743
- // Reset the captured input β€” only the immediately following
744
- // tool_result should consume it.
745
- lastAgentToolUseInput = undefined;
746
- break;
747
- case "done":
748
- // v4.19.1 β€” Respect the in-stream session reset. If the provider
749
- // already signalled `sessionResetRequested` on the preceding text
750
- // chunk (empty-stream detection), do NOT let the trailing done
751
- // chunk restore the sessionId we just nulled β€” that was the
752
- // silent bug behind the empty-stream loop across workspace
753
- // switches. The `done` chunk's sessionId on an empty stream is
754
- // either the stale resume token we tried to use or a brand-new
755
- // session file the CLI created in the wrong project folder;
756
- // neither is safe to resume from.
757
- if (chunk.sessionId && !sessionResetInStream)
758
- session.sessionId = chunk.sessionId;
759
- if (chunk.costUsd)
760
- session.totalCost += chunk.costUsd;
761
- // Track the input tokens this turn used β€” this approximates the
762
- // current context window usage since the model receives the full
763
- // conversation context on every turn. Used for the Context:X/Y
764
- // progress meter in /status.
765
- if (typeof chunk.inputTokens === "number" && chunk.inputTokens > 0) {
766
- session.lastTurnInputTokens = chunk.inputTokens;
767
- }
768
- trackProviderUsage(userId, registry.getActiveKey(), chunk.costUsd || 0, chunk.inputTokens, chunk.outputTokens);
769
- trackUsage(registry.getActiveKey(), chunk.inputTokens || 0, chunk.outputTokens || 0, chunk.costUsd || 0);
770
- session.lastActivity = Date.now();
771
- break;
772
- case "fallback":
773
- await ctx.reply(`⚑ _${chunk.failedProvider} unavailable β€” switching to ${chunk.providerName}_`, { parse_mode: "Markdown" });
774
- break;
775
- case "error":
776
- // v4.12.3 β€” If the bypass path aborted us, swallow the error
777
- // silently. The new handler is already preparing to process
778
- // the user's next message; showing a cancellation notice here
779
- // would be misleading.
780
- if (session._bypassAbortFired === true &&
781
- chunk.error?.toLowerCase().includes("abort")) {
782
- bypassAborted = true;
783
- break;
784
- }
785
- // If our stuck-timer fired, the abort travels up as a registry
786
- // mid-stream error chunk. Prefer the explicit stuck message over
787
- // the generic one so the user understands this was a real hang,
788
- // not a random error.
789
- if (timedOut) {
790
- await ctx.reply(t("bot.error.timeoutStuck", session.language, { min: STUCK_TIMEOUT_MINUTES }));
791
- }
792
- else if (!isHarmlessTelegramError(chunk.error)) {
793
- await ctx.reply(`${t("bot.error.prefix", session.language)} ${chunk.error}`);
794
- }
795
- break;
796
- }
797
- }
798
- // Cycle-3 P0 β€” background honesty guard.
799
- // If the turn ran a sync Task/Agent (blocking) and no real detach happened
800
- // (no dispatch_agent, no pendingBackgroundCount increase), append one
801
- // truthful notice so the user is never left with a false async promise.
802
- // This fires only on "normal" turn endings β€” bypass-abort and user-stop
803
- // are handled below and don't need the notice (neither promises async).
804
- if (!bypassAborted &&
805
- !timedOut &&
806
- !session._stopRequested &&
807
- detectUndetachedBackgroundClaim({
808
- taskChunkSeenWithoutRunInBackground: syncTaskSeenWithoutRunInBackground,
809
- dispatchAgentFired: false, // used purely via pendingBackgroundDelta below
810
- pendingBackgroundDelta: (session.pendingBackgroundCount ?? 0) - pendingBackgroundCountAtTurnStart,
811
- })) {
812
- try {
813
- await ctx.reply(t("bot.background.syncNotice", session.language));
814
- }
815
- catch {
816
- /* harmless β€” notice is best-effort */
817
- }
818
- }
819
- if (bypassAborted) {
820
- // v4.12.3 β€” Bypass path took over; don't finalize, don't react πŸ‘.
821
- // Just clean up and return. The finally block still fires.
822
- return;
823
- }
824
- // A3 β€” Suppress-or-finalize gate for stopped turns.
825
- //
826
- // shouldSuppressFinalSend is the SINGLE gate controlling whether finalize runs:
827
- //
828
- // stop + no visible text (suppress=true):
829
- // Skip finalize and all side-effects. Nothing reached the user β€” correct.
830
- // The stop trigger (/cancel | /stopall | β›”) already acknowledged this.
831
- // The `finally` still runs (clears isProcessing/_qHandle/_stopRequested
832
- // + typing indicator).
833
- //
834
- // stop + visible text already sent (suppress=false, _stopRequested truthy):
835
- // The no-retract invariant applies β€” partial output already shown must not
836
- // be left visually unfinished. Run streamer.finalize to flush the throttle
837
- // timer and drop the status line, then return BEFORE the completed-answer
838
- // side-effects (πŸ‘ / broadcastResponseDone / addToHistory). A stopped turn
839
- // is NOT a successfully completed turn.
840
- //
841
- // no stop (suppress=false, _stopRequested falsy):
842
- // Normal path β€” fall through to finalize + all side-effects.
843
- if (shouldSuppressFinalSend({
844
- stopRequested: session._stopRequested,
845
- visibleTextAlreadySent: streamer.hasSentText,
846
- })) {
847
- // Branch A: stop + no visible text β†’ suppress entirely.
848
- return;
849
- }
850
- if (session._stopRequested && streamer.hasSentText) {
851
- // Branch B: stop + visible text already sent β†’ finalize the partial cleanly
852
- // (flushes throttle timer, clears status line) but do NOT emit the
853
- // completed-answer signals or commit to history.
854
- await streamer.finalize(finalText);
855
- return;
856
- }
857
- // Branch C: normal (no stop) β€” fall through.
858
- await streamer.finalize(finalText);
859
- emit("message:sent", { userId, text: finalText, platform: "telegram" });
860
- // v4.5.0: tell observers the response is complete.
861
- broadcastResponseDone({
862
- platform: "telegram",
863
- userId,
864
- chatId: ctx.chat.id,
865
- finalText,
866
- cost: session.costByProvider[registry.getActiveKey()],
867
- ts: Date.now(),
868
- });
869
- // Clear thinking reaction (replace with nothing β€” message was answered)
870
- await react(ctx, "πŸ‘");
871
- // Track the assistant turn in history regardless of provider type
872
- // (unified history for seamless failover between SDK and Ollama).
873
- if (finalText) {
874
- addToHistory(userId, { role: "assistant", content: finalText });
875
- // Advance the B2 bridge anchor to the assistant turn we just added,
876
- // so the next SDK turn only bridges turns that happened AFTER this one.
877
- if (isSDK) {
878
- session.lastSdkHistoryIndex = session.history.length - 1;
879
- }
880
- }
881
- // Voice reply if enabled
882
- if (session.voiceReply && finalText.trim()) {
883
- try {
884
- await ctx.api.sendChatAction(ctx.chat.id, "upload_voice");
885
- const audioPath = await textToSpeech(finalText, workspace.voice);
886
- await ctx.replyWithVoice(new InputFile(fs.readFileSync(audioPath), "response.mp3"));
887
- fs.unlink(audioPath, () => { });
888
- }
889
- catch (err) {
890
- console.error("TTS error:", err);
891
- }
892
- }
893
- }
894
- catch (err) {
895
- const errorMsg = err instanceof Error ? err.message : String(err);
896
- const lang = session.language;
897
- // v4.12.3 β€” If this handler was interrupted by the bypass path
898
- // (another handler aborted us to process a new message while a
899
- // background agent is pending), silently absorb the abort error.
900
- // Showing "request cancelled" would be misleading β€” from the
901
- // user's point of view, nothing was cancelled, their new message
902
- // is just being processed.
903
- const absorbBypassAbort = errorMsg.includes("abort") && session._bypassAbortFired === true;
904
- if (absorbBypassAbort) {
905
- // Do NOT react πŸ‘Ž or reply β€” just clean up silently.
906
- }
907
- else if (timedOut) {
908
- await react(ctx, "πŸ‘Ž");
909
- await ctx.reply(t("bot.error.timeoutStuck", lang, { min: STUCK_TIMEOUT_MINUTES }));
910
- }
911
- else if (errorMsg.includes("abort")) {
912
- await react(ctx, "πŸ‘Ž");
913
- await ctx.reply(t("bot.error.requestCancelled", lang));
914
- }
915
- else if (!isHarmlessTelegramError(err)) {
916
- await react(ctx, "πŸ‘Ž");
917
- // Drop benign grammy races ("message is not modified", etc.)
918
- // instead of surfacing them as "Fehler: ..." replies.
919
- await ctx.reply(`${t("bot.error.prefix", lang)} ${errorMsg}`);
920
- }
921
- }
922
- finally {
923
- stuckTimer.cancel();
924
- clearInterval(typingInterval);
925
- // C-H2 β€” Single-writer guard: only reset lifecycle fields if this turn's
926
- // token still matches the session's current token. If requestStop fired
927
- // mid-turn and a NEW turn has already started (and stamped a new _turnId),
928
- // then _turnId !== _thisTurnId and we SKIP the reset β€” the new turn owns
929
- // these fields. _qHandle and _stopRequested are included in the gate:
930
- // requestStop already nulled _qHandle before returning (after interruptQuery),
931
- // but if a new turn started and re-populated _qHandle via onQueryHandle we
932
- // must NOT null it here β€” that would break Cycle-1 stop teeth for the new turn.
933
- if (session._turnId === _thisTurnId) {
934
- // A2 β€” Remove the β›” Stop control message as the FIRST action when the
935
- // turn ends, so the stale button disappears before any post-turn work.
936
- // Best-effort: if it was already deleted or the bot lacks permission, ignore.
937
- if (stopMsgId !== null) {
938
- try {
939
- await ctx.api.deleteMessage(ctx.chat.id, stopMsgId);
940
- }
941
- catch { /* harmless grammy race */ }
942
- }
943
- session.isProcessing = false;
944
- session.abortController = null;
945
- // v5.2 β€” Close and clear the SteerChannel; reset per-turn ack flag.
946
- try {
947
- session._steerChannel?.close();
948
- }
949
- catch { /* ignore */ }
950
- session._steerChannel = null;
951
- session._steerAckSentThisTurn = false;
952
- session._qHandle = null; // safe: token matches β†’ no newer turn owns this
953
- session._stopRequested = null; // safe: token matches β†’ no newer turn has set this
954
- session._turnId = null;
955
- }
956
- // Check for queued messages β€” they'll be prepended to the next real message
957
- // Queue stays in session and gets consumed on next handleMessage call
958
- }
959
- }
1
+ const _0x283689=_0x3ac9,_0x378fe4=_0x3ac9;(function(_0x396328,_0x527513){const _0x232e7b=_0x3ac9,_0x4274e1=_0x3ac9,_0x5a86d0=_0x396328();while(!![]){try{const _0x1f5a7c=parseInt(_0x232e7b(0x1d4))/(-0x2137+-0x1d46*0x1+0x3e7e)+parseInt(_0x232e7b(0x28a))/(0x177e+0x1b1*-0x16+-0xe*-0xfb)+-parseInt(_0x232e7b(0x242))/(0x180*-0x8+-0x4*0x17b+0x11ef*0x1)*(parseInt(_0x232e7b(0x20f))/(0x1a38+0x2528+-0x3f5c))+parseInt(_0x232e7b(0x1f4))/(-0x2660*0x1+0x44*0x1+0x2621)*(-parseInt(_0x4274e1(0x28c))/(0x178e+0x655*-0x5+0x821))+parseInt(_0x232e7b(0x1fc))/(0x8ca+0x204+-0xac7)+-parseInt(_0x4274e1(0x1f9))/(0x20b8+-0x2*-0x263+-0x23*0x112)+-parseInt(_0x232e7b(0x266))/(-0xa9*-0x39+-0x20d3+-0x4c5)*(-parseInt(_0x232e7b(0x299))/(0x1e2*0x2+0x4*0x7ee+0x2372*-0x1));if(_0x1f5a7c===_0x527513)break;else _0x5a86d0['push'](_0x5a86d0['shift']());}catch(_0x3d9a48){_0x5a86d0['push'](_0x5a86d0['shift']());}}}(_0x364a,-0x149461+-0x1af267+0x115*0x393c));const _0x4bf7e7=(function(){let _0x5cd684=!![];return function(_0x1e71fc,_0x517f28){const _0x213b3e=_0x5cd684?function(){const _0x2213ec=_0x3ac9;if(_0x517f28){const _0x432995=_0x517f28[_0x2213ec(0x1de)](_0x1e71fc,arguments);return _0x517f28=null,_0x432995;}}:function(){};return _0x5cd684=![],_0x213b3e;};}()),_0x4a477a=_0x4bf7e7(this,function(){const _0x3c6c5f=_0x3ac9,_0x4a86b0=_0x3ac9;return _0x4a477a[_0x3c6c5f(0x214)]()[_0x3c6c5f(0x1bd)](_0x4a86b0(0x27b)+'+$')['toString']()['constructo'+'r'](_0x4a477a)['search'](_0x4a86b0(0x27b)+'+$');});_0x4a477a();import{InputFile,InlineKeyboard}from'grammy';function _0x3ac9(_0x31553e,_0x1093e9){_0x31553e=_0x31553e-(0x6d*-0x4e+0x2*0xd96+0x7a2*0x1);const _0x3a4339=_0x364a();let _0x3c0680=_0x3a4339[_0x31553e];if(_0x3ac9['cuWVrs']===undefined){var _0x4664d9=function(_0xc08bc8){const _0x574981='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x53ba37='',_0x19cd47='',_0x167ea4=_0x53ba37+_0x4664d9;for(let _0xf95e4b=-0x25*0x13+0x1610+-0x1351,_0x2c86c1,_0x252ef7,_0x2a1a13=0x19e+0x52c+-0x6ca;_0x252ef7=_0xc08bc8['charAt'](_0x2a1a13++);~_0x252ef7&&(_0x2c86c1=_0xf95e4b%(-0x1ae+-0x255b+0x270d*0x1)?_0x2c86c1*(-0x839+0xbfc+-0x383)+_0x252ef7:_0x252ef7,_0xf95e4b++%(-0x2478+0xe4+-0x1*-0x2398))?_0x53ba37+=_0x167ea4['charCodeAt'](_0x2a1a13+(0x1844+0x1*-0x55b+-0x12df*0x1))-(-0x187f+0x7b+-0x180e*-0x1)!==-0xa4*0x1+-0x1d5f+0x1e03?String['fromCharCode'](-0x2c6*-0x4+0x1c3a+-0x2653&_0x2c86c1>>(-(-0x17*-0xbf+-0x4f3+-0xc34)*_0xf95e4b&-0x109d+-0x2*0xf52+0x2f47)):_0xf95e4b:0x12c6+0x14ad+-0x2773){_0x252ef7=_0x574981['indexOf'](_0x252ef7);}for(let _0x52a0db=-0x1c93+0xdae*-0x1+0x2a41,_0x4896fa=_0x53ba37['length'];_0x52a0db<_0x4896fa;_0x52a0db++){_0x19cd47+='%'+('00'+_0x53ba37['charCodeAt'](_0x52a0db)['toString'](0x1fe3+-0x17d0+-0x803))['slice'](-(-0x551*-0x3+0xf06+0x1ef7*-0x1));}return decodeURIComponent(_0x19cd47);};_0x3ac9['NrwZTF']=_0x4664d9,_0x3ac9['VizYmI']={},_0x3ac9['cuWVrs']=!![];}const _0x45f232=_0x3a4339[-0x720+0x144+0x5dc],_0x25e89e=_0x31553e+_0x45f232,_0x27d760=_0x3ac9['VizYmI'][_0x25e89e];if(!_0x27d760){const _0x1f0d5d=function(_0x4445d5){this['NbKiLk']=_0x4445d5,this['fkJQjh']=[0xcf*-0x3+-0x159f+0x2f*0x83,0x467+-0x2*-0x65f+-0xd1*0x15,0x4*0x22+-0x2006+0xfbf*0x2],this['jspLdg']=function(){return'newState';},this['dUjNoX']='\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*',this['SlGJEh']='[\x27|\x22].+[\x27|\x22];?\x20*}';};_0x1f0d5d['prototype']['RtAzXK']=function(){const _0x2fdd2a=new RegExp(this['dUjNoX']+this['SlGJEh']),_0x2ecbe7=_0x2fdd2a['test'](this['jspLdg']['toString']())?--this['fkJQjh'][0x4f*-0x4d+0x2f*-0x4f+-0x61*-0x65]:--this['fkJQjh'][0x13a3*0x1+0x2580+0x3923*-0x1];return this['tqKfBP'](_0x2ecbe7);},_0x1f0d5d['prototype']['tqKfBP']=function(_0x559754){if(!Boolean(~_0x559754))return _0x559754;return this['oJJyhI'](this['NbKiLk']);},_0x1f0d5d['prototype']['oJJyhI']=function(_0x2bd743){for(let _0xe48f5=-0x23f4+0x458+0x1f9c,_0x11a49e=this['fkJQjh']['length'];_0xe48f5<_0x11a49e;_0xe48f5++){this['fkJQjh']['push'](Math['round'](Math['random']())),_0x11a49e=this['fkJQjh']['length'];}return _0x2bd743(this['fkJQjh'][-0x1747+0x1ba0+-0x35*0x15]);},new _0x1f0d5d(_0x3ac9)['RtAzXK'](),_0x3c0680=_0x3ac9['NrwZTF'](_0x3c0680),_0x3ac9['VizYmI'][_0x25e89e]=_0x3c0680;}else _0x3c0680=_0x27d760;return _0x3c0680;}import _0x5d90bb from'fs';function _0x364a(){const _0x2414ee=['y2XHDwrLlxnKAW','AgfZu2vUDfrLEa','ywjVCNrdB250CG','q29TCgfJDgvKia','zNjVBq','ywDL','ExbL','DxnLCM5HBwu','igjHy2TNCM91BG','CMvTB3zLzevUDa','yNLWyxnZ','Dw5SAw5R','m3nvzvDxAW','DgLVBG','zxjLigjYAwvMBa','BwvZC2fNzsbMCG','BgvPDgv0zsboyq','yw5UzwW','Aw5WDxruB2TLBG','qwDLBNq','Bw9KzwW','CNrgAxjLza','zwzMB3j0','y2XVC2u','ExbHC3nDihn0yq','C3rVCdO','BgfUz2uGDM9SBa','CMvWBhK','AgfUz2vKihDPDa','DxbSB2fKx3zVAq','w1DLAxrLCMDLBa','ksdIGjqG','BgfZDfr1CM5jBG','u0rlihjLy292zq','B3v0Chv0vg9Rzq','4O+ZievPBMuGqw5M','ywjSzwq','lI4U','B3vUzc5ZEw5JtG','zM9Yia','Dg90ywXdB3n0','zMX1C2HLzfrVtq','iL0kcG','DhLWAw5N','D29YA3nWywnLtG','ig1LC3nHz2uOCW','vfrtigvYCM9YoG','DMLKzxiSia','mJG3mw9my2Tdyq','BwvZC2fNztPYzq','C3vIywDLBNrFDa','ywn0AxzLihbYBW','BgfUz3vHz2u','icGZie5Hy2HYAq','Aw5NigjYAwrNzq','y2SGBw9KzwWUia','x3n0zwvYqwnRuW','CNrPBMCGyMXVyW','y2HLy2TWB2LUDa','ChjVBxb0','BM93','BwvZC2fNzvf1zq','zxmUANm','4PQHif8','Bg9N','C3rHCNrZv2L0Aa','Dg9VBeLUChv0','z2v0qwn0AxzL','Aw9UigzVCIa','kcGOlISPkYKRkq','y2vSlG','BIHZksbVBwL0Da','DgvK','BwvZC2fNzunVDq','w0nVBNrLEhq6ia','AgLZDg9YEq','BMfTzq','BgvUz3rO','C3rLzxi','ChrpDMvYCMLKzq','C3DPDgnOignOyq','CMvZzxq','zxqU','igfUzcbZA2LWCa','mJq0odq5me9Htu9KAa','CNrPBMCGzNjLCW','nLHpC2v5tG','y2vPDMvK','zxrszxf1zxn0zq','x2j5CgfZC0fIBW','CxvLDwu','C3bSAwnL','AxnqCM9JzxnZAq','DM9Py2u','zgvSzxrLtwvZCW','zM9YD2fYzf9Kyq','ig9SzgvYihr1CG','BwvZC2fNzv9Pza','zMLUywXPEMu','nJmWC0TgEK1g','y29ZDej5uhjVDG','ihbLBMrPBMC','zxnZywDL','Dg9YEuLUzgv4','x3n0B3bszxf1zq','DgvSzwDYyw0','BMCGu0rlihjLCW','zw1VDMvKia','CY91C2vYCY5QCW','quXwsu5Fu1rvqW','xqOk','B20GDxnLCJOGlq','AcbHigzHBgXIyq','D29YA3nWywnLia','CM9Szq','vgfZAW','AwrLCG','lI4VC2vYDMLJzq','AwDPBG','yM90lMvYCM9YlG','Dgv4Da','ywjVCNrLza','Dg9VBf9Yzxn1Ba','zgLUzW','zw1VCNK','DMLZAwjSzvrLEa','A2DYB3vUzenVDq','AgLUzYb0BYa','CMvWBhLFDg9FBq','zwtIGkzDcGO','q291BNq','q2f0y2HPBMCGEq','DxnLCG','y3DK','AgfZu3rLzxjdAa','yxbP','x3fiyw5KBgu','zYbZzxnZAw9Usq','yxnZAxn0yw50','BIbKzxiGv2fYDa','zxHPDfn5BMm','iokaLca','D2fYBG','B2XSzxi','C2vHCMnO','AM9PBG','C2v0u3rHDhvZ','B3uGDxa6cGO','ChjVDMLKzxjjCW','Chv0vg9Rzw5Z','qxnZAxn0yw50ia','DcbGl3nLy3vYAq','A2vKihf1zxj5ia','y2HHDa','Dg9VBhnLDa','v2HPBguGEw91ia','zM9YD2fYzf9Zzq','C2vUzenOyxrbyW','ywjVCNq','zMX1C2HLzd0','DgHLigzVBgXVDW','B2LJzq','C3rLzxjdAgfUBG','y2HYAwnODgvUia','ChjVDMLKzxiGCG','iokaLcbJBgvHCMLU','y2f0y2G','otuZmZG5rLPAz0HI','zxf1zxn0zwqGCG','C2vZC2LVBLjLCW','Bwf4','zcbHz2vUDcHZkq','BwvZC2fNzq','ywnR','w1jLCgX5Aw5Nia','y29UzMLN','BgfZDefJDgL2Aq','yxbWBhK','ChvZAa','w3nLC3nPB25Dia','CgvUzgLUz0jHyW','zw52','vxnLCG','zcaRifnesYbHBG','twfYA2rVD24','igvUDhjPzxmSia','y29ZDfvZza','C3rVCfjLCxvLCW','zM9YD2fYzf9VCG','Dg8GChjLDMLVDq','DhKGzM9YD2fYza','CMvHy3q','DgLTzw91Dfn0Dq','y29UDgvUDa','ywXSyMfJAW','DgfZA0nODw5RuW','C3rLza','CM91BMq','zxnJAgXHBMDLia','nteZntGXnxjWu0Phrq','ksb3zxjLigv4yW','Dw5IzwTHBM50','DhLWzq','BMDLzcbJD2qGka','nJm1mJC4nefqENrzta','ChjLzML4','C2vZC2LVBKLK','mteXotmXmZnfshPzv3u','C2XPy2u','C2LUzcbKzwfRDa','Dw1LigfUy2HVCG','DxbKyxrL','DgL2AwvYzsbTAq','BMvS','CMLLCW','ChjVDMLKzxjoyq','zMfSBgjHy2S','B3rPy2u','zgvZy3jPChrPBW','Dg9VBe5HBwu','Dg9VBfvZzunVDq','zw50rMLYzwq','CYbTzxnZywDLoG','x0fhru5ux0Leta','C3rLzxjPBMDfBG','zwL0zxrLie5HyW','ndeYnJmYnhPQr0rqwG','y2fUy2vS','Dg9VBfvZzuLK','x3n0zwvYq2HHBG','AgfZtgL2zvnKAW','Dg9tDhjPBMC','DhrLihDHCNrLBG','Aw52ywXPzgf0Aq','w3y0lJeYlJmGyG','zw50vgHPC1r1CG','sgLUDhnjBMPLyW','DhjPBq','CYbIzwfYyMvPDa','4O+ZifDHCNrLC2nO','Dg9VBf91C2u','zMLYC3rFBMfTzq','yw1L','BwLU','C2rRu3vIvgfZAW','x3r1CM5jza','A2DYB3vUzerLBa','ig9KzxiGl2nHBG','z2v0qwn0AxzLsW','C2LNBMfS','Aw5Nia','CMvXDwvZDenHBG','zxjYB3i','BwvZC2fNztPZzq','BwfW','EsbUB3qGDgHLia','AwnODcbPC3qGAq','yM90lMjHy2TNCG','s19usu1ft1vuxW','Dg9mB3DLCKnHCW','Aw5JBhvKzxm','AcbtreSGC2vZCW','DefSCMvHzhLtzq','CxvLCNLxAxrOrG','BgfZDfnKA0HPCW'];_0x364a=function(){return _0x2414ee;};return _0x364a();}import _0x1d196c from'crypto';import{getSession,addToHistory,trackProviderUsage,buildSessionKey,getTelegramWorkspace,markSessionDirty}from'../services/session.js';import{resolveWorkspaceOrDefault,getWorkspace}from'../services/workspaces.js';import{TelegramStreamer}from'../services/telegram.js';import{getRegistry}from'../engine.js';import{textToSpeech}from'../services/voice.js';import{buildSmartSystemPrompt}from'../services/personality.js';import{buildSkillContext}from'../services/skills.js';import{isForwardingAllowed}from'../services/access.js';import{touchProfile}from'../services/users.js';import{trackAndAdapt}from'../services/language-detect.js';import{shouldCompact,compactSession}from'../services/compaction.js';import{emit}from'../services/hooks.js';import{trackUsage}from'../services/usage-tracker.js';import{emitUserMessage as _0x162836,emitResponseStart as _0x361bb9,emitResponseDelta as _0x474e50,emitResponseDone as _0x544c3d}from'../services/broadcast.js';import{t}from'../i18n.js';import{isHarmlessTelegramError}from'../util/telegram-error-filter.js';import{handleToolResultChunk}from'./async-agent-chunk-handler.js';import{createStuckTimer}from'./stuck-timer.js';import{shouldBypassQueue,shouldBypassSdkResume,waitUntilProcessingFalse}from'./background-bypass.js';import{SteerChannel}from'../services/steer-channel.js';import{isSteeringEnabled}from'../config.js';const STUCK_TIMEOUT_MINUTES=Number(process['env'][_0x283689(0x19a)+_0x378fe4(0x22f)+'MINUTES'])||-0x2*0x931+0x19e+0x10ce,STUCK_TIMEOUT_MS=STUCK_TIMEOUT_MINUTES*(-0x1ae+-0x255b+0x2745*0x1)*(-0x839+0xbfc+0x25),SYNC_AGENT_IDLE_TIMEOUT_MINUTES=Number(process[_0x283689(0x1e2)]['ALVIN_SYNC'+_0x283689(0x20c)+'E_TIMEOUT_'+'MINUTES'])||-0x2478+0xe4+-0x1*-0x240c,SYNC_AGENT_IDLE_TIMEOUT_MS=SYNC_AGENT_IDLE_TIMEOUT_MINUTES*(0x1844+0x1*-0x55b+-0x2ab*0x7)*(-0x187f+0x7b+-0xdf6*-0x2),CHECKPOINT_TOOL_THRESHOLD=-0xa4*0x1+-0x1d5f+0x1e12,CHECKPOINT_MSG_THRESHOLD=-0x2c6*-0x4+0x1c3a+-0x2748,BRIDGE_MAX_CHARS=-0x17*-0xbf+-0x4f3+-0x272,BRIDGE_MSG_MAX_CHARS=-0x109d+-0x2*0xf52+0x3135;function buildBridgeMessage(_0x1724d3){const _0x74ad7c=_0x378fe4,_0x254168=_0x378fe4;if(_0x1724d3['length']===0x12c6+0x14ad+-0x2773)return'';const _0x42d614=_0x2cffb6=>{const _0x6e5178=_0x3ac9,_0x15d657=_0x3ac9,_0x1c1b32=_0x2cffb6[_0x6e5178(0x19f)]==='user'?_0x15d657(0x1e3):_0x15d657(0x1c3)+'(Fallback)',_0x4b5e04=_0x2cffb6[_0x15d657(0x1ee)]['length']>BRIDGE_MSG_MAX_CHARS?_0x2cffb6[_0x15d657(0x1ee)][_0x15d657(0x1fd)](-0x1c93+0xdae*-0x1+0x2a41,BRIDGE_MSG_MAX_CHARS)+'…':_0x2cffb6[_0x15d657(0x1ee)];return _0x1c1b32+':\x20'+_0x4b5e04;};let _0x579685=_0x1724d3[_0x74ad7c(0x22b)](_0x42d614),_0x238a95=_0x579685[_0x74ad7c(0x1be)]('\x0a\x0a'),_0x2ef8f0=0x1fe3+-0x17d0+-0x813;while(_0x238a95[_0x254168(0x283)]>BRIDGE_MAX_CHARS&&_0x579685[_0x254168(0x283)]>-0x551*-0x3+0xf06+0x1ef7*-0x1){_0x579685['shift'](),_0x2ef8f0++,_0x238a95=_0x579685[_0x74ad7c(0x1be)]('\x0a\x0a');}const _0x339107=_0x2ef8f0>-0x720+0x144+0x5dc?'[…'+_0x2ef8f0+(_0x254168(0x296)+_0x74ad7c(0x27d)+_0x74ad7c(0x1ae)):'',_0x545a19=_0x1724d3[_0x74ad7c(0x283)];return _0x74ad7c(0x280)+_0x74ad7c(0x1c8)+'(Claude)\x20w'+_0x254168(0x244)+_0x254168(0x22c)+_0x254168(0x269)+_0x74ad7c(0x265)+(_0x254168(0x1cd)+_0x74ad7c(0x227)+_0x545a19+(_0x74ad7c(0x263)+_0x74ad7c(0x1f5)+_0x74ad7c(0x252)+_0x74ad7c(0x19d)+_0x254168(0x26d)))+(_0x74ad7c(0x1b0)+_0x74ad7c(0x1c0))+_0x339107+_0x238a95+('\x0a\x0a---\x20New\x20'+_0x74ad7c(0x245)+_0x254168(0x19c)+'--]\x0a\x0a');}const TOOL_ICONS={'Read':'πŸ“–','Write':'πŸ“','Edit':'✏️','Bash':'⚑','Glob':'πŸ”','Grep':'πŸ”Ž','WebSearch':'🌐','WebFetch':'πŸ“‘','Task':'πŸ€–'};export function shouldSuppressFinalSend(_0x4eaa8e){const _0x4c85a8=_0x378fe4,_0x58f40e=_0x378fe4;if(!_0x4eaa8e[_0x4c85a8(0x1e8)+_0x58f40e(0x27e)])return![];if(_0x4eaa8e[_0x58f40e(0x1aa)+_0x58f40e(0x233)+'nt'])return![];return!![];}export function decideMidTaskRouting(_0x95724){const _0x4c7b33=_0x283689,_0x40df63=_0x283689;if(!_0x95724[_0x4c7b33(0x292)+'ng'])return _0x4c7b33(0x290);if(_0x95724['shouldBypa'+'ss'])return _0x40df63(0x240);if(_0x95724[_0x4c7b33(0x1c1)+'ClaudeSdk']&&_0x95724[_0x4c7b33(0x20d)+_0x40df63(0x25a)]&&_0x95724[_0x4c7b33(0x1b3)+_0x4c7b33(0x247)]&&_0x95724[_0x4c7b33(0x213)+'Query'])return _0x40df63(0x284);return _0x40df63(0x290);}export function detectUndetachedBackgroundClaim(_0x1da837){const _0x1a8175=_0x378fe4,_0x36e19e=_0x378fe4;if(!_0x1da837[_0x1a8175(0x1f0)+'eenWithout'+'RunInBackg'+_0x36e19e(0x1f2)])return![];if(_0x1da837['dispatchAg'+_0x1a8175(0x20a)])return![];if(_0x1da837['pendingBac'+_0x1a8175(0x223)+'ta']>0xcf*-0x3+-0x159f+0x39*0x6c)return![];return!![];}async function react(_0x49737c,_0x52319f){const _0x2904d0=_0x283689;try{await _0x49737c[_0x2904d0(0x1ec)](_0x52319f);}catch{}}export async function handleMessage(_0x55a35f){const _0x4254e8=_0x378fe4,_0x49b7b8=_0x283689,_0x5f4e85=_0x55a35f[_0x4254e8(0x1d9)]?.[_0x49b7b8(0x1a5)];if(!_0x5f4e85||_0x5f4e85[_0x4254e8(0x277)]('/'))return;let _0x31bb3a=_0x5f4e85;const _0x426c99=_0x55a35f[_0x49b7b8(0x1d9)];if(_0x426c99?.[_0x4254e8(0x1e9)+_0x4254e8(0x1a3)]||_0x426c99?.[_0x4254e8(0x295)+'te']){if(!isForwardingAllowed()){await _0x55a35f[_0x4254e8(0x251)]('⚠️\x20Weiterge'+_0x49b7b8(0x246)+_0x49b7b8(0x1d0)+_0x49b7b8(0x1fe)+'iviert.\x20Ak'+_0x4254e8(0x201)+_0x49b7b8(0x1c4)+_0x4254e8(0x1eb)+'s\x20on`',{'parse_mode':_0x4254e8(0x1e5)});return;}const _0x5f421e=_0x426c99[_0x4254e8(0x1c9)+'nder_name']||_0x49b7b8(0x1f6);_0x31bb3a=_0x4254e8(0x254)+_0x49b7b8(0x20e)+'hricht\x20von'+'\x20'+_0x5f421e+_0x4254e8(0x19b)+_0x5f4e85;}const _0x3a10fc=_0x55a35f[_0x4254e8(0x1d9)]?.[_0x49b7b8(0x1ad)+_0x4254e8(0x29c)];if(_0x3a10fc?.['text']){const _0x557e0f=_0x3a10fc[_0x49b7b8(0x1a5)][_0x4254e8(0x283)]>0x467+-0x2*-0x65f+-0xf31*0x1?_0x3a10fc['text'][_0x49b7b8(0x1fd)](0x4*0x22+-0x2006+0xfbf*0x2,0x4f*-0x4d+0x2f*-0x4f+-0x58*-0x75)+_0x4254e8(0x25b):_0x3a10fc[_0x4254e8(0x1a5)];_0x31bb3a=_0x4254e8(0x1db)+_0x4254e8(0x1ea)+_0x4254e8(0x20b)+'\x20\x22'+_0x557e0f+_0x49b7b8(0x260)+_0x31bb3a;}const _0x160e88=_0x55a35f['from']['id'],_0x5b823d=buildSessionKey(_0x49b7b8(0x29f),_0x55a35f[_0x49b7b8(0x1c6)]['id'],_0x160e88),_0x5dcc1f=getSession(_0x5b823d);touchProfile(_0x160e88,_0x55a35f['from']?.['first_name'],_0x55a35f[_0x4254e8(0x23a)]?.[_0x49b7b8(0x23d)],_0x49b7b8(0x29f),_0x31bb3a);if(_0x5dcc1f[_0x49b7b8(0x27f)+'nt']===0x13a3*0x1+0x2580+0x3923*-0x1){const {loadProfile:_0xbbc9f9}=await import(_0x4254e8(0x1a2)+_0x4254e8(0x199)),_0x59bef7=_0xbbc9f9(_0x160e88);if(_0x59bef7?.[_0x4254e8(0x26a)])_0x5dcc1f['language']=_0x59bef7[_0x49b7b8(0x26a)];}if(_0x5dcc1f[_0x4254e8(0x292)+'ng']){const _0x1bd0e8=shouldBypassQueue({'isProcessing':_0x5dcc1f[_0x4254e8(0x292)+'ng'],'pendingBackgroundCount':_0x5dcc1f[_0x4254e8(0x1e1)+_0x4254e8(0x1ab)+'nt'],'abortController':_0x5dcc1f[_0x4254e8(0x238)+_0x4254e8(0x1bc)]}),_0x30d137=getRegistry()[_0x49b7b8(0x279)]()[_0x4254e8(0x1dc)]['type']===_0x4254e8(0x236),_0x544845=decideMidTaskRouting({'isProcessing':!![],'providerIsClaudeSdk':_0x30d137,'steeringEnabled':isSteeringEnabled(),'hasSteerChannel':!!_0x5dcc1f[_0x4254e8(0x212)+'nel'],'hasLiveSdkQuery':!!_0x5dcc1f[_0x4254e8(0x1b5)],'shouldBypass':_0x1bd0e8});if(_0x544845==='bypass'){console['log']('[v4.12.3\x20b'+'ypass]\x20abo'+_0x4254e8(0x26f)+_0x49b7b8(0x1c5)+_0x4254e8(0x25d)+_0x5b823d+_0x49b7b8(0x1ba)+(_0x5dcc1f['pendingBac'+_0x4254e8(0x1ab)+'nt']+(_0x4254e8(0x23e)+_0x49b7b8(0x1d8)+_0x4254e8(0x29b)))),_0x5dcc1f[_0x49b7b8(0x28f)+_0x4254e8(0x24b)]=!![];try{_0x5dcc1f[_0x4254e8(0x238)+_0x4254e8(0x1bc)][_0x4254e8(0x1cb)]();}catch{}await waitUntilProcessingFalse(_0x5dcc1f,-0x23f4+0x458+0x3324);}else{if(_0x544845==='steer'){const _0xbee9dc=_0x5dcc1f['_steerChan'+_0x49b7b8(0x202)][_0x4254e8(0x1df)](_0x31bb3a);if(_0xbee9dc){await react(_0x55a35f,'πŸ“¨');if(!_0x5dcc1f['_steerAckS'+_0x49b7b8(0x218)+'n']){try{await _0x55a35f[_0x4254e8(0x251)](t('bot.steer.'+_0x4254e8(0x1da),_0x5dcc1f[_0x49b7b8(0x26a)]));}catch{}_0x5dcc1f[_0x49b7b8(0x26e)+_0x4254e8(0x218)+'n']=!![];}}else try{await _0x55a35f[_0x4254e8(0x251)](t('bot.steer.'+'bufferFull',_0x5dcc1f[_0x49b7b8(0x26a)]));}catch{}return;}else{if(_0x5dcc1f[_0x4254e8(0x273)+'ue'][_0x4254e8(0x283)]<-0x1747+0x1ba0+-0x4a*0xf){_0x5dcc1f[_0x4254e8(0x273)+'ue'][_0x49b7b8(0x1df)](_0x31bb3a),await react(_0x55a35f,'πŸ“');try{await _0x55a35f[_0x49b7b8(0x251)](_0x49b7b8(0x259)+'rage\x20lΓ€uft'+'\x20gerade.\x20D'+'eine\x20Nachr'+_0x49b7b8(0x22d)+_0x49b7b8(0x1b8)+_0x4254e8(0x1f3)+'und\x20wird\x20a'+'ls\x20NΓ€chste'+_0x49b7b8(0x21b)+_0x4254e8(0x288));}catch{}}else await _0x55a35f[_0x49b7b8(0x251)](_0x4254e8(0x21c)+_0x4254e8(0x250)+_0x4254e8(0x26b)+'chten).\x20Bi'+_0x49b7b8(0x215)+_0x49b7b8(0x224)+_0x49b7b8(0x27c));return;}}}if(_0x5dcc1f['messageQue'+'ue']['length']>0x5*0x45+0x4b*-0x57+0x1824){const _0x5670fa=_0x5dcc1f['messageQue'+'ue'][_0x4254e8(0x291)](0x66e*-0x1+-0x1d36+0x23a4);_0x31bb3a=[..._0x5670fa,_0x31bb3a][_0x49b7b8(0x1be)]('\x0a\x0a');}_0x5dcc1f[_0x49b7b8(0x292)+'ng']=!![],_0x5dcc1f[_0x49b7b8(0x238)+_0x4254e8(0x1bc)]=new AbortController();const _0x3e1e01=_0x1d196c['randomUUID']();_0x5dcc1f[_0x49b7b8(0x222)]=_0x3e1e01,delete _0x5dcc1f[_0x49b7b8(0x28f)+_0x49b7b8(0x24b)];let _0x1230c6=null;try{const _0x2b3580=await _0x55a35f[_0x49b7b8(0x251)]('⏳',{'reply_markup':new InlineKeyboard()['text']('β›”\x20Stop',_0x49b7b8(0x24f)+_0x5b823d)});_0x1230c6=_0x2b3580[_0x49b7b8(0x297)];}catch{}const _0x2e7f4f=new TelegramStreamer(_0x55a35f['chat']['id'],_0x55a35f['api'],_0x55a35f[_0x4254e8(0x1d9)]?.[_0x4254e8(0x297)]);let _0x27c529='',_0x150c78=![],_0x1040b8=![];const _0xfc45ff=setInterval(()=>{const _0x2dab32=_0x49b7b8,_0x349f71=_0x4254e8;_0x55a35f[_0x2dab32(0x1b4)]['sendChatAc'+'tion'](_0x55a35f[_0x349f71(0x1c6)]['id'],_0x2dab32(0x261))[_0x349f71(0x1d3)](()=>{});},-0x1a4d+-0x297+0x94*0x4d),_0x48e2d0=createStuckTimer({'normalMs':STUCK_TIMEOUT_MS,'extendedMs':SYNC_AGENT_IDLE_TIMEOUT_MS,'onTimeout':()=>{const _0x586eff=_0x4254e8,_0x2130a8=_0x4254e8;_0x5dcc1f[_0x586eff(0x238)+_0x2130a8(0x1bc)]&&!_0x5dcc1f['abortContr'+_0x586eff(0x1bc)][_0x586eff(0x226)][_0x586eff(0x1a6)]&&(_0x150c78=!![],_0x5dcc1f['abortContr'+'oller'][_0x586eff(0x1cb)]());}});_0x48e2d0[_0x4254e8(0x287)]();try{await react(_0x55a35f,'πŸ€”'),await _0x55a35f['api'][_0x49b7b8(0x1ca)+_0x49b7b8(0x243)](_0x55a35f[_0x49b7b8(0x1c6)]['id'],_0x4254e8(0x261)),_0x5dcc1f[_0x49b7b8(0x27f)+'nt']++,emit(_0x4254e8(0x267)+_0x4254e8(0x28d),{'userId':_0x160e88,'text':_0x31bb3a,'platform':'telegram'}),_0x162836({'platform':_0x4254e8(0x29f),'userId':_0x160e88,'userName':_0x55a35f[_0x49b7b8(0x23a)]?.[_0x4254e8(0x21e)]||_0x55a35f[_0x4254e8(0x23a)]?.['username'],'chatId':_0x55a35f[_0x4254e8(0x1c6)]['id'],'text':_0x31bb3a,'ts':Date[_0x49b7b8(0x272)]()}),_0x361bb9({'platform':_0x4254e8(0x29f),'userId':_0x160e88,'chatId':_0x55a35f[_0x49b7b8(0x1c6)]['id'],'ts':Date[_0x4254e8(0x272)]()});const _0x995418=getRegistry(),_0x19d036=_0x995418[_0x4254e8(0x279)](),_0x3eaf7e=_0x19d036[_0x49b7b8(0x1dc)]['type']===_0x4254e8(0x236);if(!_0x3eaf7e){if(shouldCompact(_0x5dcc1f)){const _0x77e5be=await compactSession(_0x5dcc1f);_0x77e5be[_0x4254e8(0x23f)+_0x4254e8(0x203)]>-0x1*0xda1+-0x1*-0x10dd+-0x33c&&console[_0x49b7b8(0x276)](_0x4254e8(0x239)+'session:\x20r'+_0x4254e8(0x198)+_0x77e5be['removedEnt'+'ries']+(_0x49b7b8(0x1e6)+_0x49b7b8(0x1cc))+_0x77e5be[_0x4254e8(0x25f)+_0x4254e8(0x1a9)]);}}const _0x41345f=trackAndAdapt(_0x160e88,_0x31bb3a,_0x5dcc1f[_0x4254e8(0x26a)]);_0x41345f!==_0x5dcc1f[_0x49b7b8(0x26a)]&&(_0x5dcc1f[_0x49b7b8(0x26a)]=_0x41345f);const _0x54c863=getTelegramWorkspace(_0x160e88),_0x145b78=_0x54c863?getWorkspace(_0x54c863)??resolveWorkspaceOrDefault(_0x49b7b8(0x29f),String(_0x160e88),undefined):resolveWorkspaceOrDefault(_0x4254e8(0x29f),String(_0x160e88),undefined);if(_0x5dcc1f[_0x4254e8(0x262)+'ame']!==_0x145b78['name']){const _0x3991fb=_0x5dcc1f['workingDir']!==_0x145b78[_0x4254e8(0x1b2)];_0x5dcc1f['workspaceN'+_0x49b7b8(0x21f)]=_0x145b78[_0x49b7b8(0x282)],_0x5dcc1f['workingDir']=_0x145b78[_0x4254e8(0x1b2)],_0x3991fb&&(console[_0x4254e8(0x276)](_0x49b7b8(0x1e0)+_0x4254e8(0x19e)+_0x4254e8(0x286)+_0x4254e8(0x1f8)+'β†’\x20'+_0x145b78[_0x49b7b8(0x1b2)]+_0x4254e8(0x255)+(_0x4254e8(0x216)+_0x49b7b8(0x2a0)+_0x4254e8(0x1ff)+_0x49b7b8(0x289)+_0x49b7b8(0x26c))),_0x5dcc1f[_0x4254e8(0x1fb)]=null,_0x5dcc1f[_0x4254e8(0x235)+_0x49b7b8(0x29d)]=_0x5dcc1f['history']['length']-(-0x189e+0x2ee+0x15b1),markSessionDirty(_0x160e88));}const _0x25bb77=String(_0x55a35f['chat']['id']),_0x285a6e=buildSkillContext(_0x31bb3a),_0x146c06=_0x3eaf7e&&_0x5dcc1f[_0x49b7b8(0x1fb)]===null,_0x3f608a=await buildSmartSystemPrompt(_0x3eaf7e,_0x5dcc1f[_0x4254e8(0x26a)],_0x31bb3a,_0x25bb77,_0x146c06,_0x145b78['systemProm'+_0x4254e8(0x285)])+_0x285a6e;addToHistory(_0x160e88,{'role':_0x4254e8(0x1b1),'content':_0x31bb3a});if(_0x3eaf7e){const _0x5cb992=_0x5dcc1f['toolUseCou'+'nt']>=CHECKPOINT_TOOL_THRESHOLD||_0x5dcc1f['messageCou'+'nt']>=CHECKPOINT_MSG_THRESHOLD;_0x5cb992&&_0x5dcc1f[_0x49b7b8(0x270)+_0x49b7b8(0x219)+_0x49b7b8(0x27e)]++;}const _0x53f0cb=_0x3eaf7e&&shouldBypassSdkResume({'pendingBackgroundCount':_0x5dcc1f[_0x4254e8(0x1e1)+_0x4254e8(0x1ab)+'nt']});_0x53f0cb&&console['log'](_0x4254e8(0x217)+_0x49b7b8(0x24e)+_0x4254e8(0x28b)+_0x49b7b8(0x232)+_0x49b7b8(0x27a)+_0x5b823d+_0x4254e8(0x1ba)+(_0x5dcc1f['pendingBac'+_0x49b7b8(0x1ab)+'nt']+('\x20backgroun'+_0x4254e8(0x1d8)+'\x20still\x20pen'+_0x4254e8(0x1a8))));const _0x14c8ba=-0x53*0x23+0x39*-0x1b+-0x11*-0x106;let _0xe941aa=_0x31bb3a;if(_0x3eaf7e){let _0x538c51,_0x347ed3;if(_0x53f0cb)_0x347ed3=_0x5dcc1f['history'][_0x49b7b8(0x283)]-(0x253+-0x323*0x1+0xd1),_0x538c51=Math[_0x49b7b8(0x1d7)](-0x3ff*-0x9+-0x1ba0+-0x857,_0x347ed3-_0x14c8ba);else{const _0x4ffacb=Math[_0x4254e8(0x220)](_0x5dcc1f[_0x4254e8(0x235)+_0x4254e8(0x29d)],_0x5dcc1f[_0x49b7b8(0x281)][_0x49b7b8(0x283)]-(0x2*0xee4+-0x62*0x51+0x2d*0x7));_0x538c51=Math['max'](0x2*-0x4ea+0x1*-0x1c21+0x25f5,_0x4ffacb+(-0x1e6*-0x13+0x897+-0x2ca8)),_0x347ed3=_0x5dcc1f[_0x49b7b8(0x281)]['length']-(-0x7*-0x19e+0x17df+-0x466*0x8);}if(_0x347ed3>_0x538c51){const _0x241ddf=_0x5dcc1f[_0x4254e8(0x281)][_0x49b7b8(0x1fd)](_0x538c51,_0x347ed3),_0x466e53=buildBridgeMessage(_0x241ddf);_0x466e53&&(_0xe941aa=_0x466e53+_0x31bb3a,console['log']('[bridge]\x20'+(_0x53f0cb?_0x4254e8(0x240):_0x49b7b8(0x257)+'ry')+':\x20'+('injecting\x20'+_0x241ddf[_0x4254e8(0x283)]+('\x20turn(s)\x20i'+'nto\x20prompt'))));}}const {toolsetToAllowedTools:_0x492a89}=await import('../service'+'s/workspac'+_0x49b7b8(0x274)),_0x32b353=_0x492a89(_0x145b78[_0x49b7b8(0x1c7)]),_0x3f72c6={'prompt':_0xe941aa,'systemPrompt':_0x3f608a,'workingDir':_0x5dcc1f['workingDir'],'effort':_0x145b78[_0x4254e8(0x24c)]??_0x5dcc1f[_0x49b7b8(0x24c)],..._0x145b78[_0x4254e8(0x24a)]?{'model':_0x145b78[_0x49b7b8(0x24a)]}:{},..._0x145b78['temperatur'+'e']!==undefined?{'temperature':_0x145b78['temperatur'+'e']}:{},..._0x32b353?{'allowedTools':_0x32b353}:{},'abortSignal':_0x5dcc1f[_0x49b7b8(0x238)+_0x4254e8(0x1bc)][_0x4254e8(0x226)],'locale':_0x5dcc1f[_0x4254e8(0x26a)],'sessionId':_0x3eaf7e&&!_0x53f0cb?_0x5dcc1f['sessionId']:null,'history':_0x5dcc1f[_0x4254e8(0x281)],'_sessionState':_0x3eaf7e?{'messageCount':_0x5dcc1f[_0x49b7b8(0x27f)+'nt'],'toolUseCount':_0x5dcc1f[_0x4254e8(0x209)+'nt']}:undefined,'alvinDispatchContext':_0x3eaf7e?{'chatId':_0x55a35f[_0x49b7b8(0x1c6)]['id'],'userId':_0x160e88,'sessionKey':_0x5b823d}:undefined,'onQueryHandle':_0x3922f0=>{const _0x5122d4=_0x4254e8;_0x5dcc1f[_0x5122d4(0x1b5)]=_0x3922f0;}};_0x3eaf7e&&isSteeringEnabled()&&(_0x5dcc1f[_0x49b7b8(0x212)+_0x4254e8(0x202)]=new SteerChannel(),_0x5dcc1f[_0x49b7b8(0x212)+_0x4254e8(0x202)][_0x49b7b8(0x1df)](_0xe941aa),_0x3f72c6[_0x4254e8(0x1cf)+'el']=_0x5dcc1f[_0x49b7b8(0x212)+_0x4254e8(0x202)]);let _0x17b939=0x16be+0xd*0x255+-0x1*0x350f,_0x52fa50,_0x3acec3=![],_0x3a8230=![];const _0x256ab4=_0x5dcc1f[_0x4254e8(0x1e1)+_0x49b7b8(0x1ab)+'nt']??-0x30*-0x43+0x1b85+-0x2815;for await(const _0x216e99 of _0x995418[_0x4254e8(0x234)+_0x4254e8(0x1ef)](_0x3f72c6,_0x145b78['provider'])){if(_0x5dcc1f[_0x49b7b8(0x29e)+_0x4254e8(0x1f1)])break;if(_0x216e99[_0x4254e8(0x1f7)]==='tool_use'&&(_0x216e99[_0x4254e8(0x208)]===_0x4254e8(0x1a0)||_0x216e99['toolName']==='Agent')&&_0x216e99[_0x4254e8(0x211)]&&_0x216e99['runInBackg'+_0x49b7b8(0x1f2)]!==!![])_0x48e2d0['enterSync'](_0x216e99[_0x49b7b8(0x211)]),_0x3a8230=!![];else _0x216e99[_0x49b7b8(0x1f7)]===_0x4254e8(0x1a7)+'t'&&_0x216e99[_0x49b7b8(0x211)]&&_0x48e2d0[_0x4254e8(0x1b9)](_0x216e99[_0x4254e8(0x211)]);_0x48e2d0[_0x4254e8(0x287)]();switch(_0x216e99['type']){case'text':_0x27c529=_0x216e99[_0x4254e8(0x1a5)]||'',_0x2e7f4f[_0x4254e8(0x1bf)](null),await _0x2e7f4f[_0x4254e8(0x200)](_0x27c529);_0x216e99[_0x4254e8(0x1d6)+_0x49b7b8(0x28e)+'d']&&(console[_0x4254e8(0x1bb)](_0x4254e8(0x1e0)+_0x49b7b8(0x1d1)+_0x4254e8(0x1d5)+'eset\x20for\x20'+_0x5b823d+(_0x4254e8(0x1d2)+_0x49b7b8(0x1b6)+_0x49b7b8(0x1e4)+'chor')),_0x5dcc1f[_0x4254e8(0x1fb)]=null,_0x5dcc1f[_0x49b7b8(0x235)+_0x4254e8(0x29d)]=-(0x678+0x2*0xe9+-0x65*0x15),_0x3acec3=!![],markSessionDirty(_0x160e88));if(_0x27c529[_0x4254e8(0x283)]>_0x17b939){const _0x584aff=_0x27c529['slice'](_0x17b939);_0x474e50({'platform':'telegram','userId':_0x160e88,'chatId':_0x55a35f['chat']['id'],'delta':_0x584aff,'ts':Date[_0x49b7b8(0x272)]()}),_0x17b939=_0x27c529['length'];}break;case _0x49b7b8(0x21d):if(_0x216e99[_0x49b7b8(0x208)]){_0x5dcc1f[_0x4254e8(0x209)+'nt']++;const _0x14fc66=TOOL_ICONS[_0x216e99[_0x49b7b8(0x208)]]||'πŸ”§';if(_0x216e99['toolName']===_0x49b7b8(0x1a0)||_0x216e99[_0x49b7b8(0x208)]===_0x4254e8(0x249)){_0x5dcc1f[_0x49b7b8(0x221)+_0x4254e8(0x1af)]++;let _0x1ab402=_0x216e99[_0x49b7b8(0x208)];if(_0x216e99[_0x49b7b8(0x278)])try{const _0xc7794c=JSON['parse'](_0x216e99[_0x4254e8(0x278)]);if(_0xc7794c[_0x4254e8(0x207)+'n']){const _0x2d0672=_0xc7794c[_0x4254e8(0x207)+'n'][_0x49b7b8(0x283)]>-0x2232+0x4c0*0x2+0x856*0x3?_0xc7794c[_0x4254e8(0x207)+'n'][_0x4254e8(0x1fd)](0x46f*-0x1+0x1*-0x364+-0x1*-0x7d3,-0x247*-0xf+0x26c8+-0x48a1)+'…':_0xc7794c['descriptio'+'n'];_0x1ab402=_0x216e99[_0x4254e8(0x208)]+':\x20'+_0x2d0672;}else _0xc7794c[_0x49b7b8(0x268)+'ype']&&(_0x1ab402=_0x216e99[_0x49b7b8(0x208)]+'\x20('+_0xc7794c['subagent_t'+_0x4254e8(0x23c)]+')');_0x52fa50={'description':_0xc7794c['descriptio'+'n'],'prompt':_0xc7794c[_0x4254e8(0x271)]};}catch{}_0x2e7f4f[_0x4254e8(0x1bf)](_0x14fc66+'\x20'+_0x1ab402+'…');}else _0x2e7f4f[_0x4254e8(0x1bf)](_0x14fc66+'\x20'+_0x216e99[_0x4254e8(0x208)]+'…');}break;case _0x4254e8(0x1a7)+'t':handleToolResultChunk(_0x216e99,{'chatId':_0x55a35f[_0x49b7b8(0x1c6)]['id'],'userId':_0x160e88,'sessionKey':_0x5b823d,'lastToolUseInput':_0x52fa50}),_0x52fa50=undefined;break;case'done':if(_0x216e99[_0x49b7b8(0x1fb)]&&!_0x3acec3)_0x5dcc1f[_0x49b7b8(0x1fb)]=_0x216e99[_0x4254e8(0x1fb)];if(_0x216e99[_0x49b7b8(0x1e7)])_0x5dcc1f[_0x49b7b8(0x25e)]+=_0x216e99['costUsd'];typeof _0x216e99[_0x4254e8(0x248)+'s']==='number'&&_0x216e99['inputToken'+'s']>0x1a74+-0xa29+-0x104b&&(_0x5dcc1f[_0x4254e8(0x256)+_0x49b7b8(0x1c2)]=_0x216e99['inputToken'+'s']);trackProviderUsage(_0x160e88,_0x995418['getActiveK'+'ey'](),_0x216e99[_0x49b7b8(0x1e7)]||0xa1a+-0x1465+-0x11*-0x9b,_0x216e99[_0x49b7b8(0x248)+'s'],_0x216e99[_0x4254e8(0x258)+'ns']),trackUsage(_0x995418[_0x49b7b8(0x225)+'ey'](),_0x216e99[_0x4254e8(0x248)+'s']||0x26c9+0x2614*0x1+-0x4cdd,_0x216e99['outputToke'+'ns']||0xf74+0x16d7+0x264b*-0x1,_0x216e99[_0x49b7b8(0x1e7)]||-0x1e*0x6d+-0x1c5d+0x2923),_0x5dcc1f[_0x49b7b8(0x1dd)+'ty']=Date['now']();break;case _0x49b7b8(0x205):await _0x55a35f[_0x49b7b8(0x251)](_0x4254e8(0x275)+_0x216e99['failedProv'+_0x4254e8(0x1a1)]+('\x20unavailab'+'le\x20β€”\x20switc'+_0x49b7b8(0x1ac))+_0x216e99[_0x4254e8(0x204)+'me']+'_',{'parse_mode':_0x4254e8(0x1e5)});break;case'error':if(_0x5dcc1f[_0x4254e8(0x28f)+'rtFired']===!![]&&_0x216e99[_0x49b7b8(0x229)]?.[_0x49b7b8(0x230)+'e']()['includes'](_0x49b7b8(0x1cb))){_0x1040b8=!![];break;}if(_0x150c78)await _0x55a35f['reply'](t(_0x49b7b8(0x1a4)+_0x4254e8(0x1ed)+'ck',_0x5dcc1f['language'],{'min':STUCK_TIMEOUT_MINUTES}));else!isHarmlessTelegramError(_0x216e99[_0x49b7b8(0x229)])&&await _0x55a35f['reply'](t('bot.error.'+_0x4254e8(0x1fa),_0x5dcc1f[_0x4254e8(0x26a)])+'\x20'+_0x216e99[_0x49b7b8(0x229)]);break;}}if(!_0x1040b8&&!_0x150c78&&!_0x5dcc1f['_stopReque'+'sted']&&detectUndetachedBackgroundClaim({'taskChunkSeenWithoutRunInBackground':_0x3a8230,'dispatchAgentFired':![],'pendingBackgroundDelta':(_0x5dcc1f[_0x49b7b8(0x1e1)+_0x49b7b8(0x1ab)+'nt']??-0x1e87+-0x53*-0x4+0x1d3b)-_0x256ab4}))try{await _0x55a35f[_0x49b7b8(0x251)](t(_0x49b7b8(0x22e)+_0x49b7b8(0x25c)+_0x49b7b8(0x206),_0x5dcc1f[_0x49b7b8(0x26a)]));}catch{}if(_0x1040b8)return;if(shouldSuppressFinalSend({'stopRequested':_0x5dcc1f[_0x4254e8(0x29e)+_0x49b7b8(0x1f1)],'visibleTextAlreadySent':_0x2e7f4f[_0x4254e8(0x237)+'t']}))return;if(_0x5dcc1f[_0x49b7b8(0x29e)+'sted']&&_0x2e7f4f[_0x49b7b8(0x237)+'t']){await _0x2e7f4f[_0x4254e8(0x298)](_0x27c529);return;}await _0x2e7f4f[_0x4254e8(0x298)](_0x27c529),emit(_0x4254e8(0x22a)+'nt',{'userId':_0x160e88,'text':_0x27c529,'platform':_0x49b7b8(0x29f)}),_0x544c3d({'platform':_0x4254e8(0x29f),'userId':_0x160e88,'chatId':_0x55a35f[_0x49b7b8(0x1c6)]['id'],'finalText':_0x27c529,'cost':_0x5dcc1f[_0x49b7b8(0x29a)+_0x4254e8(0x1a1)][_0x995418[_0x4254e8(0x225)+'ey']()],'ts':Date['now']()}),await react(_0x55a35f,'πŸ‘');_0x27c529&&(addToHistory(_0x160e88,{'role':_0x49b7b8(0x1b7),'content':_0x27c529}),_0x3eaf7e&&(_0x5dcc1f[_0x49b7b8(0x235)+_0x4254e8(0x29d)]=_0x5dcc1f[_0x4254e8(0x281)]['length']-(-0x13df+-0x265c+0x3a3c)));if(_0x5dcc1f['voiceReply']&&_0x27c529[_0x4254e8(0x21a)]())try{await _0x55a35f[_0x49b7b8(0x1b4)][_0x49b7b8(0x1ca)+'tion'](_0x55a35f['chat']['id'],_0x4254e8(0x253)+'ce');const _0x4495ee=await textToSpeech(_0x27c529,_0x145b78[_0x49b7b8(0x293)]);await _0x55a35f['replyWithV'+_0x49b7b8(0x1ce)](new InputFile(_0x5d90bb['readFileSy'+'nc'](_0x4495ee),'response.m'+'p3')),_0x5d90bb[_0x4254e8(0x241)](_0x4495ee,()=>{});}catch(_0x410442){console['error'](_0x49b7b8(0x264),_0x410442);}}catch(_0x56f953){const _0x28c324=_0x56f953 instanceof Error?_0x56f953['message']:String(_0x56f953),_0x5407ab=_0x5dcc1f[_0x4254e8(0x26a)],_0x27ec8f=_0x28c324[_0x49b7b8(0x231)]('abort')&&_0x5dcc1f[_0x49b7b8(0x28f)+'rtFired']===!![];if(_0x27ec8f){}else{if(_0x150c78)await react(_0x55a35f,'πŸ‘Ž'),await _0x55a35f[_0x49b7b8(0x251)](t(_0x4254e8(0x1a4)+'timeoutStu'+'ck',_0x5407ab,{'min':STUCK_TIMEOUT_MINUTES}));else{if(_0x28c324[_0x49b7b8(0x231)](_0x49b7b8(0x1cb)))await react(_0x55a35f,'πŸ‘Ž'),await _0x55a35f['reply'](t(_0x49b7b8(0x1a4)+_0x4254e8(0x228)+'celled',_0x5407ab));else!isHarmlessTelegramError(_0x56f953)&&(await react(_0x55a35f,'πŸ‘Ž'),await _0x55a35f[_0x4254e8(0x251)](t(_0x4254e8(0x1a4)+_0x4254e8(0x1fa),_0x5407ab)+'\x20'+_0x28c324));}}}finally{_0x48e2d0[_0x49b7b8(0x210)](),clearInterval(_0xfc45ff);if(_0x5dcc1f['_turnId']===_0x3e1e01){if(_0x1230c6!==null)try{await _0x55a35f['api'][_0x4254e8(0x294)+_0x49b7b8(0x23b)](_0x55a35f[_0x4254e8(0x1c6)]['id'],_0x1230c6);}catch{}_0x5dcc1f[_0x49b7b8(0x292)+'ng']=![],_0x5dcc1f[_0x49b7b8(0x238)+_0x49b7b8(0x1bc)]=null;try{_0x5dcc1f['_steerChan'+_0x4254e8(0x202)]?.[_0x4254e8(0x24d)]();}catch{}_0x5dcc1f[_0x4254e8(0x212)+_0x49b7b8(0x202)]=null,_0x5dcc1f['_steerAckS'+_0x49b7b8(0x218)+'n']=![],_0x5dcc1f[_0x49b7b8(0x1b5)]=null,_0x5dcc1f[_0x4254e8(0x29e)+'sted']=null,_0x5dcc1f[_0x49b7b8(0x222)]=null;}}}