alvin-bot 5.7.0 โ†’ 5.8.1

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 (137) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +25 -31
  3. package/dist/claude.js +1 -102
  4. package/dist/config.js +1 -96
  5. package/dist/engine.js +1 -90
  6. package/dist/find-claude-binary.js +1 -98
  7. package/dist/handlers/async-agent-chunk-handler.js +1 -50
  8. package/dist/handlers/background-bypass.js +1 -75
  9. package/dist/handlers/commands.js +1 -2336
  10. package/dist/handlers/cron-progress.js +1 -52
  11. package/dist/handlers/document.js +1 -194
  12. package/dist/handlers/message.js +1 -959
  13. package/dist/handlers/photo.js +1 -154
  14. package/dist/handlers/platform-message.js +1 -360
  15. package/dist/handlers/stuck-timer.js +1 -54
  16. package/dist/handlers/video.js +1 -237
  17. package/dist/handlers/voice.js +1 -148
  18. package/dist/i18n.js +1 -805
  19. package/dist/index.js +1 -697
  20. package/dist/init-data-dir.js +1 -98
  21. package/dist/middleware/auth.js +1 -233
  22. package/dist/migrate.js +1 -162
  23. package/dist/paths.js +1 -146
  24. package/dist/platforms/discord.js +1 -175
  25. package/dist/platforms/index.js +1 -130
  26. package/dist/platforms/signal.js +1 -205
  27. package/dist/platforms/slack-slash-parser.js +1 -32
  28. package/dist/platforms/slack.js +1 -501
  29. package/dist/platforms/telegram.js +1 -111
  30. package/dist/platforms/types.js +1 -8
  31. package/dist/platforms/whatsapp-auth-helpers.js +1 -53
  32. package/dist/platforms/whatsapp.js +1 -707
  33. package/dist/providers/claude-sdk-provider.js +1 -565
  34. package/dist/providers/codex-cli-provider.js +1 -134
  35. package/dist/providers/index.js +1 -7
  36. package/dist/providers/ollama-provider.js +1 -32
  37. package/dist/providers/openai-compatible.js +1 -406
  38. package/dist/providers/registry.js +1 -352
  39. package/dist/providers/runtime-header.js +1 -45
  40. package/dist/providers/tool-executor.js +1 -475
  41. package/dist/providers/types.js +1 -227
  42. package/dist/services/access.js +1 -144
  43. package/dist/services/allowed-users-gate.js +1 -56
  44. package/dist/services/alvin-dispatch.js +1 -174
  45. package/dist/services/alvin-mcp-tools.js +1 -104
  46. package/dist/services/asset-index.js +1 -224
  47. package/dist/services/async-agent-parser.js +1 -418
  48. package/dist/services/async-agent-watcher.js +1 -583
  49. package/dist/services/auto-diagnostic.js +1 -228
  50. package/dist/services/broadcast.js +1 -52
  51. package/dist/services/browser-manager.js +1 -562
  52. package/dist/services/browser-webfetch.js +1 -127
  53. package/dist/services/browser.js +1 -121
  54. package/dist/services/cdp-bootstrap.js +1 -357
  55. package/dist/services/compaction.js +1 -144
  56. package/dist/services/critical-notify.js +1 -203
  57. package/dist/services/cron-resolver.js +1 -58
  58. package/dist/services/cron-scheduling.js +1 -310
  59. package/dist/services/cron.js +1 -861
  60. package/dist/services/custom-tools.js +1 -317
  61. package/dist/services/delivery-queue.js +1 -173
  62. package/dist/services/delivery-registry.js +1 -21
  63. package/dist/services/disk-cleanup.js +1 -203
  64. package/dist/services/elevenlabs.js +1 -58
  65. package/dist/services/embeddings/auto-detect.js +1 -74
  66. package/dist/services/embeddings/fts5.js +1 -108
  67. package/dist/services/embeddings/gemini.js +1 -65
  68. package/dist/services/embeddings/index.js +1 -496
  69. package/dist/services/embeddings/ollama.js +1 -78
  70. package/dist/services/embeddings/openai.js +1 -49
  71. package/dist/services/embeddings/provider.js +1 -22
  72. package/dist/services/embeddings/vector-base.js +1 -113
  73. package/dist/services/embeddings-migration.js +1 -193
  74. package/dist/services/embeddings.js +1 -9
  75. package/dist/services/env-file.js +1 -50
  76. package/dist/services/exec-guard.js +1 -71
  77. package/dist/services/fallback-order.js +1 -154
  78. package/dist/services/file-permissions.js +1 -93
  79. package/dist/services/heartbeat-file.js +1 -65
  80. package/dist/services/heartbeat.js +1 -313
  81. package/dist/services/hooks.js +1 -44
  82. package/dist/services/imagegen.js +1 -72
  83. package/dist/services/language-detect.js +1 -154
  84. package/dist/services/markdown.js +1 -63
  85. package/dist/services/mcp.js +1 -263
  86. package/dist/services/memory-extractor.js +1 -178
  87. package/dist/services/memory-inject-mode.js +1 -43
  88. package/dist/services/memory-layers.js +1 -156
  89. package/dist/services/memory.js +1 -146
  90. package/dist/services/ollama-manager.js +1 -339
  91. package/dist/services/permissions-wizard.js +1 -291
  92. package/dist/services/personality.js +1 -376
  93. package/dist/services/plugins.js +1 -171
  94. package/dist/services/preflight.js +1 -292
  95. package/dist/services/process-manager.js +1 -291
  96. package/dist/services/release-highlights.js +1 -79
  97. package/dist/services/reminders.js +1 -97
  98. package/dist/services/restart.js +1 -48
  99. package/dist/services/security-audit.js +1 -74
  100. package/dist/services/self-diagnosis.js +1 -272
  101. package/dist/services/self-search.js +1 -129
  102. package/dist/services/session-persistence.js +1 -237
  103. package/dist/services/session.js +1 -282
  104. package/dist/services/skills.js +1 -290
  105. package/dist/services/ssrf-guard.js +1 -162
  106. package/dist/services/standing-orders.js +1 -29
  107. package/dist/services/steer-channel.js +1 -46
  108. package/dist/services/stop-controller.js +1 -52
  109. package/dist/services/subagent-dedup.js +1 -86
  110. package/dist/services/subagent-delivery.js +1 -452
  111. package/dist/services/subagent-stats.js +1 -123
  112. package/dist/services/subagents.js +1 -814
  113. package/dist/services/sudo.js +1 -329
  114. package/dist/services/telegram.js +1 -158
  115. package/dist/services/timing-safe-bearer.js +1 -51
  116. package/dist/services/tool-discovery.js +1 -214
  117. package/dist/services/trends.js +1 -580
  118. package/dist/services/updater.js +1 -291
  119. package/dist/services/usage-tracker.js +1 -144
  120. package/dist/services/users.js +1 -271
  121. package/dist/services/voice.js +1 -104
  122. package/dist/services/watchdog-brake.js +1 -154
  123. package/dist/services/watchdog.js +1 -311
  124. package/dist/services/workspaces.js +1 -276
  125. package/dist/tui/index.js +1 -667
  126. package/dist/util/console-formatter.js +1 -109
  127. package/dist/util/debounce.js +1 -24
  128. package/dist/util/telegram-error-filter.js +1 -62
  129. package/dist/version.js +1 -24
  130. package/dist/web/bind-strategy.js +1 -42
  131. package/dist/web/canvas.js +1 -30
  132. package/dist/web/doctor-api.js +1 -604
  133. package/dist/web/openai-compat.js +1 -252
  134. package/dist/web/server.js +1 -1902
  135. package/dist/web/setup-api.js +1 -1101
  136. package/package.json +5 -2
  137. 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 _0x1c8223=_0x39fc,_0x4b2df0=_0x39fc;(function(_0x35ed64,_0x3fd3c5){const _0x37c2e5=_0x39fc,_0x1db83c=_0x39fc,_0x2351eb=_0x35ed64();while(!![]){try{const _0x1c34b1=parseInt(_0x37c2e5(0x1e4))/(-0x3*-0x867+-0x2*0x4f+0x1896*-0x1)*(parseInt(_0x37c2e5(0x18b))/(-0x104b*0x1+0x728+0x925))+parseInt(_0x1db83c(0x1cb))/(0x798+-0xb06+-0x1*-0x371)+parseInt(_0x1db83c(0x206))/(0x66b+0xfb9+0x18*-0xec)+parseInt(_0x37c2e5(0x191))/(0x12f5*-0x1+0x1141+0x1b9)*(parseInt(_0x37c2e5(0x241))/(-0x172b+0xe97+0x89a))+-parseInt(_0x1db83c(0x1de))/(-0x168d+-0xd+0x16a1)*(-parseInt(_0x37c2e5(0x20a))/(-0x13a3+-0x1*0xead+0x2258))+parseInt(_0x1db83c(0x210))/(-0x12b0+-0x623+0x4a*0x56)+-parseInt(_0x1db83c(0x25a))/(0x6e0+0x12*0x227+0x4*-0xb65);if(_0x1c34b1===_0x3fd3c5)break;else _0x2351eb['push'](_0x2351eb['shift']());}catch(_0x2ba0f9){_0x2351eb['push'](_0x2351eb['shift']());}}}(_0x52ee,0x13*-0x220f+-0xc242a+0x180619));const _0x13143b=(function(){let _0x340ed3=!![];return function(_0x118e07,_0x58440c){const _0x489365=_0x340ed3?function(){const _0x45746c=_0x39fc;if(_0x58440c){const _0x255cde=_0x58440c[_0x45746c(0x216)](_0x118e07,arguments);return _0x58440c=null,_0x255cde;}}:function(){};return _0x340ed3=![],_0x489365;};}()),_0x25a963=_0x13143b(this,function(){const _0x1a7cd9=_0x39fc,_0xb4e05c=_0x39fc;return _0x25a963[_0x1a7cd9(0x1ff)]()[_0x1a7cd9(0x26e)](_0xb4e05c(0x23a)+'+$')[_0xb4e05c(0x1ff)]()['constructo'+'r'](_0x25a963)[_0x1a7cd9(0x26e)](_0x1a7cd9(0x23a)+'+$');});_0x25a963();import{InputFile,InlineKeyboard}from'grammy';import _0x599c4c from'fs';import _0x18e9fa 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 _0x2ba758,emitResponseStart as _0x111065,emitResponseDelta as _0x30db27,emitResponseDone as _0x479824}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';function _0x39fc(_0x5a8df4,_0x3fc4b0){_0x5a8df4=_0x5a8df4-(-0x312+0x216e+-0x1ceb);const _0x193c77=_0x52ee();let _0x4a72bf=_0x193c77[_0x5a8df4];if(_0x39fc['kjgpho']===undefined){var _0x2ee599=function(_0x442f03){const _0x502130='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0xfda012='',_0x5c9e0a='',_0x3660d3=_0xfda012+_0x2ee599;for(let _0x423aef=0x1ce0+0x14*0x6c+-0x18e*0x18,_0x3f29bb,_0x4e070c,_0x6a8c5a=-0x2679+-0x1005+0x367e;_0x4e070c=_0x442f03['charAt'](_0x6a8c5a++);~_0x4e070c&&(_0x3f29bb=_0x423aef%(-0x8*-0xaf+0x599*-0x2+-0x5*-0x126)?_0x3f29bb*(0x1447+-0x1*0x16dd+0x2d6)+_0x4e070c:_0x4e070c,_0x423aef++%(0x1751+0x2475+-0x3bc2))?_0xfda012+=_0x3660d3['charCodeAt'](_0x6a8c5a+(-0x3*0xaa1+0x83*-0x1c+0x2e41))-(0xe7d*-0x1+0x1e0e+-0xf87)!==-0xaf7+-0xd*-0x89+0x402?String['fromCharCode'](0x7*-0x495+0x18ba+0x858&_0x3f29bb>>(-(0x69f*0x3+0x1220+-0x25fb)*_0x423aef&-0x1*0x1d54+-0x11*-0xd4+0xf46)):_0x423aef:-0x41b*-0x5+0x184d+-0x166a*0x2){_0x4e070c=_0x502130['indexOf'](_0x4e070c);}for(let _0x2c4733=-0xc33+-0x13d*0x1d+0x301c*0x1,_0x4ca11e=_0xfda012['length'];_0x2c4733<_0x4ca11e;_0x2c4733++){_0x5c9e0a+='%'+('00'+_0xfda012['charCodeAt'](_0x2c4733)['toString'](-0x1*-0x187b+0x442+-0x1cad*0x1))['slice'](-(0x71f*-0x2+-0x9a+0xeda));}return decodeURIComponent(_0x5c9e0a);};_0x39fc['qVFDNb']=_0x2ee599,_0x39fc['GJCKeJ']={},_0x39fc['kjgpho']=!![];}const _0x513ed2=_0x193c77[-0xc*0x2f6+-0x1*0x179c+-0x4*-0xec9],_0x5c759a=_0x5a8df4+_0x513ed2,_0x1f6d8f=_0x39fc['GJCKeJ'][_0x5c759a];if(!_0x1f6d8f){const _0x33575a=function(_0x1591d2){this['lsGdQV']=_0x1591d2,this['lNDhBc']=[0x21*0x59+-0x1*0xed4+0x35c,-0x1bcf+-0x2f*-0x8b+0x24a,-0x1ada+-0xf2d+0x2a07],this['LeSKfL']=function(){return'newState';},this['fMLKva']='\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*',this['eAzjcT']='[\x27|\x22].+[\x27|\x22];?\x20*}';};_0x33575a['prototype']['HLnGuj']=function(){const _0x1f6616=new RegExp(this['fMLKva']+this['eAzjcT']),_0x514e5a=_0x1f6616['test'](this['LeSKfL']['toString']())?--this['lNDhBc'][0x15e5+-0x16e+-0x1476]:--this['lNDhBc'][0xb04+-0x1887+0x3*0x481];return this['IzCtMJ'](_0x514e5a);},_0x33575a['prototype']['IzCtMJ']=function(_0x5afc4f){if(!Boolean(~_0x5afc4f))return _0x5afc4f;return this['CIiBxt'](this['lsGdQV']);},_0x33575a['prototype']['CIiBxt']=function(_0x2a6440){for(let _0x59d9e8=0xa14+-0x7*0x505+-0x5*-0x503,_0x479a24=this['lNDhBc']['length'];_0x59d9e8<_0x479a24;_0x59d9e8++){this['lNDhBc']['push'](Math['round'](Math['random']())),_0x479a24=this['lNDhBc']['length'];}return _0x2a6440(this['lNDhBc'][0x5*-0x297+0x15*0x1d5+-0x1986]);},new _0x33575a(_0x39fc)['HLnGuj'](),_0x4a72bf=_0x39fc['qVFDNb'](_0x4a72bf),_0x39fc['GJCKeJ'][_0x5c759a]=_0x4a72bf;}else _0x4a72bf=_0x1f6d8f;return _0x4a72bf;}function _0x52ee(){const _0x581cc3=['BMrLCL9Uyw1L','iL0kcG','CYbVBMa','mZK0mJm1B1DVAMvl','C2XPy2u','iokaLca','y29ZDfvZza','C2LUzcbKzwfRDa','C3rVCfjLCxvLCW','DxnLCG','w2jYAwrNzv0G','B20GDxnLCJOGlq','y29ZDej5uhjVDG','DcbGl3nLy3vYAq','Bw9KzwW','CMvWBhK','CMvXDwvZDenHBG','4O+ZifDHCNrLC2nO','Dgv4Da','ig9SzgvYihr1CG','x0fhru5ux0Leta','Dw1LigfUy2HVCG','z2v0qwn0AxzL','y2vSBgvK','ywDL','qxnZAxn0yw50ia','zwzMB3j0','DxbSB2fKx3zVAq','zw50rMLYzwq','Aw5Nia','C3rLzxi','yw1L','sgLUDhnjBMPLyW','BwvZC2fNzq','Aw5NigjYAwrNzq','CMvHy3q','BgfZDefJDgL2Aq','ChjVDMLKzxiGCG','BgvUz3rO','CMvTB3zLzevUDa','AxzPzxj0lIbbAW','igvUDhjPzxmSia','igjHy2TNCM91BG','zgvSzxrLtwvZCW','C3rHCNrZv2L0Aa','C3rLza','zxrszxf1zxn0zq','CY91C2vYCY5QCW','Dg9VBe5HBwu','CMvWBhLFDg9FBq','ChvZAa','Dg9VBf9Yzxn1Ba','u0rlihjLy292zq','BIbKzxiGv2fYDa','zxnJAgXHBMDLia','zwLUzsboywnOCG','w1DLAxrLCMDLBa','y2SGBw9KzwWUia','q291BNq','C3vIywDLBNrFDa','DgL2AwvYzsbTAq','mZe0mJiYn2TyuxvRsq','B2XSzxi','DM9Py2vszxbSEq','w3nLC3nPB25Dia','s19usu1ft1vuxW','zw1VDMvKia','BwvZC2fNzsbMCG','ywjVCNq','B3v0Chv0vg9Rzq','rv9usu1ft1vuxW','Aw5JBhvKzxm','C2vZC2LVBJOGCG','ywn0AxzLihbYBW','q29TCgfJDgvKia','y2XHDwrLlxnKAW','zgLUzW','Aw5Qzwn0Aw5Nia','AcbHigzHBgXIyq','Dg9VBeLUChv0','ndeZENvJCfP0','y2HHDa','AcbtreSGC2vZCW','xqOk','B3rPy2u','x3n0zwvYqwnRuW','ntqWndLetMzeExu','zgvZy3jPChrPBW','C2vZC2LVBKLK','CM9Szq','BgfUz2uGDM9SBa','A2DYB3vUzenVDq','ihbLBMrPBMC','BgfZDfr1CM5jBG','uxvLCNK','ksdIGjqG','C3LZDgvTuhjVBq','A2vKihf1zxj5ia','yM90lNn0zwvYlG','vgfZAW','quXwsu5Fu1LoqW','BgfUz3vHz2u','DMLZAwjSzvrLEa','ExbL','DgHLigzVBgXVDW','CMLLCW','zMfSBgjHy2S','y2fUy2vS','ywXSyMfJAW','A2DYB3vUzerLBa','zMX1C2HLzd0','Aw5WDxruB2TLBG','Dg9VBfvZzunVDq','Dg9tDhjPBMC','zNjVBq','ywjSzwq','ChjVDMLKzxjoyq','yM90lMvYCM9YlG','x3n0zwvYq2HHBG','zwtIGkzDcGO','nJKWmdy4tvDMq09X','ChjLzML4','AgfZtgL2zvnKAW','zxmUANm','nZyWmZjpC2vIs0y','4PQG77IpifDLAxrLCMDL','CMvHzezPBgvtEq','BwvZC2fNzunVDq','yw5UzwW','CM91BMq','ode1otG5nw9QqKjQEa','yxnZAxn0yw50','Dg9YEuLUzgv4','C2vUzenOyxrbyW','BwLU','igDLCMfKzs4Gra','yxbWBhK','y29UDgvUDa','Dw5KihDPCMqGyq','BM93','CgvUzgLUz0jHyW','AhjPy2H0ihzVBG','BwvZC2fNzvf1zq','z2v0qwn0AxzLsW','CMvZzxq','yM90lMjHy2TNCG','CMvZCg9UC2uUBq','x2j5CgfZC0fIBW','DgvSzwDYyw0','y2HVCG','zw50vgHPC1r1CG','Bwf4','CYbTzxnZywDLoG','ywjVCNrdB250CG','q2XHDwrLu2rR','DgfZA0nODw5RuW','AgfZu2vUDfrLEa','cGOTls0GtMv3ia','CxvLCNLxAxrOrG','kezHBgXIywnRkq','zcbHz2vUDcHZkq','zxnZywDL','DgLTzw91Dfn0Dq','BNvTyMvY','BMDLzcbJD2qGka','DxnLCM5HBwu','BgfZDfnKA0HPCW','yNLWyxnZ','B3vUzc5ZEw5JtG','DhjPBq','y2vPDMvK','zw50zxjtEw5J','kcGOlISPkYKRkq','y2H0zw4PlIbcAq','zxnLDcbMB3iG','Dg9VBfvZzuLK','CNrgAxjLza','vxnLCG','zM9YD2fYzf9VCG','otbxD1PMD1u','zMLYC3rFBMfTzq','y2vSlG','AwnODcbPC3qGAq','zxjYB3i','zcaRifnesYbHBG','qwDLBNq','BIHZksbVBwL0Da','y2HYAwnODgvUia','BwvZC2fNzv9Pza','twfYA2rVD24','zxjLigjYAwvMBa','zw1VCNK','DgLVBG','ChjVDMLKzxi','zg9Uzq','ChrpDMvYCMLKzq','ig1LC3nHz2uOCW','ChjVDMLKzxjjCW','4PQHif8','CNrPBMCGzNjLCW','Dg9VBf91C2u','icGZie5Hy2HYAq','DefSCMvHzhLtzq','lI4VC2vYDMLJzq','mZu4mti0mJbsuxf0vum','Chv0vg9Rzw5Z','zYbZzxnZAw9Usq','AgLZDg9YEq','BMvS','x3fiyw5KBgu','C2LNBMfS','w0nVBNrLEhq6ia','y2XVC2u','zM9YD2fYzf9Zzq','zMX1C2HLzfrVtq','BhmGtSoKy2HZDgu','Bg9N','y3DK','BgvPDgv0zsboyq','DM9Py2u','AwrLCG','D29YA2LUz0rPCG','zwL0zxrLie5HyW','4PUuifn0B3a','C2vHCMnO','C2rRu3vIvgfZAW','ls1DcGO','Dg9mB3DLCKnHCW','D29YA3nWywnLtG','DxbKyxrL','CxvLDwu','x3r1CM5jza','zM9Yia','zw52','DhrLihDHCNrLBG','ywnR','x3n0B3bszxf1zq','zMLUywXPEMu','AxnqCM9JzxnZAq','y2HLy2TWB2LUDa','DgvK','vfrtigvYCM9YoG','BwfW','AgfUz2vKihDPDa','C2HVDwXKqNLWyq','zxqU','AM9PBG','CNvUsw5cywnRzW','BMfTzq','lI4U','yxbP','y2f0y2G','w3y0lJeYlJmGyG','C2v0u3rHDhvZ','C3bSAwnL','DgvTCgvYyxr1CG','iokaLcbJBgvHCMLU','4O+ZievPBMuGqw5M','q2f0y2HPBMCGEq','DhLWAw5N','y29UzMLN','mtjktMnxuuu','ihvUyxzHAwXHyG','DhLWzq'];_0x52ee=function(){return _0x581cc3;};return _0x52ee();}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']['ALVIN_STUC'+_0x1c8223(0x1cf)+'MINUTES'])||-0x1005+-0x10d9+-0x1a*-0x144,STUCK_TIMEOUT_MS=STUCK_TIMEOUT_MINUTES*(0x6b9*0x3+-0x1*0x9a7+-0x5e*0x1c)*(-0x2247+0x2634+0x5*-0x1),SYNC_AGENT_IDLE_TIMEOUT_MINUTES=Number(process[_0x4b2df0(0x277)][_0x4b2df0(0x1f2)+_0x4b2df0(0x1a2)+_0x4b2df0(0x1d4)+'MINUTES'])||-0x1c54+-0x26e3+0x43af,SYNC_AGENT_IDLE_TIMEOUT_MS=SYNC_AGENT_IDLE_TIMEOUT_MINUTES*(0x51*0x34+-0x8dc+-0x75c)*(-0xf07*-0x2+0x340+-0x1d66),CHECKPOINT_TOOL_THRESHOLD=-0xd*-0x89+-0x14df+0xdf9,CHECKPOINT_MSG_THRESHOLD=-0x3*-0x83e+0x1c3d+0x11*-0x31d,BRIDGE_MAX_CHARS=0x65e+0x214c+-0x1de6,BRIDGE_MSG_MAX_CHARS=0xe14+-0xd*-0x1dd+-0x2459;function buildBridgeMessage(_0x3c295d){const _0x58f561=_0x1c8223,_0x124245=_0x1c8223;if(_0x3c295d[_0x58f561(0x1b4)]===-0x41b*-0x5+0x184d+-0x166a*0x2)return'';const _0x4b7fa5=_0x3288f9=>{const _0x2f51b4=_0x58f561,_0x5fcfea=_0x58f561,_0x2ec643=_0x3288f9[_0x2f51b4(0x1e7)]===_0x5fcfea(0x197)?_0x5fcfea(0x23f):_0x5fcfea(0x1a7)+_0x5fcfea(0x22d),_0x1208ef=_0x3288f9[_0x2f51b4(0x217)][_0x2f51b4(0x1b4)]>BRIDGE_MSG_MAX_CHARS?_0x3288f9[_0x5fcfea(0x217)][_0x5fcfea(0x192)](-0xc33+-0x13d*0x1d+0x301c*0x1,BRIDGE_MSG_MAX_CHARS)+'โ€ฆ':_0x3288f9['content'];return _0x2ec643+':\x20'+_0x1208ef;};let _0x36549b=_0x3c295d[_0x58f561(0x178)](_0x4b7fa5),_0x7c155=_0x36549b[_0x124245(0x17c)]('\x0a\x0a'),_0x19ef62=-0x1*-0x187b+0x442+-0x1cbd*0x1;while(_0x7c155[_0x58f561(0x1b4)]>BRIDGE_MAX_CHARS&&_0x36549b[_0x124245(0x1b4)]>0x71f*-0x2+-0x9a+0xeda){_0x36549b['shift'](),_0x19ef62++,_0x7c155=_0x36549b[_0x58f561(0x17c)]('\x0a\x0a');}const _0x514b7d=_0x19ef62>-0xc*0x2f6+-0x1*0x179c+-0x4*-0xec9?'[โ€ฆ'+_0x19ef62+(_0x58f561(0x1a1)+_0x58f561(0x248)+_0x124245(0x205)):'',_0x1def8d=_0x3c295d[_0x58f561(0x1b4)];return _0x58f561(0x261)+'While\x20you\x20'+'(Claude)\x20w'+_0x124245(0x24c)+'y\x20not\x20the\x20'+_0x58f561(0x1d7)+'vider,\x20'+(_0x124245(0x1f6)+_0x58f561(0x1ab)+_0x1def8d+(_0x58f561(0x252)+')\x20were\x20exc'+_0x58f561(0x179)+_0x58f561(0x1dc)+_0x58f561(0x1c7)))+(_0x124245(0x188)+'ou\x20up:\x0a\x0a')+_0x514b7d+_0x7c155+(_0x124245(0x22b)+_0x124245(0x1d1)+_0x124245(0x199)+_0x58f561(0x270));}const TOOL_ICONS={'Read':'๐Ÿ“–','Write':'๐Ÿ“','Edit':'โœ๏ธ','Bash':'โšก','Glob':'๐Ÿ”','Grep':'๐Ÿ”Ž','WebSearch':'๐ŸŒ','WebFetch':'๐Ÿ“ก','Task':'๐Ÿค–'};export function shouldSuppressFinalSend(_0x182ab9){const _0x136be0=_0x4b2df0,_0x504298=_0x1c8223;if(!_0x182ab9[_0x136be0(0x196)+_0x136be0(0x176)])return![];if(_0x182ab9[_0x136be0(0x1f4)+_0x136be0(0x258)+'nt'])return![];return!![];}export function decideMidTaskRouting(_0x2066d4){const _0x46ed31=_0x4b2df0,_0x2af0fe=_0x4b2df0;if(!_0x2066d4['isProcessi'+'ng'])return _0x46ed31(0x274);if(_0x2066d4[_0x2af0fe(0x17a)+'ss'])return _0x46ed31(0x235);if(_0x2066d4[_0x2af0fe(0x253)+_0x46ed31(0x228)]&&_0x2066d4['steeringEn'+_0x2af0fe(0x201)]&&_0x2066d4['hasSteerCh'+_0x46ed31(0x20e)]&&_0x2066d4[_0x46ed31(0x208)+_0x46ed31(0x1ec)])return _0x2af0fe(0x1ac);return'queue';}export function detectUndetachedBackgroundClaim(_0x4d7fa2){const _0x3a92cb=_0x4b2df0,_0x8aab62=_0x4b2df0;if(!_0x4d7fa2[_0x3a92cb(0x229)+'eenWithout'+'RunInBackg'+'round'])return![];if(_0x4d7fa2['dispatchAg'+_0x3a92cb(0x1aa)])return![];if(_0x4d7fa2[_0x3a92cb(0x21a)+_0x3a92cb(0x1fb)+'ta']>0x21*0x59+-0x1*0xed4+0x35b)return![];return!![];}async function react(_0x155f4f,_0xd5059a){const _0x2139a2=_0x4b2df0;try{await _0x155f4f[_0x2139a2(0x1b1)](_0xd5059a);}catch{}}export async function handleMessage(_0x10e815){const _0x48e040=_0x1c8223,_0xbfd3aa=_0x1c8223,_0xa0393e=_0x10e815[_0x48e040(0x1af)]?.[_0xbfd3aa(0x1a0)];if(!_0xa0393e||_0xa0393e[_0xbfd3aa(0x1ba)]('/'))return;let _0x23076f=_0xa0393e;const _0x38b88d=_0x10e815[_0x48e040(0x1af)];if(_0x38b88d?.[_0xbfd3aa(0x240)+'igin']||_0x38b88d?.['forward_da'+'te']){if(!isForwardingAllowed()){await _0x10e815[_0x48e040(0x19d)](_0xbfd3aa(0x20b)+_0xbfd3aa(0x268)+_0xbfd3aa(0x249)+_0x48e040(0x195)+_0xbfd3aa(0x1b6)+_0x48e040(0x1ca)+_0x48e040(0x19b)+'ty\x20forward'+_0x48e040(0x190),{'parse_mode':_0x48e040(0x24b)});return;}const _0x26686a=_0x38b88d[_0x48e040(0x263)+_0x48e040(0x18e)]||'unbekannt';_0x23076f=_0xbfd3aa(0x1c6)+_0xbfd3aa(0x26c)+_0x48e040(0x21b)+'\x20'+_0x26686a+_0xbfd3aa(0x1e1)+_0xa0393e;}const _0xec11d2=_0x10e815[_0x48e040(0x1af)]?.[_0x48e040(0x1bf)+_0xbfd3aa(0x22f)];if(_0xec11d2?.[_0xbfd3aa(0x1a0)]){const _0x1808d4=_0xec11d2[_0xbfd3aa(0x1a0)][_0x48e040(0x1b4)]>-0x1bcf+-0x2f*-0x8b+0x43e?_0xec11d2[_0xbfd3aa(0x1a0)][_0x48e040(0x192)](-0x1ada+-0xf2d+0x2a07,0x15e5+-0x16e+-0x1283)+_0xbfd3aa(0x17f):_0xec11d2[_0x48e040(0x1a0)];_0x23076f='[Replying\x20'+'to\x20previou'+_0xbfd3aa(0x226)+'\x20\x22'+_0x1808d4+_0x48e040(0x18f)+_0x23076f;}const _0x410cf9=_0x10e815[_0x48e040(0x200)]['id'],_0x38f32d=buildSessionKey(_0x48e040(0x222),_0x10e815[_0xbfd3aa(0x1df)]['id'],_0x410cf9),_0x15e051=getSession(_0x38f32d);touchProfile(_0x410cf9,_0x10e815[_0x48e040(0x200)]?.['first_name'],_0x10e815[_0xbfd3aa(0x200)]?.[_0x48e040(0x233)],'telegram',_0x23076f);if(_0x15e051[_0xbfd3aa(0x20d)+'nt']===0xb04+-0x1887+0x3*0x481){const {loadProfile:_0x7a1d65}=await import(_0xbfd3aa(0x259)+_0x48e040(0x1bd)),_0x433a30=_0x7a1d65(_0x410cf9);if(_0x433a30?.[_0x48e040(0x1f3)])_0x15e051[_0x48e040(0x1f3)]=_0x433a30[_0x48e040(0x1f3)];}if(_0x15e051[_0xbfd3aa(0x174)+'ng']){const _0x4620be=shouldBypassQueue({'isProcessing':_0x15e051[_0x48e040(0x174)+'ng'],'pendingBackgroundCount':_0x15e051['pendingBac'+_0x48e040(0x1e9)+'nt'],'abortController':_0x15e051[_0xbfd3aa(0x227)+'oller']}),_0x129f99=getRegistry()[_0x48e040(0x1a4)]()[_0x48e040(0x18a)][_0x48e040(0x18d)]==='claude-sdk',_0x587c4f=decideMidTaskRouting({'isProcessing':!![],'providerIsClaudeSdk':_0x129f99,'steeringEnabled':isSteeringEnabled(),'hasSteerChannel':!!_0x15e051['_steerChan'+'nel'],'hasLiveSdkQuery':!!_0x15e051['_qHandle'],'shouldBypass':_0x4620be});if(_0x587c4f===_0xbfd3aa(0x235)){console['log'](_0xbfd3aa(0x182)+'ypass]\x20abo'+'rting\x20bloc'+_0x48e040(0x1ef)+_0xbfd3aa(0x276)+_0x38f32d+_0xbfd3aa(0x193)+(_0x15e051[_0xbfd3aa(0x21a)+'kgroundCou'+'nt']+('\x20backgroun'+_0x48e040(0x22e)+_0x48e040(0x1ea)))),_0x15e051[_0x48e040(0x221)+_0xbfd3aa(0x23e)]=!![];try{_0x15e051[_0xbfd3aa(0x227)+_0xbfd3aa(0x1cc)][_0xbfd3aa(0x1d2)]();}catch{}await waitUntilProcessingFalse(_0x15e051,0xa14+-0x7*0x505+-0xf*-0x2f9);}else{if(_0x587c4f===_0xbfd3aa(0x1ac)){const _0x4d78d5=_0x15e051[_0xbfd3aa(0x204)+'nel']['push'](_0x23076f);if(_0x4d78d5){await react(_0x10e815,'๐Ÿ“จ');if(!_0x15e051[_0x48e040(0x1e3)+'entThisTur'+'n']){try{await _0x10e815[_0xbfd3aa(0x19d)](t(_0x48e040(0x1f0)+_0x48e040(0x171),_0x15e051[_0xbfd3aa(0x1f3)]));}catch{}_0x15e051['_steerAckS'+'entThisTur'+'n']=!![];}}else try{await _0x10e815[_0xbfd3aa(0x19d)](t(_0xbfd3aa(0x1f0)+'bufferFull',_0x15e051[_0x48e040(0x1f3)]));}catch{}return;}else{if(_0x15e051[_0xbfd3aa(0x21c)+'ue']['length']<0x5*-0x297+0x15*0x1d5+-0x1983){_0x15e051[_0x48e040(0x21c)+'ue'][_0xbfd3aa(0x1c0)](_0x23076f),await react(_0x10e815,'๐Ÿ“');try{await _0x10e815[_0x48e040(0x19d)](_0xbfd3aa(0x187)+'rage\x20lรคuft'+_0x48e040(0x215)+_0xbfd3aa(0x1c5)+_0x48e040(0x244)+_0x48e040(0x1c3)+_0xbfd3aa(0x1c4)+_0xbfd3aa(0x218)+_0x48e040(0x265)+'s\x20bearbeit'+_0x48e040(0x17b));}catch{}}else await _0x10e815[_0xbfd3aa(0x19d)](_0x48e040(0x19f)+_0xbfd3aa(0x1e8)+_0x48e040(0x257)+_0xbfd3aa(0x23b)+_0x48e040(0x278)+'\x20oder\x20/can'+_0xbfd3aa(0x243));return;}}}if(_0x15e051[_0x48e040(0x21c)+'ue'][_0x48e040(0x1b4)]>0x231f+-0x1*-0xd2d+0x304c*-0x1){const _0x58be49=_0x15e051[_0xbfd3aa(0x21c)+'ue'][_0x48e040(0x184)](-0x471*0x3+0x1*0x10cb+0x8*-0x6f);_0x23076f=[..._0x58be49,_0x23076f][_0xbfd3aa(0x17c)]('\x0a\x0a');}_0x15e051['isProcessi'+'ng']=!![],_0x15e051['abortContr'+'oller']=new AbortController();const _0x233efd=_0x18e9fa['randomUUID']();_0x15e051[_0xbfd3aa(0x275)]=_0x233efd,delete _0x15e051[_0xbfd3aa(0x221)+_0x48e040(0x23e)];let _0x592f07=null;try{const _0x221fe3=await _0x10e815[_0x48e040(0x19d)]('โณ',{'reply_markup':new InlineKeyboard()[_0x48e040(0x1a0)](_0xbfd3aa(0x26d),'stop:'+_0x38f32d)});_0x592f07=_0x221fe3[_0x48e040(0x24a)];}catch{}const _0x3d3924=new TelegramStreamer(_0x10e815[_0x48e040(0x1df)]['id'],_0x10e815[_0xbfd3aa(0x180)],_0x10e815[_0xbfd3aa(0x1af)]?.[_0xbfd3aa(0x24a)]);let _0x2cff89='',_0x88919a=![],_0x38a6ac=![];const _0xb113e9=setInterval(()=>{const _0x286e8f=_0x48e040,_0x341b4c=_0x48e040;_0x10e815['api'][_0x286e8f(0x213)+_0x341b4c(0x24e)](_0x10e815['chat']['id'],_0x286e8f(0x189))[_0x341b4c(0x181)](()=>{});},0x18dc+0x1*-0x219e+0x1862),_0xe3b5fd=createStuckTimer({'normalMs':STUCK_TIMEOUT_MS,'extendedMs':SYNC_AGENT_IDLE_TIMEOUT_MS,'onTimeout':()=>{const _0xbd0f6a=_0xbfd3aa,_0x734642=_0xbfd3aa;_0x15e051[_0xbd0f6a(0x227)+_0xbd0f6a(0x1cc)]&&!_0x15e051[_0x734642(0x227)+_0x734642(0x1cc)][_0x734642(0x260)]['aborted']&&(_0x88919a=!![],_0x15e051[_0xbd0f6a(0x227)+'oller']['abort']());}});_0xe3b5fd[_0x48e040(0x21e)]();try{await react(_0x10e815,'๐Ÿค”'),await _0x10e815[_0x48e040(0x180)]['sendChatAc'+_0x48e040(0x24e)](_0x10e815[_0x48e040(0x1df)]['id'],_0xbfd3aa(0x189)),_0x15e051['messageCou'+'nt']++,emit('message:re'+_0x48e040(0x238),{'userId':_0x410cf9,'text':_0x23076f,'platform':_0xbfd3aa(0x222)}),_0x2ba758({'platform':'telegram','userId':_0x410cf9,'userName':_0x10e815[_0x48e040(0x200)]?.[_0x48e040(0x242)]||_0x10e815[_0xbfd3aa(0x200)]?.[_0x48e040(0x233)],'chatId':_0x10e815[_0x48e040(0x1df)]['id'],'text':_0x23076f,'ts':Date[_0xbfd3aa(0x219)]()}),_0x111065({'platform':_0x48e040(0x222),'userId':_0x410cf9,'chatId':_0x10e815[_0xbfd3aa(0x1df)]['id'],'ts':Date['now']()});const _0x4de674=getRegistry(),_0x59f602=_0x4de674[_0xbfd3aa(0x1a4)](),_0x290696=_0x59f602[_0xbfd3aa(0x18a)][_0x48e040(0x18d)]===_0xbfd3aa(0x1d9);if(!_0x290696){if(shouldCompact(_0x15e051)){const _0x9092bf=await compactSession(_0x15e051);_0x9092bf[_0x48e040(0x1b5)+_0xbfd3aa(0x1f7)]>-0x1*0xd59+0x14b*-0x1b+-0x3042*-0x1&&console[_0xbfd3aa(0x266)](_0xbfd3aa(0x1d8)+_0xbfd3aa(0x1d6)+_0xbfd3aa(0x1d0)+_0x9092bf['removedEnt'+'ries']+(_0x48e040(0x1b7)+_0xbfd3aa(0x1fc))+_0x9092bf[_0x48e040(0x264)+_0xbfd3aa(0x24d)]);}}const _0x454f9a=trackAndAdapt(_0x410cf9,_0x23076f,_0x15e051[_0xbfd3aa(0x1f3)]);_0x454f9a!==_0x15e051[_0xbfd3aa(0x1f3)]&&(_0x15e051['language']=_0x454f9a);const _0x1f6bae=getTelegramWorkspace(_0x410cf9),_0x53fbb0=_0x1f6bae?getWorkspace(_0x1f6bae)??resolveWorkspaceOrDefault(_0x48e040(0x222),String(_0x410cf9),undefined):resolveWorkspaceOrDefault('telegram',String(_0x410cf9),undefined);if(_0x15e051[_0xbfd3aa(0x272)+_0x48e040(0x1ad)]!==_0x53fbb0[_0xbfd3aa(0x17e)]){const _0x585ceb=_0x15e051[_0xbfd3aa(0x26b)]!==_0x53fbb0['cwd'];_0x15e051[_0x48e040(0x272)+_0x48e040(0x1ad)]=_0x53fbb0['name'],_0x15e051['workingDir']=_0x53fbb0[_0xbfd3aa(0x267)],_0x585ceb&&(console[_0x48e040(0x266)](_0xbfd3aa(0x1ce)+'workspace\x20'+'switch\x20cha'+_0xbfd3aa(0x232)+'โ†’\x20'+_0x53fbb0[_0x48e040(0x267)]+_0xbfd3aa(0x1ed)+('invalidati'+'ng\x20SDK\x20res'+_0xbfd3aa(0x1a3)+'\x20and\x20skipp'+_0xbfd3aa(0x1b0))),_0x15e051['sessionId']=null,_0x15e051[_0x48e040(0x234)+'toryIndex']=_0x15e051[_0xbfd3aa(0x25d)][_0x48e040(0x1b4)]-(-0x8f*-0xd+0x144e+-0x1b90),markSessionDirty(_0x410cf9));}const _0x56128c=String(_0x10e815['chat']['id']),_0x1a56f5=buildSkillContext(_0x23076f),_0x461fc7=_0x290696&&_0x15e051[_0xbfd3aa(0x1e6)]===null,_0x3df1cd=await buildSmartSystemPrompt(_0x290696,_0x15e051[_0x48e040(0x1f3)],_0x23076f,_0x56128c,_0x461fc7,_0x53fbb0[_0xbfd3aa(0x1ee)+_0xbfd3aa(0x251)])+_0x1a56f5;addToHistory(_0x410cf9,{'role':_0xbfd3aa(0x197),'content':_0x23076f});if(_0x290696){const _0x397dc8=_0x15e051['toolUseCou'+'nt']>=CHECKPOINT_TOOL_THRESHOLD||_0x15e051[_0x48e040(0x20d)+'nt']>=CHECKPOINT_MSG_THRESHOLD;_0x397dc8&&_0x15e051[_0x48e040(0x175)+_0x48e040(0x1ae)+'ted']++;}const _0x2648b1=_0x290696&&shouldBypassSdkResume({'pendingBackgroundCount':_0x15e051['pendingBac'+_0xbfd3aa(0x1e9)+'nt']});_0x2648b1&&console[_0x48e040(0x266)]('[v4.12.3\x20b'+'ypass]\x20sta'+_0x48e040(0x255)+_0x48e040(0x1e0)+'ion\x20for\x20'+_0x38f32d+_0xbfd3aa(0x193)+(_0x15e051[_0xbfd3aa(0x21a)+'kgroundCou'+'nt']+(_0xbfd3aa(0x1b8)+'d\x20agent(s)'+'\x20still\x20pen'+_0x48e040(0x1da))));const _0x583691=-0x371*0x6+0x3*0x30d+0xb89;let _0x202f14=_0x23076f;if(_0x290696){let _0x50acc4,_0xac4049;if(_0x2648b1)_0xac4049=_0x15e051[_0xbfd3aa(0x25d)][_0x48e040(0x1b4)]-(0x5*-0xd2+-0x1148+0x1563),_0x50acc4=Math[_0xbfd3aa(0x225)](0x956*0x1+-0x1*-0x87a+-0x11d0,_0xac4049-_0x583691);else{const _0x3c2a51=Math[_0x48e040(0x214)](_0x15e051[_0xbfd3aa(0x234)+_0xbfd3aa(0x212)],_0x15e051[_0xbfd3aa(0x25d)][_0xbfd3aa(0x1b4)]-(0xdd2+0x36e*0xb+-0xcb*0x41));_0x50acc4=Math[_0x48e040(0x225)](0x1385+0x1*-0x2209+0xe84,_0x3c2a51+(0x3*-0x2de+0x229c+-0x1a01)),_0xac4049=_0x15e051[_0xbfd3aa(0x25d)][_0xbfd3aa(0x1b4)]-(-0x1be8+-0x1ceb+0x1c6a*0x2);}if(_0xac4049>_0x50acc4){const _0x58a477=_0x15e051['history'][_0x48e040(0x192)](_0x50acc4,_0xac4049),_0x179436=buildBridgeMessage(_0x58a477);_0x179436&&(_0x202f14=_0x179436+_0x23076f,console[_0xbfd3aa(0x266)](_0xbfd3aa(0x198)+(_0x2648b1?_0x48e040(0x235):_0xbfd3aa(0x1c2)+'ry')+':\x20'+(_0x48e040(0x1db)+_0x58a477[_0x48e040(0x1b4)]+('\x20turn(s)\x20i'+'nto\x20prompt'))));}}const {toolsetToAllowedTools:_0x396d6d}=await import(_0x48e040(0x259)+'s/workspac'+_0x48e040(0x209)),_0xfda98b=_0x396d6d(_0x53fbb0['toolset']),_0x4c722d={'prompt':_0x202f14,'systemPrompt':_0x3df1cd,'workingDir':_0x15e051[_0x48e040(0x26b)],'effort':_0x53fbb0[_0x48e040(0x1a8)]??_0x15e051[_0xbfd3aa(0x1a8)],..._0x53fbb0[_0xbfd3aa(0x19c)]?{'model':_0x53fbb0['model']}:{},..._0x53fbb0[_0x48e040(0x185)+'e']!==undefined?{'temperature':_0x53fbb0[_0xbfd3aa(0x185)+'e']}:{},..._0xfda98b?{'allowedTools':_0xfda98b}:{},'abortSignal':_0x15e051[_0x48e040(0x227)+_0xbfd3aa(0x1cc)][_0x48e040(0x260)],'locale':_0x15e051[_0xbfd3aa(0x1f3)],'sessionId':_0x290696&&!_0x2648b1?_0x15e051['sessionId']:null,'history':_0x15e051[_0xbfd3aa(0x25d)],'_sessionState':_0x290696?{'messageCount':_0x15e051[_0x48e040(0x20d)+'nt'],'toolUseCount':_0x15e051[_0x48e040(0x1fe)+'nt']}:undefined,'alvinDispatchContext':_0x290696?{'chatId':_0x10e815[_0xbfd3aa(0x1df)]['id'],'userId':_0x410cf9,'sessionKey':_0x38f32d}:undefined,'onQueryHandle':_0x50deba=>{const _0x43410c=_0xbfd3aa;_0x15e051[_0x43410c(0x25f)]=_0x50deba;}};_0x290696&&isSteeringEnabled()&&(_0x15e051['_steerChan'+_0x48e040(0x25e)]=new SteerChannel(),_0x15e051[_0x48e040(0x204)+'nel'][_0xbfd3aa(0x1c0)](_0x202f14),_0x4c722d['steerChann'+'el']=_0x15e051['_steerChan'+_0xbfd3aa(0x25e)]);let _0x2a5b39=0x863+-0x1090*-0x1+-0x3*0x851,_0x50d554,_0x377452=![],_0x3d185b=![];const _0x2d1b87=_0x15e051[_0xbfd3aa(0x21a)+'kgroundCou'+'nt']??-0x4*-0x1bb+0xe17+0x1503*-0x1;for await(const _0x726f1b of _0x4de674[_0xbfd3aa(0x22c)+_0xbfd3aa(0x1fa)](_0x4c722d,_0x53fbb0[_0xbfd3aa(0x24f)])){if(_0x15e051[_0x48e040(0x172)+_0x48e040(0x1bb)])break;if(_0x726f1b['type']===_0xbfd3aa(0x256)&&(_0x726f1b[_0x48e040(0x1be)]==='Task'||_0x726f1b[_0x48e040(0x1be)]===_0xbfd3aa(0x247))&&_0x726f1b['toolUseId']&&_0x726f1b[_0x48e040(0x17d)+_0x48e040(0x20f)]!==!![])_0xe3b5fd[_0xbfd3aa(0x239)](_0x726f1b[_0xbfd3aa(0x23d)]),_0x3d185b=!![];else _0x726f1b[_0x48e040(0x18d)]===_0x48e040(0x1c1)+'t'&&_0x726f1b[_0x48e040(0x23d)]&&_0xe3b5fd['exitSync'](_0x726f1b['toolUseId']);_0xe3b5fd['reset']();switch(_0x726f1b['type']){case _0xbfd3aa(0x1a0):_0x2cff89=_0x726f1b['text']||'',_0x3d3924['setStatus'](null),await _0x3d3924[_0xbfd3aa(0x273)](_0x2cff89);_0x726f1b['sessionRes'+_0xbfd3aa(0x1bc)+'d']&&(console['warn'](_0x48e040(0x1ce)+_0x48e040(0x1b3)+'equested\x20r'+_0xbfd3aa(0x23c)+_0x38f32d+(_0xbfd3aa(0x186)+_0xbfd3aa(0x25c)+_0xbfd3aa(0x246)+_0x48e040(0x223))),_0x15e051[_0xbfd3aa(0x1e6)]=null,_0x15e051[_0xbfd3aa(0x234)+_0xbfd3aa(0x212)]=-(-0x11b1+-0x175b+0x290d),_0x377452=!![],markSessionDirty(_0x410cf9));if(_0x2cff89[_0xbfd3aa(0x1b4)]>_0x2a5b39){const _0x5ba00d=_0x2cff89[_0xbfd3aa(0x192)](_0x2a5b39);_0x30db27({'platform':'telegram','userId':_0x410cf9,'chatId':_0x10e815[_0xbfd3aa(0x1df)]['id'],'delta':_0x5ba00d,'ts':Date[_0x48e040(0x219)]()}),_0x2a5b39=_0x2cff89['length'];}break;case _0xbfd3aa(0x256):if(_0x726f1b[_0x48e040(0x1be)]){_0x15e051[_0x48e040(0x1fe)+'nt']++;const _0x144068=TOOL_ICONS[_0x726f1b[_0x48e040(0x1be)]]||'๐Ÿ”ง';if(_0x726f1b[_0x48e040(0x1be)]===_0xbfd3aa(0x1f1)||_0x726f1b[_0xbfd3aa(0x1be)]==='Agent'){_0x15e051[_0xbfd3aa(0x26f)+_0x48e040(0x1c8)]++;let _0x534931=_0x726f1b[_0xbfd3aa(0x1be)];if(_0x726f1b['toolInput'])try{const _0x5bb109=JSON['parse'](_0x726f1b[_0x48e040(0x1dd)]);if(_0x5bb109[_0x48e040(0x1e5)+'n']){const _0x5727b4=_0x5bb109[_0xbfd3aa(0x1e5)+'n'][_0x48e040(0x1b4)]>-0x2*0x784+-0x1c2+0x1*0x111a?_0x5bb109['descriptio'+'n'][_0x48e040(0x192)](0x263f+-0x1bf+-0x2480,-0x12ee+0x2*0x921+0xfc)+'โ€ฆ':_0x5bb109[_0x48e040(0x1e5)+'n'];_0x534931=_0x726f1b[_0xbfd3aa(0x1be)]+':\x20'+_0x5727b4;}else _0x5bb109[_0xbfd3aa(0x1c9)+'ype']&&(_0x534931=_0x726f1b[_0x48e040(0x1be)]+'\x20('+_0x5bb109[_0xbfd3aa(0x1c9)+_0x48e040(0x1f5)]+')');_0x50d554={'description':_0x5bb109[_0xbfd3aa(0x1e5)+'n'],'prompt':_0x5bb109['prompt']};}catch{}_0x3d3924['setStatus'](_0x144068+'\x20'+_0x534931+'โ€ฆ');}else _0x3d3924[_0x48e040(0x183)](_0x144068+'\x20'+_0x726f1b[_0xbfd3aa(0x1be)]+'โ€ฆ');}break;case _0xbfd3aa(0x1c1)+'t':handleToolResultChunk(_0x726f1b,{'chatId':_0x10e815[_0xbfd3aa(0x1df)]['id'],'userId':_0x410cf9,'sessionKey':_0x38f32d,'lastToolUseInput':_0x50d554}),_0x50d554=undefined;break;case _0x48e040(0x250):if(_0x726f1b[_0xbfd3aa(0x1e6)]&&!_0x377452)_0x15e051[_0x48e040(0x1e6)]=_0x726f1b[_0xbfd3aa(0x1e6)];if(_0x726f1b[_0x48e040(0x194)])_0x15e051['totalCost']+=_0x726f1b['costUsd'];typeof _0x726f1b[_0xbfd3aa(0x1fd)+'s']===_0xbfd3aa(0x231)&&_0x726f1b[_0xbfd3aa(0x1fd)+'s']>-0x1287+-0x7*0x454+-0x1af*-0x1d&&(_0x15e051[_0x48e040(0x1eb)+_0x48e040(0x25b)]=_0x726f1b[_0x48e040(0x1fd)+'s']);trackProviderUsage(_0x410cf9,_0x4de674['getActiveK'+'ey'](),_0x726f1b[_0x48e040(0x194)]||-0x1c41+-0x1*0x1aa2+0x36e3,_0x726f1b[_0xbfd3aa(0x1fd)+'s'],_0x726f1b['outputToke'+'ns']),trackUsage(_0x4de674[_0xbfd3aa(0x21d)+'ey'](),_0x726f1b[_0x48e040(0x1fd)+'s']||-0xc2*0x13+0x1442+0x12c*-0x5,_0x726f1b[_0x48e040(0x1d3)+'ns']||-0x15b2*0x1+-0xd*-0xf2+-0x4*-0x25a,_0x726f1b[_0xbfd3aa(0x194)]||0x23cb+0x15d5*0x1+-0x39a0*0x1),_0x15e051[_0xbfd3aa(0x1b2)+'ty']=Date[_0xbfd3aa(0x219)]();break;case _0x48e040(0x1f8):await _0x10e815[_0xbfd3aa(0x19d)](_0xbfd3aa(0x254)+_0x726f1b['failedProv'+_0xbfd3aa(0x26a)]+(_0xbfd3aa(0x18c)+'le\x20โ€”\x20switc'+'hing\x20to\x20')+_0x726f1b[_0xbfd3aa(0x202)+'me']+'_',{'parse_mode':_0x48e040(0x24b)});break;case'error':if(_0x15e051['_bypassAbo'+_0xbfd3aa(0x23e)]===!![]&&_0x726f1b[_0x48e040(0x245)]?.[_0x48e040(0x271)+'e']()[_0x48e040(0x1d5)](_0xbfd3aa(0x1d2))){_0x38a6ac=!![];break;}if(_0x88919a)await _0x10e815['reply'](t(_0xbfd3aa(0x203)+'timeoutStu'+'ck',_0x15e051[_0xbfd3aa(0x1f3)],{'min':STUCK_TIMEOUT_MINUTES}));else!isHarmlessTelegramError(_0x726f1b[_0x48e040(0x245)])&&await _0x10e815[_0x48e040(0x19d)](t(_0xbfd3aa(0x203)+_0x48e040(0x207),_0x15e051['language'])+'\x20'+_0x726f1b[_0x48e040(0x245)]);break;}}if(!_0x38a6ac&&!_0x88919a&&!_0x15e051['_stopReque'+'sted']&&detectUndetachedBackgroundClaim({'taskChunkSeenWithoutRunInBackground':_0x3d185b,'dispatchAgentFired':![],'pendingBackgroundDelta':(_0x15e051['pendingBac'+_0x48e040(0x1e9)+'nt']??-0x9a7+-0x7e+0xa25)-_0x2d1b87}))try{await _0x10e815[_0xbfd3aa(0x19d)](t(_0xbfd3aa(0x21f)+_0x48e040(0x236)+_0xbfd3aa(0x1e2),_0x15e051['language']));}catch{}if(_0x38a6ac)return;if(shouldSuppressFinalSend({'stopRequested':_0x15e051['_stopReque'+_0x48e040(0x1bb)],'visibleTextAlreadySent':_0x3d3924[_0x48e040(0x22a)+'t']}))return;if(_0x15e051[_0x48e040(0x172)+_0x48e040(0x1bb)]&&_0x3d3924['hasSentTex'+'t']){await _0x3d3924[_0xbfd3aa(0x173)](_0x2cff89);return;}await _0x3d3924[_0x48e040(0x173)](_0x2cff89),emit('message:se'+'nt',{'userId':_0x410cf9,'text':_0x2cff89,'platform':_0x48e040(0x222)}),_0x479824({'platform':_0xbfd3aa(0x222),'userId':_0x410cf9,'chatId':_0x10e815[_0xbfd3aa(0x1df)]['id'],'finalText':_0x2cff89,'cost':_0x15e051[_0xbfd3aa(0x19a)+'ider'][_0x4de674['getActiveK'+'ey']()],'ts':Date['now']()}),await react(_0x10e815,'๐Ÿ‘');_0x2cff89&&(addToHistory(_0x410cf9,{'role':_0x48e040(0x211),'content':_0x2cff89}),_0x290696&&(_0x15e051[_0xbfd3aa(0x234)+_0xbfd3aa(0x212)]=_0x15e051['history'][_0x48e040(0x1b4)]-(0x228a+-0x1d6a+-0x51f)));if(_0x15e051[_0x48e040(0x1cd)]&&_0x2cff89[_0xbfd3aa(0x237)]())try{await _0x10e815[_0x48e040(0x180)][_0x48e040(0x213)+_0x48e040(0x24e)](_0x10e815[_0x48e040(0x1df)]['id'],_0xbfd3aa(0x1a9)+'ce');const _0x26d1f6=await textToSpeech(_0x2cff89,_0x53fbb0[_0x48e040(0x269)]);await _0x10e815['replyWithV'+'oice'](new InputFile(_0x599c4c[_0x48e040(0x20c)+'nc'](_0x26d1f6),_0xbfd3aa(0x220)+'p3')),_0x599c4c['unlink'](_0x26d1f6,()=>{});}catch(_0x6d94fc){console[_0xbfd3aa(0x245)](_0xbfd3aa(0x177),_0x6d94fc);}}catch(_0x7ca223){const _0x292040=_0x7ca223 instanceof Error?_0x7ca223['message']:String(_0x7ca223),_0x4792d5=_0x15e051['language'],_0x2a2d=_0x292040['includes'](_0x48e040(0x1d2))&&_0x15e051['_bypassAbo'+_0x48e040(0x23e)]===!![];if(_0x2a2d){}else{if(_0x88919a)await react(_0x10e815,'๐Ÿ‘Ž'),await _0x10e815[_0xbfd3aa(0x19d)](t(_0xbfd3aa(0x203)+_0x48e040(0x230)+'ck',_0x4792d5,{'min':STUCK_TIMEOUT_MINUTES}));else{if(_0x292040[_0x48e040(0x1d5)]('abort'))await react(_0x10e815,'๐Ÿ‘Ž'),await _0x10e815[_0x48e040(0x19d)](t(_0x48e040(0x203)+_0xbfd3aa(0x19e)+_0x48e040(0x1a5),_0x4792d5));else!isHarmlessTelegramError(_0x7ca223)&&(await react(_0x10e815,'๐Ÿ‘Ž'),await _0x10e815[_0xbfd3aa(0x19d)](t(_0x48e040(0x203)+_0xbfd3aa(0x207),_0x4792d5)+'\x20'+_0x292040));}}}finally{_0xe3b5fd[_0xbfd3aa(0x1f9)](),clearInterval(_0xb113e9);if(_0x15e051[_0x48e040(0x275)]===_0x233efd){if(_0x592f07!==null)try{await _0x10e815[_0xbfd3aa(0x180)][_0xbfd3aa(0x1b9)+_0x48e040(0x1a6)](_0x10e815[_0xbfd3aa(0x1df)]['id'],_0x592f07);}catch{}_0x15e051[_0xbfd3aa(0x174)+'ng']=![],_0x15e051[_0xbfd3aa(0x227)+_0xbfd3aa(0x1cc)]=null;try{_0x15e051[_0xbfd3aa(0x204)+_0xbfd3aa(0x25e)]?.[_0xbfd3aa(0x262)]();}catch{}_0x15e051[_0xbfd3aa(0x204)+'nel']=null,_0x15e051['_steerAckS'+_0xbfd3aa(0x224)+'n']=![],_0x15e051['_qHandle']=null,_0x15e051[_0x48e040(0x172)+_0x48e040(0x1bb)]=null,_0x15e051[_0x48e040(0x275)]=null;}}}