@dreb/telegram 2.4.1 → 2.4.3

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.
@@ -104,6 +104,13 @@ export declare class AgentBridge {
104
104
  * Set model.
105
105
  */
106
106
  setModel(provider: string, modelId: string): Promise<any>;
107
+ /**
108
+ * Resolve a model pattern using the same logic as CLI/TUI.
109
+ */
110
+ resolveModel(pattern: string): Promise<{
111
+ model: any;
112
+ warning?: string;
113
+ } | null>;
107
114
  /**
108
115
  * Set thinking level.
109
116
  */
@@ -1 +1 @@
1
- {"version":3,"file":"agent-bridge.d.ts","sourceRoot":"","sources":["../src/agent-bridge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Agent bridge — manages the RPC connection to a dreb agent process.\n * One bridge per user, handles lifecycle, event subscription, and session management.\n */\n\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { RpcClient, type RpcSessionInfo } from \"@dreb/coding-agent/rpc\";\nimport type { Config } from \"./config.js\";\nimport { log } from \"./util/telegram.js\";\n\n/**\n * Resolve the absolute path to the dreb CLI entry point.\n * RpcClient defaults to \"dist/cli.js\" (relative to cwd), but we need\n * the absolute path since the bot's working dir differs from the dreb repo.\n */\nfunction resolveDrebCliPath(): string {\n\t// import.meta.resolve finds @dreb/coding-agent/dist/index.js\n\tconst resolved = import.meta.resolve(\"@dreb/coding-agent\");\n\tconst distDir = dirname(fileURLToPath(resolved));\n\treturn join(distDir, \"cli.js\");\n}\n\n/** RPC events include both AgentEvent and session-specific events */\ntype RpcEvent = { type: string; [key: string]: any };\n\nexport type AgentEventListener = (event: RpcEvent) => void;\n\nexport class AgentBridge {\n\tprivate client: RpcClient | null = null;\n\tprivate eventListeners: AgentEventListener[] = [];\n\tprivate _isStreaming = false;\n\tprivate _sessionFile: string | undefined;\n\tprivate _sessionId: string | undefined;\n\tprivate exited = false;\n\n\tconstructor(private config: Config) {}\n\n\t/** Whether the RPC process is alive */\n\tget isAlive(): boolean {\n\t\treturn this.client !== null && !this.exited;\n\t}\n\n\t/** Whether the agent is currently streaming a response */\n\tget isStreaming(): boolean {\n\t\treturn this._isStreaming;\n\t}\n\n\t/** Current session file path */\n\tget sessionFile(): string | undefined {\n\t\treturn this._sessionFile;\n\t}\n\n\t/** Current session ID */\n\tget sessionId(): string | undefined {\n\t\treturn this._sessionId;\n\t}\n\n\t/**\n\t * Start the RPC process. Does NOT resume a session — call resumeLatest() or newSession() after.\n\t */\n\tasync start(): Promise<void> {\n\t\tif (this.client) return;\n\n\t\tthis.client = new RpcClient({\n\t\t\tcliPath: resolveDrebCliPath(),\n\t\t\tcwd: this.config.workingDir,\n\t\t\tprovider: this.config.provider,\n\t\t\tmodel: this.config.model,\n\t\t\targs: [\"--ui\", \"telegram\"],\n\t\t});\n\n\t\tthis.exited = false;\n\t\tawait this.client.start();\n\n\t\t// Subscribe to events and forward to listeners\n\t\t// Cast: RpcClient types events as AgentEvent but actually forwards all AgentSessionEvent types\n\t\tthis.client.onEvent((event) => {\n\t\t\tthis.handleEvent(event as RpcEvent);\n\t\t});\n\n\t\t// Detect process exit\n\t\t// RpcClient doesn't expose a direct \"on exit\" — we detect it when send() fails\n\t\tlog(\"[BRIDGE] RPC process started\");\n\t}\n\n\t/**\n\t * Resume the most recent session, or do nothing if no sessions exist.\n\t */\n\tasync resumeLatest(): Promise<boolean> {\n\t\tif (!this.client) return false;\n\t\ttry {\n\t\t\tconst sessions = await this.client.listSessions();\n\t\t\tif (sessions.length === 0) return false;\n\n\t\t\tconst latest = sessions[0]; // Already sorted by modified desc\n\t\t\tconst result = await this.client.switchSession(latest.path);\n\t\t\tif (!result.cancelled) {\n\t\t\t\tthis._sessionFile = latest.path;\n\t\t\t\tthis._sessionId = latest.id;\n\t\t\t\tlog(`[BRIDGE] Resumed session ${latest.id.slice(0, 8)}`);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tlog(`[BRIDGE] Failed to resume latest session: ${e}`);\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * List available sessions.\n\t */\n\tasync listSessions(): Promise<RpcSessionInfo[]> {\n\t\tif (!this.client) return [];\n\t\ttry {\n\t\t\treturn await this.client.listSessions();\n\t\t} catch (e) {\n\t\t\tlog(`[BRIDGE] Failed to list sessions: ${e}`);\n\t\t\treturn [];\n\t\t}\n\t}\n\n\t/**\n\t * Switch to a specific session by path.\n\t */\n\tasync switchSession(sessionPath: string): Promise<boolean> {\n\t\tif (!this.client) return false;\n\t\ttry {\n\t\t\tconst result = await this.client.switchSession(sessionPath);\n\t\t\tif (!result.cancelled) {\n\t\t\t\tthis._sessionFile = sessionPath;\n\t\t\t\tconst state = await this.client.getState();\n\t\t\t\tthis._sessionId = state.sessionId;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tlog(`[BRIDGE] Failed to switch session: ${e}`);\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Create a new session.\n\t */\n\tasync newSession(): Promise<boolean> {\n\t\tif (!this.client) return false;\n\t\ttry {\n\t\t\tconst result = await this.client.newSession();\n\t\t\tif (!result.cancelled) {\n\t\t\t\tconst state = await this.client.getState();\n\t\t\t\tthis._sessionFile = state.sessionFile;\n\t\t\t\tthis._sessionId = state.sessionId;\n\t\t\t\tlog(`[BRIDGE] New session ${state.sessionId.slice(0, 8)}`);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tlog(`[BRIDGE] Failed to create new session: ${e}`);\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Send a prompt to the agent.\n\t */\n\tasync prompt(message: string, images?: Array<{ type: \"image\"; data: string; mimeType: string }>): Promise<void> {\n\t\tawait this.ensureAlive();\n\t\ttry {\n\t\t\tawait this.client!.prompt(message, images);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Queue a steering message to interrupt the agent mid-run.\n\t * The agent injects it after the current tool-call batch finishes.\n\t */\n\tasync steer(message: string, images?: Array<{ type: \"image\"; data: string; mimeType: string }>): Promise<void> {\n\t\tawait this.ensureAlive();\n\t\ttry {\n\t\t\tawait this.client!.steer(message, images);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Queue a follow-up message for after the agent finishes its current run.\n\t */\n\tasync followUp(message: string, images?: Array<{ type: \"image\"; data: string; mimeType: string }>): Promise<void> {\n\t\tawait this.ensureAlive();\n\t\ttry {\n\t\t\tawait this.client!.followUp(message, images);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Abort the current operation.\n\t */\n\tasync abort(): Promise<void> {\n\t\tif (!this.client) return;\n\t\ttry {\n\t\t\tawait this.client.abort();\n\t\t} catch {\n\t\t\t// Process may have already exited\n\t\t}\n\t}\n\n\t/**\n\t * Get the dreb version.\n\t */\n\tasync getVersion(): Promise<string> {\n\t\tawait this.ensureAlive();\n\t\ttry {\n\t\t\treturn await this.client!.getVersion();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get session statistics.\n\t */\n\tasync getSessionStats(): Promise<any> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.getSessionStats();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get current state.\n\t */\n\tasync getState(): Promise<any> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.getState();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get available commands (skills, extensions, prompt templates).\n\t */\n\tasync getCommands(): Promise<any[]> {\n\t\tif (!this.client) return [];\n\t\ttry {\n\t\t\treturn await this.client.getCommands();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Compact context.\n\t */\n\tasync compact(): Promise<any> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.compact();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get available models.\n\t */\n\tasync getAvailableModels(): Promise<any[]> {\n\t\tif (!this.client) return [];\n\t\ttry {\n\t\t\treturn await this.client.getAvailableModels();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Set model.\n\t */\n\tasync setModel(provider: string, modelId: string): Promise<any> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.setModel(provider, modelId);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Set thinking level.\n\t */\n\tasync setThinkingLevel(level: string): Promise<void> {\n\t\tif (!this.client) return;\n\t\ttry {\n\t\t\tawait this.client.setThinkingLevel(level as any);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get all messages.\n\t */\n\tasync getMessages(): Promise<any[]> {\n\t\tif (!this.client) return [];\n\t\ttry {\n\t\t\treturn await this.client.getMessages();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Hatch a new buddy companion. Runs inside the agent process\n\t * so API keys never cross the process boundary.\n\t */\n\tasync buddyHatch(): Promise<any> {\n\t\tif (!this.client) throw new Error(\"Agent not connected.\");\n\t\ttry {\n\t\t\treturn await this.client.buddyHatch();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Reroll the buddy companion. Runs inside the agent process\n\t * so API keys never cross the process boundary.\n\t */\n\tasync buddyReroll(): Promise<any> {\n\t\tif (!this.client) throw new Error(\"Agent not connected.\");\n\t\ttry {\n\t\t\treturn await this.client.buddyReroll();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get last assistant text.\n\t */\n\tasync getLastAssistantText(): Promise<string | null> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.getLastAssistantText();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Refresh session info from the RPC process state.\n\t */\n\tasync refreshSessionInfo(): Promise<void> {\n\t\tif (!this.client) return;\n\t\ttry {\n\t\t\tconst state = await this.client.getState();\n\t\t\tthis._sessionFile = state.sessionFile;\n\t\t\tthis._sessionId = state.sessionId;\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\t// Non-critical — don't re-throw\n\t\t}\n\t}\n\n\t/**\n\t * Subscribe to agent events.\n\t */\n\tonEvent(listener: AgentEventListener): () => void {\n\t\tthis.eventListeners.push(listener);\n\t\treturn () => {\n\t\t\tconst idx = this.eventListeners.indexOf(listener);\n\t\t\tif (idx !== -1) this.eventListeners.splice(idx, 1);\n\t\t};\n\t}\n\n\t/**\n\t * Stop the RPC process.\n\t */\n\tasync stop(): Promise<void> {\n\t\tif (this.client) {\n\t\t\ttry {\n\t\t\t\tawait this.client.stop();\n\t\t\t} catch {\n\t\t\t\t// Ignore\n\t\t\t}\n\t\t\tthis.client = null;\n\t\t\tthis.exited = true;\n\t\t\tthis.eventListeners = [];\n\t\t\tlog(\"[BRIDGE] RPC process stopped\");\n\t\t}\n\t}\n\n\t// =========================================================================\n\t// Internal\n\t// =========================================================================\n\n\tprivate handleEvent(event: RpcEvent): void {\n\t\t// Track streaming state\n\t\tif (event.type === \"agent_start\") this._isStreaming = true;\n\t\tif (event.type === \"agent_end\") {\n\t\t\tthis._isStreaming = false;\n\t\t\t// Capture session info from agent_end messages\n\t\t\t// Session file/id updates happen via getState after prompt\n\t\t}\n\n\t\tfor (const listener of this.eventListeners) {\n\t\t\ttry {\n\t\t\t\tlistener(event);\n\t\t\t} catch (e) {\n\t\t\t\tlog(`[BRIDGE] Event listener error: ${e}`);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handleProcessError(e: unknown): void {\n\t\tconst msg = e instanceof Error ? e.message : String(e);\n\t\tif (\n\t\t\tmsg.includes(\"not started\") ||\n\t\t\tmsg.includes(\"not running\") ||\n\t\t\tmsg.includes(\"EPIPE\") ||\n\t\t\tmsg.includes(\"write after end\") ||\n\t\t\tmsg.includes(\"Timeout waiting for response\") ||\n\t\t\tmsg.includes(\"RPC process exited\")\n\t\t) {\n\t\t\tlog(`[BRIDGE] RPC process exited or hung: ${msg.slice(0, 100)}`);\n\t\t\t// Kill the child process before dropping the reference to prevent orphans\n\t\t\tif (this.client) {\n\t\t\t\tthis.client.stop().catch(() => {});\n\t\t\t}\n\t\t\tthis.exited = true;\n\t\t\tthis.client = null;\n\t\t}\n\t}\n\n\tprivate async ensureAlive(): Promise<void> {\n\t\tif (!this.client || this.exited) {\n\t\t\tlog(\"[BRIDGE] Restarting dead RPC process\");\n\t\t\tthis.client = null;\n\t\t\tthis.exited = false;\n\t\t\tawait this.start();\n\t\t\t// Session selection is handled by ensureBridgeWithSession — not here.\n\t\t\t// The previous session is still the latest and will be picked up by\n\t\t\t// resumeLatest() on the next message.\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"agent-bridge.d.ts","sourceRoot":"","sources":["../src/agent-bridge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Agent bridge — manages the RPC connection to a dreb agent process.\n * One bridge per user, handles lifecycle, event subscription, and session management.\n */\n\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { RpcClient, type RpcSessionInfo } from \"@dreb/coding-agent/rpc\";\nimport type { Config } from \"./config.js\";\nimport { log } from \"./util/telegram.js\";\n\n/**\n * Resolve the absolute path to the dreb CLI entry point.\n * RpcClient defaults to \"dist/cli.js\" (relative to cwd), but we need\n * the absolute path since the bot's working dir differs from the dreb repo.\n */\nfunction resolveDrebCliPath(): string {\n\t// import.meta.resolve finds @dreb/coding-agent/dist/index.js\n\tconst resolved = import.meta.resolve(\"@dreb/coding-agent\");\n\tconst distDir = dirname(fileURLToPath(resolved));\n\treturn join(distDir, \"cli.js\");\n}\n\n/** RPC events include both AgentEvent and session-specific events */\ntype RpcEvent = { type: string; [key: string]: any };\n\nexport type AgentEventListener = (event: RpcEvent) => void;\n\nexport class AgentBridge {\n\tprivate client: RpcClient | null = null;\n\tprivate eventListeners: AgentEventListener[] = [];\n\tprivate _isStreaming = false;\n\tprivate _sessionFile: string | undefined;\n\tprivate _sessionId: string | undefined;\n\tprivate exited = false;\n\n\tconstructor(private config: Config) {}\n\n\t/** Whether the RPC process is alive */\n\tget isAlive(): boolean {\n\t\treturn this.client !== null && !this.exited;\n\t}\n\n\t/** Whether the agent is currently streaming a response */\n\tget isStreaming(): boolean {\n\t\treturn this._isStreaming;\n\t}\n\n\t/** Current session file path */\n\tget sessionFile(): string | undefined {\n\t\treturn this._sessionFile;\n\t}\n\n\t/** Current session ID */\n\tget sessionId(): string | undefined {\n\t\treturn this._sessionId;\n\t}\n\n\t/**\n\t * Start the RPC process. Does NOT resume a session — call resumeLatest() or newSession() after.\n\t */\n\tasync start(): Promise<void> {\n\t\tif (this.client) return;\n\n\t\tthis.client = new RpcClient({\n\t\t\tcliPath: resolveDrebCliPath(),\n\t\t\tcwd: this.config.workingDir,\n\t\t\tprovider: this.config.provider,\n\t\t\tmodel: this.config.model,\n\t\t\targs: [\"--ui\", \"telegram\"],\n\t\t});\n\n\t\tthis.exited = false;\n\t\tawait this.client.start();\n\n\t\t// Subscribe to events and forward to listeners\n\t\t// Cast: RpcClient types events as AgentEvent but actually forwards all AgentSessionEvent types\n\t\tthis.client.onEvent((event) => {\n\t\t\tthis.handleEvent(event as RpcEvent);\n\t\t});\n\n\t\t// Detect process exit\n\t\t// RpcClient doesn't expose a direct \"on exit\" — we detect it when send() fails\n\t\tlog(\"[BRIDGE] RPC process started\");\n\t}\n\n\t/**\n\t * Resume the most recent session, or do nothing if no sessions exist.\n\t */\n\tasync resumeLatest(): Promise<boolean> {\n\t\tif (!this.client) return false;\n\t\ttry {\n\t\t\tconst sessions = await this.client.listSessions();\n\t\t\tif (sessions.length === 0) return false;\n\n\t\t\tconst latest = sessions[0]; // Already sorted by modified desc\n\t\t\tconst result = await this.client.switchSession(latest.path);\n\t\t\tif (!result.cancelled) {\n\t\t\t\tthis._sessionFile = latest.path;\n\t\t\t\tthis._sessionId = latest.id;\n\t\t\t\tlog(`[BRIDGE] Resumed session ${latest.id.slice(0, 8)}`);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tlog(`[BRIDGE] Failed to resume latest session: ${e}`);\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * List available sessions.\n\t */\n\tasync listSessions(): Promise<RpcSessionInfo[]> {\n\t\tif (!this.client) return [];\n\t\ttry {\n\t\t\treturn await this.client.listSessions();\n\t\t} catch (e) {\n\t\t\tlog(`[BRIDGE] Failed to list sessions: ${e}`);\n\t\t\treturn [];\n\t\t}\n\t}\n\n\t/**\n\t * Switch to a specific session by path.\n\t */\n\tasync switchSession(sessionPath: string): Promise<boolean> {\n\t\tif (!this.client) return false;\n\t\ttry {\n\t\t\tconst result = await this.client.switchSession(sessionPath);\n\t\t\tif (!result.cancelled) {\n\t\t\t\tthis._sessionFile = sessionPath;\n\t\t\t\tconst state = await this.client.getState();\n\t\t\t\tthis._sessionId = state.sessionId;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tlog(`[BRIDGE] Failed to switch session: ${e}`);\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Create a new session.\n\t */\n\tasync newSession(): Promise<boolean> {\n\t\tif (!this.client) return false;\n\t\ttry {\n\t\t\tconst result = await this.client.newSession();\n\t\t\tif (!result.cancelled) {\n\t\t\t\tconst state = await this.client.getState();\n\t\t\t\tthis._sessionFile = state.sessionFile;\n\t\t\t\tthis._sessionId = state.sessionId;\n\t\t\t\tlog(`[BRIDGE] New session ${state.sessionId.slice(0, 8)}`);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tlog(`[BRIDGE] Failed to create new session: ${e}`);\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Send a prompt to the agent.\n\t */\n\tasync prompt(message: string, images?: Array<{ type: \"image\"; data: string; mimeType: string }>): Promise<void> {\n\t\tawait this.ensureAlive();\n\t\ttry {\n\t\t\tawait this.client!.prompt(message, images);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Queue a steering message to interrupt the agent mid-run.\n\t * The agent injects it after the current tool-call batch finishes.\n\t */\n\tasync steer(message: string, images?: Array<{ type: \"image\"; data: string; mimeType: string }>): Promise<void> {\n\t\tawait this.ensureAlive();\n\t\ttry {\n\t\t\tawait this.client!.steer(message, images);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Queue a follow-up message for after the agent finishes its current run.\n\t */\n\tasync followUp(message: string, images?: Array<{ type: \"image\"; data: string; mimeType: string }>): Promise<void> {\n\t\tawait this.ensureAlive();\n\t\ttry {\n\t\t\tawait this.client!.followUp(message, images);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Abort the current operation.\n\t */\n\tasync abort(): Promise<void> {\n\t\tif (!this.client) return;\n\t\ttry {\n\t\t\tawait this.client.abort();\n\t\t} catch {\n\t\t\t// Process may have already exited\n\t\t}\n\t}\n\n\t/**\n\t * Get the dreb version.\n\t */\n\tasync getVersion(): Promise<string> {\n\t\tawait this.ensureAlive();\n\t\ttry {\n\t\t\treturn await this.client!.getVersion();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get session statistics.\n\t */\n\tasync getSessionStats(): Promise<any> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.getSessionStats();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get current state.\n\t */\n\tasync getState(): Promise<any> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.getState();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get available commands (skills, extensions, prompt templates).\n\t */\n\tasync getCommands(): Promise<any[]> {\n\t\tif (!this.client) return [];\n\t\ttry {\n\t\t\treturn await this.client.getCommands();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Compact context.\n\t */\n\tasync compact(): Promise<any> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.compact();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get available models.\n\t */\n\tasync getAvailableModels(): Promise<any[]> {\n\t\tif (!this.client) return [];\n\t\ttry {\n\t\t\treturn await this.client.getAvailableModels();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Set model.\n\t */\n\tasync setModel(provider: string, modelId: string): Promise<any> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.setModel(provider, modelId);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Resolve a model pattern using the same logic as CLI/TUI.\n\t */\n\tasync resolveModel(pattern: string): Promise<{ model: any; warning?: string } | null> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.resolveModel(pattern);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Set thinking level.\n\t */\n\tasync setThinkingLevel(level: string): Promise<void> {\n\t\tif (!this.client) return;\n\t\ttry {\n\t\t\tawait this.client.setThinkingLevel(level as any);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get all messages.\n\t */\n\tasync getMessages(): Promise<any[]> {\n\t\tif (!this.client) return [];\n\t\ttry {\n\t\t\treturn await this.client.getMessages();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Hatch a new buddy companion. Runs inside the agent process\n\t * so API keys never cross the process boundary.\n\t */\n\tasync buddyHatch(): Promise<any> {\n\t\tif (!this.client) throw new Error(\"Agent not connected.\");\n\t\ttry {\n\t\t\treturn await this.client.buddyHatch();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Reroll the buddy companion. Runs inside the agent process\n\t * so API keys never cross the process boundary.\n\t */\n\tasync buddyReroll(): Promise<any> {\n\t\tif (!this.client) throw new Error(\"Agent not connected.\");\n\t\ttry {\n\t\t\treturn await this.client.buddyReroll();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get last assistant text.\n\t */\n\tasync getLastAssistantText(): Promise<string | null> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.getLastAssistantText();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Refresh session info from the RPC process state.\n\t */\n\tasync refreshSessionInfo(): Promise<void> {\n\t\tif (!this.client) return;\n\t\ttry {\n\t\t\tconst state = await this.client.getState();\n\t\t\tthis._sessionFile = state.sessionFile;\n\t\t\tthis._sessionId = state.sessionId;\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\t// Non-critical — don't re-throw\n\t\t}\n\t}\n\n\t/**\n\t * Subscribe to agent events.\n\t */\n\tonEvent(listener: AgentEventListener): () => void {\n\t\tthis.eventListeners.push(listener);\n\t\treturn () => {\n\t\t\tconst idx = this.eventListeners.indexOf(listener);\n\t\t\tif (idx !== -1) this.eventListeners.splice(idx, 1);\n\t\t};\n\t}\n\n\t/**\n\t * Stop the RPC process.\n\t */\n\tasync stop(): Promise<void> {\n\t\tif (this.client) {\n\t\t\ttry {\n\t\t\t\tawait this.client.stop();\n\t\t\t} catch {\n\t\t\t\t// Ignore\n\t\t\t}\n\t\t\tthis.client = null;\n\t\t\tthis.exited = true;\n\t\t\tthis.eventListeners = [];\n\t\t\tlog(\"[BRIDGE] RPC process stopped\");\n\t\t}\n\t}\n\n\t// =========================================================================\n\t// Internal\n\t// =========================================================================\n\n\tprivate handleEvent(event: RpcEvent): void {\n\t\t// Track streaming state\n\t\tif (event.type === \"agent_start\") this._isStreaming = true;\n\t\tif (event.type === \"agent_end\") {\n\t\t\tthis._isStreaming = false;\n\t\t\t// Capture session info from agent_end messages\n\t\t\t// Session file/id updates happen via getState after prompt\n\t\t}\n\n\t\tfor (const listener of this.eventListeners) {\n\t\t\ttry {\n\t\t\t\tlistener(event);\n\t\t\t} catch (e) {\n\t\t\t\tlog(`[BRIDGE] Event listener error: ${e}`);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handleProcessError(e: unknown): void {\n\t\tconst msg = e instanceof Error ? e.message : String(e);\n\t\tif (\n\t\t\tmsg.includes(\"not started\") ||\n\t\t\tmsg.includes(\"not running\") ||\n\t\t\tmsg.includes(\"EPIPE\") ||\n\t\t\tmsg.includes(\"write after end\") ||\n\t\t\tmsg.includes(\"Timeout waiting for response\") ||\n\t\t\tmsg.includes(\"RPC process exited\")\n\t\t) {\n\t\t\tlog(`[BRIDGE] RPC process exited or hung: ${msg.slice(0, 100)}`);\n\t\t\t// Kill the child process before dropping the reference to prevent orphans\n\t\t\tif (this.client) {\n\t\t\t\tthis.client.stop().catch(() => {});\n\t\t\t}\n\t\t\tthis.exited = true;\n\t\t\tthis.client = null;\n\t\t}\n\t}\n\n\tprivate async ensureAlive(): Promise<void> {\n\t\tif (!this.client || this.exited) {\n\t\t\tlog(\"[BRIDGE] Restarting dead RPC process\");\n\t\t\tthis.client = null;\n\t\t\tthis.exited = false;\n\t\t\tawait this.start();\n\t\t\t// Session selection is handled by ensureBridgeWithSession — not here.\n\t\t\t// The previous session is still the latest and will be picked up by\n\t\t\t// resumeLatest() on the next message.\n\t\t}\n\t}\n}\n"]}
@@ -297,6 +297,20 @@ export class AgentBridge {
297
297
  throw e;
298
298
  }
299
299
  }
300
+ /**
301
+ * Resolve a model pattern using the same logic as CLI/TUI.
302
+ */
303
+ async resolveModel(pattern) {
304
+ if (!this.client)
305
+ return null;
306
+ try {
307
+ return await this.client.resolveModel(pattern);
308
+ }
309
+ catch (e) {
310
+ this.handleProcessError(e);
311
+ throw e;
312
+ }
313
+ }
300
314
  /**
301
315
  * Set thinking level.
302
316
  */
@@ -1 +1 @@
1
- {"version":3,"file":"agent-bridge.js","sourceRoot":"","sources":["../src/agent-bridge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Agent bridge — manages the RPC connection to a dreb agent process.\n * One bridge per user, handles lifecycle, event subscription, and session management.\n */\n\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { RpcClient, type RpcSessionInfo } from \"@dreb/coding-agent/rpc\";\nimport type { Config } from \"./config.js\";\nimport { log } from \"./util/telegram.js\";\n\n/**\n * Resolve the absolute path to the dreb CLI entry point.\n * RpcClient defaults to \"dist/cli.js\" (relative to cwd), but we need\n * the absolute path since the bot's working dir differs from the dreb repo.\n */\nfunction resolveDrebCliPath(): string {\n\t// import.meta.resolve finds @dreb/coding-agent/dist/index.js\n\tconst resolved = import.meta.resolve(\"@dreb/coding-agent\");\n\tconst distDir = dirname(fileURLToPath(resolved));\n\treturn join(distDir, \"cli.js\");\n}\n\n/** RPC events include both AgentEvent and session-specific events */\ntype RpcEvent = { type: string; [key: string]: any };\n\nexport type AgentEventListener = (event: RpcEvent) => void;\n\nexport class AgentBridge {\n\tprivate client: RpcClient | null = null;\n\tprivate eventListeners: AgentEventListener[] = [];\n\tprivate _isStreaming = false;\n\tprivate _sessionFile: string | undefined;\n\tprivate _sessionId: string | undefined;\n\tprivate exited = false;\n\n\tconstructor(private config: Config) {}\n\n\t/** Whether the RPC process is alive */\n\tget isAlive(): boolean {\n\t\treturn this.client !== null && !this.exited;\n\t}\n\n\t/** Whether the agent is currently streaming a response */\n\tget isStreaming(): boolean {\n\t\treturn this._isStreaming;\n\t}\n\n\t/** Current session file path */\n\tget sessionFile(): string | undefined {\n\t\treturn this._sessionFile;\n\t}\n\n\t/** Current session ID */\n\tget sessionId(): string | undefined {\n\t\treturn this._sessionId;\n\t}\n\n\t/**\n\t * Start the RPC process. Does NOT resume a session — call resumeLatest() or newSession() after.\n\t */\n\tasync start(): Promise<void> {\n\t\tif (this.client) return;\n\n\t\tthis.client = new RpcClient({\n\t\t\tcliPath: resolveDrebCliPath(),\n\t\t\tcwd: this.config.workingDir,\n\t\t\tprovider: this.config.provider,\n\t\t\tmodel: this.config.model,\n\t\t\targs: [\"--ui\", \"telegram\"],\n\t\t});\n\n\t\tthis.exited = false;\n\t\tawait this.client.start();\n\n\t\t// Subscribe to events and forward to listeners\n\t\t// Cast: RpcClient types events as AgentEvent but actually forwards all AgentSessionEvent types\n\t\tthis.client.onEvent((event) => {\n\t\t\tthis.handleEvent(event as RpcEvent);\n\t\t});\n\n\t\t// Detect process exit\n\t\t// RpcClient doesn't expose a direct \"on exit\" — we detect it when send() fails\n\t\tlog(\"[BRIDGE] RPC process started\");\n\t}\n\n\t/**\n\t * Resume the most recent session, or do nothing if no sessions exist.\n\t */\n\tasync resumeLatest(): Promise<boolean> {\n\t\tif (!this.client) return false;\n\t\ttry {\n\t\t\tconst sessions = await this.client.listSessions();\n\t\t\tif (sessions.length === 0) return false;\n\n\t\t\tconst latest = sessions[0]; // Already sorted by modified desc\n\t\t\tconst result = await this.client.switchSession(latest.path);\n\t\t\tif (!result.cancelled) {\n\t\t\t\tthis._sessionFile = latest.path;\n\t\t\t\tthis._sessionId = latest.id;\n\t\t\t\tlog(`[BRIDGE] Resumed session ${latest.id.slice(0, 8)}`);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tlog(`[BRIDGE] Failed to resume latest session: ${e}`);\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * List available sessions.\n\t */\n\tasync listSessions(): Promise<RpcSessionInfo[]> {\n\t\tif (!this.client) return [];\n\t\ttry {\n\t\t\treturn await this.client.listSessions();\n\t\t} catch (e) {\n\t\t\tlog(`[BRIDGE] Failed to list sessions: ${e}`);\n\t\t\treturn [];\n\t\t}\n\t}\n\n\t/**\n\t * Switch to a specific session by path.\n\t */\n\tasync switchSession(sessionPath: string): Promise<boolean> {\n\t\tif (!this.client) return false;\n\t\ttry {\n\t\t\tconst result = await this.client.switchSession(sessionPath);\n\t\t\tif (!result.cancelled) {\n\t\t\t\tthis._sessionFile = sessionPath;\n\t\t\t\tconst state = await this.client.getState();\n\t\t\t\tthis._sessionId = state.sessionId;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tlog(`[BRIDGE] Failed to switch session: ${e}`);\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Create a new session.\n\t */\n\tasync newSession(): Promise<boolean> {\n\t\tif (!this.client) return false;\n\t\ttry {\n\t\t\tconst result = await this.client.newSession();\n\t\t\tif (!result.cancelled) {\n\t\t\t\tconst state = await this.client.getState();\n\t\t\t\tthis._sessionFile = state.sessionFile;\n\t\t\t\tthis._sessionId = state.sessionId;\n\t\t\t\tlog(`[BRIDGE] New session ${state.sessionId.slice(0, 8)}`);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tlog(`[BRIDGE] Failed to create new session: ${e}`);\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Send a prompt to the agent.\n\t */\n\tasync prompt(message: string, images?: Array<{ type: \"image\"; data: string; mimeType: string }>): Promise<void> {\n\t\tawait this.ensureAlive();\n\t\ttry {\n\t\t\tawait this.client!.prompt(message, images);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Queue a steering message to interrupt the agent mid-run.\n\t * The agent injects it after the current tool-call batch finishes.\n\t */\n\tasync steer(message: string, images?: Array<{ type: \"image\"; data: string; mimeType: string }>): Promise<void> {\n\t\tawait this.ensureAlive();\n\t\ttry {\n\t\t\tawait this.client!.steer(message, images);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Queue a follow-up message for after the agent finishes its current run.\n\t */\n\tasync followUp(message: string, images?: Array<{ type: \"image\"; data: string; mimeType: string }>): Promise<void> {\n\t\tawait this.ensureAlive();\n\t\ttry {\n\t\t\tawait this.client!.followUp(message, images);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Abort the current operation.\n\t */\n\tasync abort(): Promise<void> {\n\t\tif (!this.client) return;\n\t\ttry {\n\t\t\tawait this.client.abort();\n\t\t} catch {\n\t\t\t// Process may have already exited\n\t\t}\n\t}\n\n\t/**\n\t * Get the dreb version.\n\t */\n\tasync getVersion(): Promise<string> {\n\t\tawait this.ensureAlive();\n\t\ttry {\n\t\t\treturn await this.client!.getVersion();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get session statistics.\n\t */\n\tasync getSessionStats(): Promise<any> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.getSessionStats();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get current state.\n\t */\n\tasync getState(): Promise<any> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.getState();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get available commands (skills, extensions, prompt templates).\n\t */\n\tasync getCommands(): Promise<any[]> {\n\t\tif (!this.client) return [];\n\t\ttry {\n\t\t\treturn await this.client.getCommands();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Compact context.\n\t */\n\tasync compact(): Promise<any> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.compact();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get available models.\n\t */\n\tasync getAvailableModels(): Promise<any[]> {\n\t\tif (!this.client) return [];\n\t\ttry {\n\t\t\treturn await this.client.getAvailableModels();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Set model.\n\t */\n\tasync setModel(provider: string, modelId: string): Promise<any> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.setModel(provider, modelId);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Set thinking level.\n\t */\n\tasync setThinkingLevel(level: string): Promise<void> {\n\t\tif (!this.client) return;\n\t\ttry {\n\t\t\tawait this.client.setThinkingLevel(level as any);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get all messages.\n\t */\n\tasync getMessages(): Promise<any[]> {\n\t\tif (!this.client) return [];\n\t\ttry {\n\t\t\treturn await this.client.getMessages();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Hatch a new buddy companion. Runs inside the agent process\n\t * so API keys never cross the process boundary.\n\t */\n\tasync buddyHatch(): Promise<any> {\n\t\tif (!this.client) throw new Error(\"Agent not connected.\");\n\t\ttry {\n\t\t\treturn await this.client.buddyHatch();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Reroll the buddy companion. Runs inside the agent process\n\t * so API keys never cross the process boundary.\n\t */\n\tasync buddyReroll(): Promise<any> {\n\t\tif (!this.client) throw new Error(\"Agent not connected.\");\n\t\ttry {\n\t\t\treturn await this.client.buddyReroll();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get last assistant text.\n\t */\n\tasync getLastAssistantText(): Promise<string | null> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.getLastAssistantText();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Refresh session info from the RPC process state.\n\t */\n\tasync refreshSessionInfo(): Promise<void> {\n\t\tif (!this.client) return;\n\t\ttry {\n\t\t\tconst state = await this.client.getState();\n\t\t\tthis._sessionFile = state.sessionFile;\n\t\t\tthis._sessionId = state.sessionId;\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\t// Non-critical — don't re-throw\n\t\t}\n\t}\n\n\t/**\n\t * Subscribe to agent events.\n\t */\n\tonEvent(listener: AgentEventListener): () => void {\n\t\tthis.eventListeners.push(listener);\n\t\treturn () => {\n\t\t\tconst idx = this.eventListeners.indexOf(listener);\n\t\t\tif (idx !== -1) this.eventListeners.splice(idx, 1);\n\t\t};\n\t}\n\n\t/**\n\t * Stop the RPC process.\n\t */\n\tasync stop(): Promise<void> {\n\t\tif (this.client) {\n\t\t\ttry {\n\t\t\t\tawait this.client.stop();\n\t\t\t} catch {\n\t\t\t\t// Ignore\n\t\t\t}\n\t\t\tthis.client = null;\n\t\t\tthis.exited = true;\n\t\t\tthis.eventListeners = [];\n\t\t\tlog(\"[BRIDGE] RPC process stopped\");\n\t\t}\n\t}\n\n\t// =========================================================================\n\t// Internal\n\t// =========================================================================\n\n\tprivate handleEvent(event: RpcEvent): void {\n\t\t// Track streaming state\n\t\tif (event.type === \"agent_start\") this._isStreaming = true;\n\t\tif (event.type === \"agent_end\") {\n\t\t\tthis._isStreaming = false;\n\t\t\t// Capture session info from agent_end messages\n\t\t\t// Session file/id updates happen via getState after prompt\n\t\t}\n\n\t\tfor (const listener of this.eventListeners) {\n\t\t\ttry {\n\t\t\t\tlistener(event);\n\t\t\t} catch (e) {\n\t\t\t\tlog(`[BRIDGE] Event listener error: ${e}`);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handleProcessError(e: unknown): void {\n\t\tconst msg = e instanceof Error ? e.message : String(e);\n\t\tif (\n\t\t\tmsg.includes(\"not started\") ||\n\t\t\tmsg.includes(\"not running\") ||\n\t\t\tmsg.includes(\"EPIPE\") ||\n\t\t\tmsg.includes(\"write after end\") ||\n\t\t\tmsg.includes(\"Timeout waiting for response\") ||\n\t\t\tmsg.includes(\"RPC process exited\")\n\t\t) {\n\t\t\tlog(`[BRIDGE] RPC process exited or hung: ${msg.slice(0, 100)}`);\n\t\t\t// Kill the child process before dropping the reference to prevent orphans\n\t\t\tif (this.client) {\n\t\t\t\tthis.client.stop().catch(() => {});\n\t\t\t}\n\t\t\tthis.exited = true;\n\t\t\tthis.client = null;\n\t\t}\n\t}\n\n\tprivate async ensureAlive(): Promise<void> {\n\t\tif (!this.client || this.exited) {\n\t\t\tlog(\"[BRIDGE] Restarting dead RPC process\");\n\t\t\tthis.client = null;\n\t\t\tthis.exited = false;\n\t\t\tawait this.start();\n\t\t\t// Session selection is handled by ensureBridgeWithSession — not here.\n\t\t\t// The previous session is still the latest and will be picked up by\n\t\t\t// resumeLatest() on the next message.\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"agent-bridge.js","sourceRoot":"","sources":["../src/agent-bridge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Agent bridge — manages the RPC connection to a dreb agent process.\n * One bridge per user, handles lifecycle, event subscription, and session management.\n */\n\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { RpcClient, type RpcSessionInfo } from \"@dreb/coding-agent/rpc\";\nimport type { Config } from \"./config.js\";\nimport { log } from \"./util/telegram.js\";\n\n/**\n * Resolve the absolute path to the dreb CLI entry point.\n * RpcClient defaults to \"dist/cli.js\" (relative to cwd), but we need\n * the absolute path since the bot's working dir differs from the dreb repo.\n */\nfunction resolveDrebCliPath(): string {\n\t// import.meta.resolve finds @dreb/coding-agent/dist/index.js\n\tconst resolved = import.meta.resolve(\"@dreb/coding-agent\");\n\tconst distDir = dirname(fileURLToPath(resolved));\n\treturn join(distDir, \"cli.js\");\n}\n\n/** RPC events include both AgentEvent and session-specific events */\ntype RpcEvent = { type: string; [key: string]: any };\n\nexport type AgentEventListener = (event: RpcEvent) => void;\n\nexport class AgentBridge {\n\tprivate client: RpcClient | null = null;\n\tprivate eventListeners: AgentEventListener[] = [];\n\tprivate _isStreaming = false;\n\tprivate _sessionFile: string | undefined;\n\tprivate _sessionId: string | undefined;\n\tprivate exited = false;\n\n\tconstructor(private config: Config) {}\n\n\t/** Whether the RPC process is alive */\n\tget isAlive(): boolean {\n\t\treturn this.client !== null && !this.exited;\n\t}\n\n\t/** Whether the agent is currently streaming a response */\n\tget isStreaming(): boolean {\n\t\treturn this._isStreaming;\n\t}\n\n\t/** Current session file path */\n\tget sessionFile(): string | undefined {\n\t\treturn this._sessionFile;\n\t}\n\n\t/** Current session ID */\n\tget sessionId(): string | undefined {\n\t\treturn this._sessionId;\n\t}\n\n\t/**\n\t * Start the RPC process. Does NOT resume a session — call resumeLatest() or newSession() after.\n\t */\n\tasync start(): Promise<void> {\n\t\tif (this.client) return;\n\n\t\tthis.client = new RpcClient({\n\t\t\tcliPath: resolveDrebCliPath(),\n\t\t\tcwd: this.config.workingDir,\n\t\t\tprovider: this.config.provider,\n\t\t\tmodel: this.config.model,\n\t\t\targs: [\"--ui\", \"telegram\"],\n\t\t});\n\n\t\tthis.exited = false;\n\t\tawait this.client.start();\n\n\t\t// Subscribe to events and forward to listeners\n\t\t// Cast: RpcClient types events as AgentEvent but actually forwards all AgentSessionEvent types\n\t\tthis.client.onEvent((event) => {\n\t\t\tthis.handleEvent(event as RpcEvent);\n\t\t});\n\n\t\t// Detect process exit\n\t\t// RpcClient doesn't expose a direct \"on exit\" — we detect it when send() fails\n\t\tlog(\"[BRIDGE] RPC process started\");\n\t}\n\n\t/**\n\t * Resume the most recent session, or do nothing if no sessions exist.\n\t */\n\tasync resumeLatest(): Promise<boolean> {\n\t\tif (!this.client) return false;\n\t\ttry {\n\t\t\tconst sessions = await this.client.listSessions();\n\t\t\tif (sessions.length === 0) return false;\n\n\t\t\tconst latest = sessions[0]; // Already sorted by modified desc\n\t\t\tconst result = await this.client.switchSession(latest.path);\n\t\t\tif (!result.cancelled) {\n\t\t\t\tthis._sessionFile = latest.path;\n\t\t\t\tthis._sessionId = latest.id;\n\t\t\t\tlog(`[BRIDGE] Resumed session ${latest.id.slice(0, 8)}`);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tlog(`[BRIDGE] Failed to resume latest session: ${e}`);\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * List available sessions.\n\t */\n\tasync listSessions(): Promise<RpcSessionInfo[]> {\n\t\tif (!this.client) return [];\n\t\ttry {\n\t\t\treturn await this.client.listSessions();\n\t\t} catch (e) {\n\t\t\tlog(`[BRIDGE] Failed to list sessions: ${e}`);\n\t\t\treturn [];\n\t\t}\n\t}\n\n\t/**\n\t * Switch to a specific session by path.\n\t */\n\tasync switchSession(sessionPath: string): Promise<boolean> {\n\t\tif (!this.client) return false;\n\t\ttry {\n\t\t\tconst result = await this.client.switchSession(sessionPath);\n\t\t\tif (!result.cancelled) {\n\t\t\t\tthis._sessionFile = sessionPath;\n\t\t\t\tconst state = await this.client.getState();\n\t\t\t\tthis._sessionId = state.sessionId;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tlog(`[BRIDGE] Failed to switch session: ${e}`);\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Create a new session.\n\t */\n\tasync newSession(): Promise<boolean> {\n\t\tif (!this.client) return false;\n\t\ttry {\n\t\t\tconst result = await this.client.newSession();\n\t\t\tif (!result.cancelled) {\n\t\t\t\tconst state = await this.client.getState();\n\t\t\t\tthis._sessionFile = state.sessionFile;\n\t\t\t\tthis._sessionId = state.sessionId;\n\t\t\t\tlog(`[BRIDGE] New session ${state.sessionId.slice(0, 8)}`);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tlog(`[BRIDGE] Failed to create new session: ${e}`);\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Send a prompt to the agent.\n\t */\n\tasync prompt(message: string, images?: Array<{ type: \"image\"; data: string; mimeType: string }>): Promise<void> {\n\t\tawait this.ensureAlive();\n\t\ttry {\n\t\t\tawait this.client!.prompt(message, images);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Queue a steering message to interrupt the agent mid-run.\n\t * The agent injects it after the current tool-call batch finishes.\n\t */\n\tasync steer(message: string, images?: Array<{ type: \"image\"; data: string; mimeType: string }>): Promise<void> {\n\t\tawait this.ensureAlive();\n\t\ttry {\n\t\t\tawait this.client!.steer(message, images);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Queue a follow-up message for after the agent finishes its current run.\n\t */\n\tasync followUp(message: string, images?: Array<{ type: \"image\"; data: string; mimeType: string }>): Promise<void> {\n\t\tawait this.ensureAlive();\n\t\ttry {\n\t\t\tawait this.client!.followUp(message, images);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Abort the current operation.\n\t */\n\tasync abort(): Promise<void> {\n\t\tif (!this.client) return;\n\t\ttry {\n\t\t\tawait this.client.abort();\n\t\t} catch {\n\t\t\t// Process may have already exited\n\t\t}\n\t}\n\n\t/**\n\t * Get the dreb version.\n\t */\n\tasync getVersion(): Promise<string> {\n\t\tawait this.ensureAlive();\n\t\ttry {\n\t\t\treturn await this.client!.getVersion();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get session statistics.\n\t */\n\tasync getSessionStats(): Promise<any> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.getSessionStats();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get current state.\n\t */\n\tasync getState(): Promise<any> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.getState();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get available commands (skills, extensions, prompt templates).\n\t */\n\tasync getCommands(): Promise<any[]> {\n\t\tif (!this.client) return [];\n\t\ttry {\n\t\t\treturn await this.client.getCommands();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Compact context.\n\t */\n\tasync compact(): Promise<any> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.compact();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get available models.\n\t */\n\tasync getAvailableModels(): Promise<any[]> {\n\t\tif (!this.client) return [];\n\t\ttry {\n\t\t\treturn await this.client.getAvailableModels();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Set model.\n\t */\n\tasync setModel(provider: string, modelId: string): Promise<any> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.setModel(provider, modelId);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Resolve a model pattern using the same logic as CLI/TUI.\n\t */\n\tasync resolveModel(pattern: string): Promise<{ model: any; warning?: string } | null> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.resolveModel(pattern);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Set thinking level.\n\t */\n\tasync setThinkingLevel(level: string): Promise<void> {\n\t\tif (!this.client) return;\n\t\ttry {\n\t\t\tawait this.client.setThinkingLevel(level as any);\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get all messages.\n\t */\n\tasync getMessages(): Promise<any[]> {\n\t\tif (!this.client) return [];\n\t\ttry {\n\t\t\treturn await this.client.getMessages();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Hatch a new buddy companion. Runs inside the agent process\n\t * so API keys never cross the process boundary.\n\t */\n\tasync buddyHatch(): Promise<any> {\n\t\tif (!this.client) throw new Error(\"Agent not connected.\");\n\t\ttry {\n\t\t\treturn await this.client.buddyHatch();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Reroll the buddy companion. Runs inside the agent process\n\t * so API keys never cross the process boundary.\n\t */\n\tasync buddyReroll(): Promise<any> {\n\t\tif (!this.client) throw new Error(\"Agent not connected.\");\n\t\ttry {\n\t\t\treturn await this.client.buddyReroll();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Get last assistant text.\n\t */\n\tasync getLastAssistantText(): Promise<string | null> {\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\treturn await this.client.getLastAssistantText();\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Refresh session info from the RPC process state.\n\t */\n\tasync refreshSessionInfo(): Promise<void> {\n\t\tif (!this.client) return;\n\t\ttry {\n\t\t\tconst state = await this.client.getState();\n\t\t\tthis._sessionFile = state.sessionFile;\n\t\t\tthis._sessionId = state.sessionId;\n\t\t} catch (e) {\n\t\t\tthis.handleProcessError(e);\n\t\t\t// Non-critical — don't re-throw\n\t\t}\n\t}\n\n\t/**\n\t * Subscribe to agent events.\n\t */\n\tonEvent(listener: AgentEventListener): () => void {\n\t\tthis.eventListeners.push(listener);\n\t\treturn () => {\n\t\t\tconst idx = this.eventListeners.indexOf(listener);\n\t\t\tif (idx !== -1) this.eventListeners.splice(idx, 1);\n\t\t};\n\t}\n\n\t/**\n\t * Stop the RPC process.\n\t */\n\tasync stop(): Promise<void> {\n\t\tif (this.client) {\n\t\t\ttry {\n\t\t\t\tawait this.client.stop();\n\t\t\t} catch {\n\t\t\t\t// Ignore\n\t\t\t}\n\t\t\tthis.client = null;\n\t\t\tthis.exited = true;\n\t\t\tthis.eventListeners = [];\n\t\t\tlog(\"[BRIDGE] RPC process stopped\");\n\t\t}\n\t}\n\n\t// =========================================================================\n\t// Internal\n\t// =========================================================================\n\n\tprivate handleEvent(event: RpcEvent): void {\n\t\t// Track streaming state\n\t\tif (event.type === \"agent_start\") this._isStreaming = true;\n\t\tif (event.type === \"agent_end\") {\n\t\t\tthis._isStreaming = false;\n\t\t\t// Capture session info from agent_end messages\n\t\t\t// Session file/id updates happen via getState after prompt\n\t\t}\n\n\t\tfor (const listener of this.eventListeners) {\n\t\t\ttry {\n\t\t\t\tlistener(event);\n\t\t\t} catch (e) {\n\t\t\t\tlog(`[BRIDGE] Event listener error: ${e}`);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handleProcessError(e: unknown): void {\n\t\tconst msg = e instanceof Error ? e.message : String(e);\n\t\tif (\n\t\t\tmsg.includes(\"not started\") ||\n\t\t\tmsg.includes(\"not running\") ||\n\t\t\tmsg.includes(\"EPIPE\") ||\n\t\t\tmsg.includes(\"write after end\") ||\n\t\t\tmsg.includes(\"Timeout waiting for response\") ||\n\t\t\tmsg.includes(\"RPC process exited\")\n\t\t) {\n\t\t\tlog(`[BRIDGE] RPC process exited or hung: ${msg.slice(0, 100)}`);\n\t\t\t// Kill the child process before dropping the reference to prevent orphans\n\t\t\tif (this.client) {\n\t\t\t\tthis.client.stop().catch(() => {});\n\t\t\t}\n\t\t\tthis.exited = true;\n\t\t\tthis.client = null;\n\t\t}\n\t}\n\n\tprivate async ensureAlive(): Promise<void> {\n\t\tif (!this.client || this.exited) {\n\t\t\tlog(\"[BRIDGE] Restarting dead RPC process\");\n\t\t\tthis.client = null;\n\t\t\tthis.exited = false;\n\t\t\tawait this.start();\n\t\t\t// Session selection is handled by ensureBridgeWithSession — not here.\n\t\t\t// The previous session is still the latest and will be picked up by\n\t\t\t// resumeLatest() on the next message.\n\t\t}\n\t}\n}\n"]}
package/dist/bot.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../src/bot.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAE7B,OAAO,EAAoB,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAG1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAqB5C,iBAAS,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAQhE;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAiF7C;AAED,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC","sourcesContent":["/**\n * Bot setup — creates the grammy Bot, wires up auth, commands, and message handlers.\n */\n\nimport { Bot } from \"grammy\";\nimport { ensureBridgeWithSession } from \"./bridge-lifecycle.js\";\nimport { registerCommands, setMyCommands } from \"./commands/index.js\";\nimport type { Config } from \"./config.js\";\nimport { handleFile } from \"./handlers/file.js\";\nimport { sendPrompt } from \"./handlers/message.js\";\nimport type { UserState } from \"./types.js\";\nimport { log, safeDelete, safeSend } from \"./util/telegram.js\";\n\n/** Per-user state store */\nconst userStates = new Map<number, UserState>();\n\nfunction createUserState(config: Config): UserState {\n\treturn {\n\t\tbridge: null,\n\t\tconfig,\n\t\tpromptInFlight: false,\n\t\tnewSessionFlag: false,\n\t\tnewSessionCwd: null,\n\t\teffectiveCwd: null,\n\t\tbackgroundAgents: new Map(),\n\t\tstopRequested: false,\n\t\toutbox: [],\n\t\tbuddyController: null,\n\t};\n}\n\nfunction getUserState(userId: number, config?: Config): UserState {\n\tlet state = userStates.get(userId);\n\tif (!state) {\n\t\tif (!config) throw new Error(\"Config required for new user state creation\");\n\t\tstate = createUserState(config);\n\t\tuserStates.set(userId, state);\n\t}\n\treturn state;\n}\n\nexport function createBot(config: Config): Bot {\n\tconst bot = new Bot(config.botToken);\n\n\t// Auth middleware — check allowed user IDs\n\tif (config.allowedUserIds.length > 0) {\n\t\tbot.use(async (ctx, next) => {\n\t\t\tconst userId = ctx.from?.id;\n\t\t\tif (!userId || !config.allowedUserIds.includes(userId)) {\n\t\t\t\tlog(`[AUTH] Rejected user ${userId}`);\n\t\t\t\tawait ctx.reply(\"⛔ Not authorized\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait next();\n\t\t});\n\t}\n\n\t// Register slash commands — bind config so callers see (userId) => UserState\n\tconst boundGetUserState = (userId: number) => getUserState(userId, config);\n\tregisterCommands(bot, config, boundGetUserState);\n\n\t// Text message handler\n\tbot.on(\"message:text\", async (ctx) => {\n\t\t// Skip commands (already handled above)\n\t\tif (ctx.message.text.startsWith(\"/\")) return;\n\n\t\tconst userId = ctx.from!.id;\n\t\tconst userState = getUserState(userId, config);\n\t\tconst isBusy = userState.bridge?.isStreaming || userState.promptInFlight;\n\n\t\t// Show status immediately\n\t\tconst statusText = isBusy ? \"↩️ _Steering..._\" : \"🧠 _Thinking..._\";\n\t\tlet statusMessageId: number | null = null;\n\t\ttry {\n\t\t\tconst sent = await ctx.reply(statusText, { parse_mode: \"Markdown\" });\n\t\t\tstatusMessageId = sent.message_id;\n\t\t} catch (e) {\n\t\t\tlog(`[MSG] Failed to send status: ${e}`);\n\t\t}\n\n\t\t// Ensure bridge is alive and session is set up\n\t\ttry {\n\t\t\tawait ensureBridgeWithSession(config, userState);\n\t\t} catch (e) {\n\t\t\tlog(`[MSG] Bridge setup failed: ${e}`);\n\t\t\tif (statusMessageId) void safeDelete(ctx.api, ctx.chat!.id, statusMessageId);\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ Failed to start agent: ${e}`);\n\t\t\treturn;\n\t\t}\n\n\t\tsendPrompt(ctx.api, userState, {\n\t\t\tchatId: ctx.chat!.id,\n\t\t\treplyToId: ctx.message.message_id,\n\t\t\tuserId,\n\t\t\tprompt: ctx.message.text,\n\t\t\tstatusMessageId,\n\t\t});\n\t});\n\n\t// File handler (documents, photos, voice, audio, video)\n\tbot.on([\"message:document\", \"message:photo\", \"message:voice\", \"message:audio\", \"message:video\"], async (ctx) => {\n\t\tconst userId = ctx.from!.id;\n\t\tconst userState = getUserState(userId, config);\n\n\t\t// Ensure bridge is alive\n\t\ttry {\n\t\t\tawait ensureBridgeWithSession(config, userState);\n\t\t} catch (e) {\n\t\t\tlog(`[FILE] Bridge setup failed: ${e}`);\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ Failed to start agent: ${e}`);\n\t\t\treturn;\n\t\t}\n\n\t\tawait handleFile(ctx, ctx.api, boundGetUserState);\n\t});\n\n\t// Error handler\n\tbot.catch((err) => {\n\t\tlog(`[ERROR] ${err.error}`);\n\t});\n\n\treturn bot;\n}\n\nexport { getUserState, setMyCommands };\n"]}
1
+ {"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../src/bot.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAE7B,OAAO,EAAoB,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAG1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AA6B5C,iBAAS,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAQhE;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAqF7C;AAED,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC","sourcesContent":["/**\n * Bot setup — creates the grammy Bot, wires up auth, commands, and message handlers.\n */\n\nimport { Bot } from \"grammy\";\nimport { ensureBridgeWithSession } from \"./bridge-lifecycle.js\";\nimport { registerCommands, setMyCommands } from \"./commands/index.js\";\nimport type { Config } from \"./config.js\";\nimport { handleFile } from \"./handlers/file.js\";\nimport { sendPrompt } from \"./handlers/message.js\";\nimport type { UserState } from \"./types.js\";\nimport { log, safeDelete, safeSend } from \"./util/telegram.js\";\n\n/** Flush any pending model fallback warning to the user (shown once after bridge restart) */\nasync function flushPendingWarning(api: Bot[\"api\"], chatId: number, userState: UserState): Promise<void> {\n\tif (userState.pendingModelFallbackWarning) {\n\t\tawait safeSend(api, chatId, `⚠️ _${userState.pendingModelFallbackWarning}_`);\n\t\tuserState.pendingModelFallbackWarning = undefined;\n\t}\n}\n\n/** Per-user state store */\nconst userStates = new Map<number, UserState>();\n\nfunction createUserState(config: Config): UserState {\n\treturn {\n\t\tbridge: null,\n\t\tconfig,\n\t\tpromptInFlight: false,\n\t\tnewSessionFlag: false,\n\t\tnewSessionCwd: null,\n\t\teffectiveCwd: null,\n\t\tbackgroundAgents: new Map(),\n\t\tstopRequested: false,\n\t\toutbox: [],\n\t\tbuddyController: null,\n\t};\n}\n\nfunction getUserState(userId: number, config?: Config): UserState {\n\tlet state = userStates.get(userId);\n\tif (!state) {\n\t\tif (!config) throw new Error(\"Config required for new user state creation\");\n\t\tstate = createUserState(config);\n\t\tuserStates.set(userId, state);\n\t}\n\treturn state;\n}\n\nexport function createBot(config: Config): Bot {\n\tconst bot = new Bot(config.botToken);\n\n\t// Auth middleware — check allowed user IDs\n\tif (config.allowedUserIds.length > 0) {\n\t\tbot.use(async (ctx, next) => {\n\t\t\tconst userId = ctx.from?.id;\n\t\t\tif (!userId || !config.allowedUserIds.includes(userId)) {\n\t\t\t\tlog(`[AUTH] Rejected user ${userId}`);\n\t\t\t\tawait ctx.reply(\"⛔ Not authorized\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait next();\n\t\t});\n\t}\n\n\t// Register slash commands — bind config so callers see (userId) => UserState\n\tconst boundGetUserState = (userId: number) => getUserState(userId, config);\n\tregisterCommands(bot, config, boundGetUserState);\n\n\t// Text message handler\n\tbot.on(\"message:text\", async (ctx) => {\n\t\t// Skip commands (already handled above)\n\t\tif (ctx.message.text.startsWith(\"/\")) return;\n\n\t\tconst userId = ctx.from!.id;\n\t\tconst userState = getUserState(userId, config);\n\t\tconst isBusy = userState.bridge?.isStreaming || userState.promptInFlight;\n\n\t\t// Show status immediately\n\t\tconst statusText = isBusy ? \"↩️ _Steering..._\" : \"🧠 _Thinking..._\";\n\t\tlet statusMessageId: number | null = null;\n\t\ttry {\n\t\t\tconst sent = await ctx.reply(statusText, { parse_mode: \"Markdown\" });\n\t\t\tstatusMessageId = sent.message_id;\n\t\t} catch (e) {\n\t\t\tlog(`[MSG] Failed to send status: ${e}`);\n\t\t}\n\n\t\t// Ensure bridge is alive and session is set up\n\t\ttry {\n\t\t\tawait ensureBridgeWithSession(config, userState);\n\t\t} catch (e) {\n\t\t\tlog(`[MSG] Bridge setup failed: ${e}`);\n\t\t\tif (statusMessageId) void safeDelete(ctx.api, ctx.chat!.id, statusMessageId);\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ Failed to start agent: ${e}`);\n\t\t\treturn;\n\t\t}\n\n\t\tawait flushPendingWarning(ctx.api, ctx.chat!.id, userState);\n\n\t\tsendPrompt(ctx.api, userState, {\n\t\t\tchatId: ctx.chat!.id,\n\t\t\treplyToId: ctx.message.message_id,\n\t\t\tuserId,\n\t\t\tprompt: ctx.message.text,\n\t\t\tstatusMessageId,\n\t\t});\n\t});\n\n\t// File handler (documents, photos, voice, audio, video)\n\tbot.on([\"message:document\", \"message:photo\", \"message:voice\", \"message:audio\", \"message:video\"], async (ctx) => {\n\t\tconst userId = ctx.from!.id;\n\t\tconst userState = getUserState(userId, config);\n\n\t\t// Ensure bridge is alive\n\t\ttry {\n\t\t\tawait ensureBridgeWithSession(config, userState);\n\t\t} catch (e) {\n\t\t\tlog(`[FILE] Bridge setup failed: ${e}`);\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ Failed to start agent: ${e}`);\n\t\t\treturn;\n\t\t}\n\n\t\tawait flushPendingWarning(ctx.api, ctx.chat!.id, userState);\n\n\t\tawait handleFile(ctx, ctx.api, boundGetUserState);\n\t});\n\n\t// Error handler\n\tbot.catch((err) => {\n\t\tlog(`[ERROR] ${err.error}`);\n\t});\n\n\treturn bot;\n}\n\nexport { getUserState, setMyCommands };\n"]}
package/dist/bot.js CHANGED
@@ -7,6 +7,13 @@ import { registerCommands, setMyCommands } from "./commands/index.js";
7
7
  import { handleFile } from "./handlers/file.js";
8
8
  import { sendPrompt } from "./handlers/message.js";
9
9
  import { log, safeDelete, safeSend } from "./util/telegram.js";
10
+ /** Flush any pending model fallback warning to the user (shown once after bridge restart) */
11
+ async function flushPendingWarning(api, chatId, userState) {
12
+ if (userState.pendingModelFallbackWarning) {
13
+ await safeSend(api, chatId, `⚠️ _${userState.pendingModelFallbackWarning}_`);
14
+ userState.pendingModelFallbackWarning = undefined;
15
+ }
16
+ }
10
17
  /** Per-user state store */
11
18
  const userStates = new Map();
12
19
  function createUserState(config) {
@@ -79,6 +86,7 @@ export function createBot(config) {
79
86
  await safeSend(ctx.api, ctx.chat.id, `❌ Failed to start agent: ${e}`);
80
87
  return;
81
88
  }
89
+ await flushPendingWarning(ctx.api, ctx.chat.id, userState);
82
90
  sendPrompt(ctx.api, userState, {
83
91
  chatId: ctx.chat.id,
84
92
  replyToId: ctx.message.message_id,
@@ -100,6 +108,7 @@ export function createBot(config) {
100
108
  await safeSend(ctx.api, ctx.chat.id, `❌ Failed to start agent: ${e}`);
101
109
  return;
102
110
  }
111
+ await flushPendingWarning(ctx.api, ctx.chat.id, userState);
103
112
  await handleFile(ctx, ctx.api, boundGetUserState);
104
113
  });
105
114
  // Error handler
package/dist/bot.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bot.js","sourceRoot":"","sources":["../src/bot.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bot setup — creates the grammy Bot, wires up auth, commands, and message handlers.\n */\n\nimport { Bot } from \"grammy\";\nimport { ensureBridgeWithSession } from \"./bridge-lifecycle.js\";\nimport { registerCommands, setMyCommands } from \"./commands/index.js\";\nimport type { Config } from \"./config.js\";\nimport { handleFile } from \"./handlers/file.js\";\nimport { sendPrompt } from \"./handlers/message.js\";\nimport type { UserState } from \"./types.js\";\nimport { log, safeDelete, safeSend } from \"./util/telegram.js\";\n\n/** Per-user state store */\nconst userStates = new Map<number, UserState>();\n\nfunction createUserState(config: Config): UserState {\n\treturn {\n\t\tbridge: null,\n\t\tconfig,\n\t\tpromptInFlight: false,\n\t\tnewSessionFlag: false,\n\t\tnewSessionCwd: null,\n\t\teffectiveCwd: null,\n\t\tbackgroundAgents: new Map(),\n\t\tstopRequested: false,\n\t\toutbox: [],\n\t\tbuddyController: null,\n\t};\n}\n\nfunction getUserState(userId: number, config?: Config): UserState {\n\tlet state = userStates.get(userId);\n\tif (!state) {\n\t\tif (!config) throw new Error(\"Config required for new user state creation\");\n\t\tstate = createUserState(config);\n\t\tuserStates.set(userId, state);\n\t}\n\treturn state;\n}\n\nexport function createBot(config: Config): Bot {\n\tconst bot = new Bot(config.botToken);\n\n\t// Auth middleware — check allowed user IDs\n\tif (config.allowedUserIds.length > 0) {\n\t\tbot.use(async (ctx, next) => {\n\t\t\tconst userId = ctx.from?.id;\n\t\t\tif (!userId || !config.allowedUserIds.includes(userId)) {\n\t\t\t\tlog(`[AUTH] Rejected user ${userId}`);\n\t\t\t\tawait ctx.reply(\"⛔ Not authorized\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait next();\n\t\t});\n\t}\n\n\t// Register slash commands — bind config so callers see (userId) => UserState\n\tconst boundGetUserState = (userId: number) => getUserState(userId, config);\n\tregisterCommands(bot, config, boundGetUserState);\n\n\t// Text message handler\n\tbot.on(\"message:text\", async (ctx) => {\n\t\t// Skip commands (already handled above)\n\t\tif (ctx.message.text.startsWith(\"/\")) return;\n\n\t\tconst userId = ctx.from!.id;\n\t\tconst userState = getUserState(userId, config);\n\t\tconst isBusy = userState.bridge?.isStreaming || userState.promptInFlight;\n\n\t\t// Show status immediately\n\t\tconst statusText = isBusy ? \"↩️ _Steering..._\" : \"🧠 _Thinking..._\";\n\t\tlet statusMessageId: number | null = null;\n\t\ttry {\n\t\t\tconst sent = await ctx.reply(statusText, { parse_mode: \"Markdown\" });\n\t\t\tstatusMessageId = sent.message_id;\n\t\t} catch (e) {\n\t\t\tlog(`[MSG] Failed to send status: ${e}`);\n\t\t}\n\n\t\t// Ensure bridge is alive and session is set up\n\t\ttry {\n\t\t\tawait ensureBridgeWithSession(config, userState);\n\t\t} catch (e) {\n\t\t\tlog(`[MSG] Bridge setup failed: ${e}`);\n\t\t\tif (statusMessageId) void safeDelete(ctx.api, ctx.chat!.id, statusMessageId);\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ Failed to start agent: ${e}`);\n\t\t\treturn;\n\t\t}\n\n\t\tsendPrompt(ctx.api, userState, {\n\t\t\tchatId: ctx.chat!.id,\n\t\t\treplyToId: ctx.message.message_id,\n\t\t\tuserId,\n\t\t\tprompt: ctx.message.text,\n\t\t\tstatusMessageId,\n\t\t});\n\t});\n\n\t// File handler (documents, photos, voice, audio, video)\n\tbot.on([\"message:document\", \"message:photo\", \"message:voice\", \"message:audio\", \"message:video\"], async (ctx) => {\n\t\tconst userId = ctx.from!.id;\n\t\tconst userState = getUserState(userId, config);\n\n\t\t// Ensure bridge is alive\n\t\ttry {\n\t\t\tawait ensureBridgeWithSession(config, userState);\n\t\t} catch (e) {\n\t\t\tlog(`[FILE] Bridge setup failed: ${e}`);\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ Failed to start agent: ${e}`);\n\t\t\treturn;\n\t\t}\n\n\t\tawait handleFile(ctx, ctx.api, boundGetUserState);\n\t});\n\n\t// Error handler\n\tbot.catch((err) => {\n\t\tlog(`[ERROR] ${err.error}`);\n\t});\n\n\treturn bot;\n}\n\nexport { getUserState, setMyCommands };\n"]}
1
+ {"version":3,"file":"bot.js","sourceRoot":"","sources":["../src/bot.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bot setup — creates the grammy Bot, wires up auth, commands, and message handlers.\n */\n\nimport { Bot } from \"grammy\";\nimport { ensureBridgeWithSession } from \"./bridge-lifecycle.js\";\nimport { registerCommands, setMyCommands } from \"./commands/index.js\";\nimport type { Config } from \"./config.js\";\nimport { handleFile } from \"./handlers/file.js\";\nimport { sendPrompt } from \"./handlers/message.js\";\nimport type { UserState } from \"./types.js\";\nimport { log, safeDelete, safeSend } from \"./util/telegram.js\";\n\n/** Flush any pending model fallback warning to the user (shown once after bridge restart) */\nasync function flushPendingWarning(api: Bot[\"api\"], chatId: number, userState: UserState): Promise<void> {\n\tif (userState.pendingModelFallbackWarning) {\n\t\tawait safeSend(api, chatId, `⚠️ _${userState.pendingModelFallbackWarning}_`);\n\t\tuserState.pendingModelFallbackWarning = undefined;\n\t}\n}\n\n/** Per-user state store */\nconst userStates = new Map<number, UserState>();\n\nfunction createUserState(config: Config): UserState {\n\treturn {\n\t\tbridge: null,\n\t\tconfig,\n\t\tpromptInFlight: false,\n\t\tnewSessionFlag: false,\n\t\tnewSessionCwd: null,\n\t\teffectiveCwd: null,\n\t\tbackgroundAgents: new Map(),\n\t\tstopRequested: false,\n\t\toutbox: [],\n\t\tbuddyController: null,\n\t};\n}\n\nfunction getUserState(userId: number, config?: Config): UserState {\n\tlet state = userStates.get(userId);\n\tif (!state) {\n\t\tif (!config) throw new Error(\"Config required for new user state creation\");\n\t\tstate = createUserState(config);\n\t\tuserStates.set(userId, state);\n\t}\n\treturn state;\n}\n\nexport function createBot(config: Config): Bot {\n\tconst bot = new Bot(config.botToken);\n\n\t// Auth middleware — check allowed user IDs\n\tif (config.allowedUserIds.length > 0) {\n\t\tbot.use(async (ctx, next) => {\n\t\t\tconst userId = ctx.from?.id;\n\t\t\tif (!userId || !config.allowedUserIds.includes(userId)) {\n\t\t\t\tlog(`[AUTH] Rejected user ${userId}`);\n\t\t\t\tawait ctx.reply(\"⛔ Not authorized\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait next();\n\t\t});\n\t}\n\n\t// Register slash commands — bind config so callers see (userId) => UserState\n\tconst boundGetUserState = (userId: number) => getUserState(userId, config);\n\tregisterCommands(bot, config, boundGetUserState);\n\n\t// Text message handler\n\tbot.on(\"message:text\", async (ctx) => {\n\t\t// Skip commands (already handled above)\n\t\tif (ctx.message.text.startsWith(\"/\")) return;\n\n\t\tconst userId = ctx.from!.id;\n\t\tconst userState = getUserState(userId, config);\n\t\tconst isBusy = userState.bridge?.isStreaming || userState.promptInFlight;\n\n\t\t// Show status immediately\n\t\tconst statusText = isBusy ? \"↩️ _Steering..._\" : \"🧠 _Thinking..._\";\n\t\tlet statusMessageId: number | null = null;\n\t\ttry {\n\t\t\tconst sent = await ctx.reply(statusText, { parse_mode: \"Markdown\" });\n\t\t\tstatusMessageId = sent.message_id;\n\t\t} catch (e) {\n\t\t\tlog(`[MSG] Failed to send status: ${e}`);\n\t\t}\n\n\t\t// Ensure bridge is alive and session is set up\n\t\ttry {\n\t\t\tawait ensureBridgeWithSession(config, userState);\n\t\t} catch (e) {\n\t\t\tlog(`[MSG] Bridge setup failed: ${e}`);\n\t\t\tif (statusMessageId) void safeDelete(ctx.api, ctx.chat!.id, statusMessageId);\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ Failed to start agent: ${e}`);\n\t\t\treturn;\n\t\t}\n\n\t\tawait flushPendingWarning(ctx.api, ctx.chat!.id, userState);\n\n\t\tsendPrompt(ctx.api, userState, {\n\t\t\tchatId: ctx.chat!.id,\n\t\t\treplyToId: ctx.message.message_id,\n\t\t\tuserId,\n\t\t\tprompt: ctx.message.text,\n\t\t\tstatusMessageId,\n\t\t});\n\t});\n\n\t// File handler (documents, photos, voice, audio, video)\n\tbot.on([\"message:document\", \"message:photo\", \"message:voice\", \"message:audio\", \"message:video\"], async (ctx) => {\n\t\tconst userId = ctx.from!.id;\n\t\tconst userState = getUserState(userId, config);\n\n\t\t// Ensure bridge is alive\n\t\ttry {\n\t\t\tawait ensureBridgeWithSession(config, userState);\n\t\t} catch (e) {\n\t\t\tlog(`[FILE] Bridge setup failed: ${e}`);\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ Failed to start agent: ${e}`);\n\t\t\treturn;\n\t\t}\n\n\t\tawait flushPendingWarning(ctx.api, ctx.chat!.id, userState);\n\n\t\tawait handleFile(ctx, ctx.api, boundGetUserState);\n\t});\n\n\t// Error handler\n\tbot.catch((err) => {\n\t\tlog(`[ERROR] ${err.error}`);\n\t});\n\n\treturn bot;\n}\n\nexport { getUserState, setMyCommands };\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-lifecycle.d.ts","sourceRoot":"","sources":["../src/bridge-lifecycle.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;;GAGG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CA2B7F;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAqCxG","sourcesContent":["/**\n * Bridge lifecycle helpers — extracted to avoid circular imports between bot.ts and commands.\n */\n\nimport { AgentBridge } from \"./agent-bridge.js\";\nimport type { Config } from \"./config.js\";\nimport type { UserState } from \"./types.js\";\n\n/**\n * Ensure the user has an active agent bridge, starting one if needed.\n * Does NOT handle session selection — that's up to the caller.\n */\nexport async function ensureBridge(config: Config, userState: UserState): Promise<AgentBridge> {\n\tif (!userState.bridge || !userState.bridge.isAlive) {\n\t\t// Use effectiveCwd if set — preserves custom cwd across bridge crashes\n\t\tconst effectiveConfig =\n\t\t\tuserState.effectiveCwd && userState.effectiveCwd !== config.workingDir\n\t\t\t\t? { ...config, workingDir: userState.effectiveCwd }\n\t\t\t\t: config;\n\t\tconst bridge = new AgentBridge(effectiveConfig);\n\t\tawait bridge.start();\n\t\tuserState.bridge = bridge;\n\n\t\t// Wire up background agent tracking\n\t\tbridge.onEvent((event: any) => {\n\t\t\tif (event.type === \"background_agent_start\") {\n\t\t\t\tuserState.backgroundAgents.set(event.agentId, {\n\t\t\t\t\tagentId: event.agentId,\n\t\t\t\t\tagentType: event.agentType,\n\t\t\t\t\ttaskSummary: event.taskSummary,\n\t\t\t\t\tstartTime: Date.now(),\n\t\t\t\t});\n\t\t\t} else if (event.type === \"background_agent_end\") {\n\t\t\t\tuserState.backgroundAgents.delete(event.agentId);\n\t\t\t}\n\t\t});\n\t}\n\n\treturn userState.bridge;\n}\n\n/**\n * Ensure bridge is alive AND a session is selected.\n * Used by message/file handlers and skill commands before prompting.\n */\nexport async function ensureBridgeWithSession(config: Config, userState: UserState): Promise<AgentBridge> {\n\t// Handle new session — always kill and recreate the bridge for clean state.\n\t// For /new <path>: uses the user-specified directory.\n\t// For /new (bare): preserves the current effectiveCwd.\n\tif (userState.newSessionFlag) {\n\t\tconst cwd = userState.newSessionCwd ?? userState.effectiveCwd ?? config.workingDir;\n\t\tuserState.newSessionFlag = false;\n\t\tuserState.newSessionCwd = null;\n\n\t\t// Kill existing bridge and start a new one with the resolved cwd\n\t\tif (userState.bridge?.isAlive) {\n\t\t\tawait userState.bridge.stop();\n\t\t}\n\t\tuserState.bridge = null;\n\n\t\tconst customConfig = { ...config, workingDir: cwd };\n\t\t// Set effectiveCwd BEFORE ensureBridge so the stale-cwd override\n\t\t// in ensureBridge doesn't clobber the resolved directory\n\t\tuserState.effectiveCwd = cwd;\n\t\tconst bridge = await ensureBridge(customConfig, userState);\n\t\tawait bridge.newSession();\n\t\treturn bridge;\n\t}\n\n\tconst bridge = await ensureBridge(config, userState);\n\n\t// Track effective cwd (default from config on first bridge creation)\n\tif (!userState.effectiveCwd) {\n\t\tuserState.effectiveCwd = config.workingDir;\n\t}\n\n\t// No session yet — try to resume latest\n\tif (!bridge.sessionId) {\n\t\tawait bridge.resumeLatest();\n\t}\n\n\treturn bridge;\n}\n"]}
1
+ {"version":3,"file":"bridge-lifecycle.d.ts","sourceRoot":"","sources":["../src/bridge-lifecycle.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;;GAGG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CA2B7F;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAmDxG","sourcesContent":["/**\n * Bridge lifecycle helpers — extracted to avoid circular imports between bot.ts and commands.\n */\n\nimport { AgentBridge } from \"./agent-bridge.js\";\nimport type { Config } from \"./config.js\";\nimport type { UserState } from \"./types.js\";\n\n/**\n * Ensure the user has an active agent bridge, starting one if needed.\n * Does NOT handle session selection — that's up to the caller.\n */\nexport async function ensureBridge(config: Config, userState: UserState): Promise<AgentBridge> {\n\tif (!userState.bridge || !userState.bridge.isAlive) {\n\t\t// Use effectiveCwd if set — preserves custom cwd across bridge crashes\n\t\tconst effectiveConfig =\n\t\t\tuserState.effectiveCwd && userState.effectiveCwd !== config.workingDir\n\t\t\t\t? { ...config, workingDir: userState.effectiveCwd }\n\t\t\t\t: config;\n\t\tconst bridge = new AgentBridge(effectiveConfig);\n\t\tawait bridge.start();\n\t\tuserState.bridge = bridge;\n\n\t\t// Wire up background agent tracking\n\t\tbridge.onEvent((event: any) => {\n\t\t\tif (event.type === \"background_agent_start\") {\n\t\t\t\tuserState.backgroundAgents.set(event.agentId, {\n\t\t\t\t\tagentId: event.agentId,\n\t\t\t\t\tagentType: event.agentType,\n\t\t\t\t\ttaskSummary: event.taskSummary,\n\t\t\t\t\tstartTime: Date.now(),\n\t\t\t\t});\n\t\t\t} else if (event.type === \"background_agent_end\") {\n\t\t\t\tuserState.backgroundAgents.delete(event.agentId);\n\t\t\t}\n\t\t});\n\t}\n\n\treturn userState.bridge;\n}\n\n/**\n * Ensure bridge is alive AND a session is selected.\n * Used by message/file handlers and skill commands before prompting.\n */\nexport async function ensureBridgeWithSession(config: Config, userState: UserState): Promise<AgentBridge> {\n\t// Handle new session — always kill and recreate the bridge for clean state.\n\t// For /new <path>: uses the user-specified directory.\n\t// For /new (bare): preserves the current effectiveCwd.\n\tif (userState.newSessionFlag) {\n\t\tconst cwd = userState.newSessionCwd ?? userState.effectiveCwd ?? config.workingDir;\n\t\tuserState.newSessionFlag = false;\n\t\tuserState.newSessionCwd = null;\n\n\t\t// Kill existing bridge and start a new one with the resolved cwd\n\t\tif (userState.bridge?.isAlive) {\n\t\t\tawait userState.bridge.stop();\n\t\t}\n\t\tuserState.bridge = null;\n\n\t\tconst customConfig = { ...config, workingDir: cwd };\n\t\t// Set effectiveCwd BEFORE ensureBridge so the stale-cwd override\n\t\t// in ensureBridge doesn't clobber the resolved directory\n\t\tuserState.effectiveCwd = cwd;\n\t\tconst bridge = await ensureBridge(customConfig, userState);\n\t\tawait bridge.newSession();\n\t\treturn bridge;\n\t}\n\n\tconst hadBridge = !!userState.bridge?.isAlive;\n\tconst bridge = await ensureBridge(config, userState);\n\tconst freshBridge = !hadBridge;\n\n\t// Track effective cwd (default from config on first bridge creation)\n\tif (!userState.effectiveCwd) {\n\t\tuserState.effectiveCwd = config.workingDir;\n\t}\n\n\t// No session yet — try to resume latest\n\tif (!bridge.sessionId) {\n\t\tawait bridge.resumeLatest();\n\t}\n\n\t// Check for model fallback warning on fresh bridge (e.g. after crash)\n\tif (freshBridge) {\n\t\ttry {\n\t\t\tconst state = await bridge.getState();\n\t\t\tif (state?.modelFallbackMessage) {\n\t\t\t\tuserState.pendingModelFallbackWarning = state.modelFallbackMessage;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Non-critical — the warning is best-effort\n\t\t}\n\t}\n\n\treturn bridge;\n}\n"]}
@@ -57,7 +57,9 @@ export async function ensureBridgeWithSession(config, userState) {
57
57
  await bridge.newSession();
58
58
  return bridge;
59
59
  }
60
+ const hadBridge = !!userState.bridge?.isAlive;
60
61
  const bridge = await ensureBridge(config, userState);
62
+ const freshBridge = !hadBridge;
61
63
  // Track effective cwd (default from config on first bridge creation)
62
64
  if (!userState.effectiveCwd) {
63
65
  userState.effectiveCwd = config.workingDir;
@@ -66,6 +68,18 @@ export async function ensureBridgeWithSession(config, userState) {
66
68
  if (!bridge.sessionId) {
67
69
  await bridge.resumeLatest();
68
70
  }
71
+ // Check for model fallback warning on fresh bridge (e.g. after crash)
72
+ if (freshBridge) {
73
+ try {
74
+ const state = await bridge.getState();
75
+ if (state?.modelFallbackMessage) {
76
+ userState.pendingModelFallbackWarning = state.modelFallbackMessage;
77
+ }
78
+ }
79
+ catch {
80
+ // Non-critical — the warning is best-effort
81
+ }
82
+ }
69
83
  return bridge;
70
84
  }
71
85
  //# sourceMappingURL=bridge-lifecycle.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-lifecycle.js","sourceRoot":"","sources":["../src/bridge-lifecycle.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bridge lifecycle helpers — extracted to avoid circular imports between bot.ts and commands.\n */\n\nimport { AgentBridge } from \"./agent-bridge.js\";\nimport type { Config } from \"./config.js\";\nimport type { UserState } from \"./types.js\";\n\n/**\n * Ensure the user has an active agent bridge, starting one if needed.\n * Does NOT handle session selection — that's up to the caller.\n */\nexport async function ensureBridge(config: Config, userState: UserState): Promise<AgentBridge> {\n\tif (!userState.bridge || !userState.bridge.isAlive) {\n\t\t// Use effectiveCwd if set — preserves custom cwd across bridge crashes\n\t\tconst effectiveConfig =\n\t\t\tuserState.effectiveCwd && userState.effectiveCwd !== config.workingDir\n\t\t\t\t? { ...config, workingDir: userState.effectiveCwd }\n\t\t\t\t: config;\n\t\tconst bridge = new AgentBridge(effectiveConfig);\n\t\tawait bridge.start();\n\t\tuserState.bridge = bridge;\n\n\t\t// Wire up background agent tracking\n\t\tbridge.onEvent((event: any) => {\n\t\t\tif (event.type === \"background_agent_start\") {\n\t\t\t\tuserState.backgroundAgents.set(event.agentId, {\n\t\t\t\t\tagentId: event.agentId,\n\t\t\t\t\tagentType: event.agentType,\n\t\t\t\t\ttaskSummary: event.taskSummary,\n\t\t\t\t\tstartTime: Date.now(),\n\t\t\t\t});\n\t\t\t} else if (event.type === \"background_agent_end\") {\n\t\t\t\tuserState.backgroundAgents.delete(event.agentId);\n\t\t\t}\n\t\t});\n\t}\n\n\treturn userState.bridge;\n}\n\n/**\n * Ensure bridge is alive AND a session is selected.\n * Used by message/file handlers and skill commands before prompting.\n */\nexport async function ensureBridgeWithSession(config: Config, userState: UserState): Promise<AgentBridge> {\n\t// Handle new session — always kill and recreate the bridge for clean state.\n\t// For /new <path>: uses the user-specified directory.\n\t// For /new (bare): preserves the current effectiveCwd.\n\tif (userState.newSessionFlag) {\n\t\tconst cwd = userState.newSessionCwd ?? userState.effectiveCwd ?? config.workingDir;\n\t\tuserState.newSessionFlag = false;\n\t\tuserState.newSessionCwd = null;\n\n\t\t// Kill existing bridge and start a new one with the resolved cwd\n\t\tif (userState.bridge?.isAlive) {\n\t\t\tawait userState.bridge.stop();\n\t\t}\n\t\tuserState.bridge = null;\n\n\t\tconst customConfig = { ...config, workingDir: cwd };\n\t\t// Set effectiveCwd BEFORE ensureBridge so the stale-cwd override\n\t\t// in ensureBridge doesn't clobber the resolved directory\n\t\tuserState.effectiveCwd = cwd;\n\t\tconst bridge = await ensureBridge(customConfig, userState);\n\t\tawait bridge.newSession();\n\t\treturn bridge;\n\t}\n\n\tconst bridge = await ensureBridge(config, userState);\n\n\t// Track effective cwd (default from config on first bridge creation)\n\tif (!userState.effectiveCwd) {\n\t\tuserState.effectiveCwd = config.workingDir;\n\t}\n\n\t// No session yet — try to resume latest\n\tif (!bridge.sessionId) {\n\t\tawait bridge.resumeLatest();\n\t}\n\n\treturn bridge;\n}\n"]}
1
+ {"version":3,"file":"bridge-lifecycle.js","sourceRoot":"","sources":["../src/bridge-lifecycle.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bridge lifecycle helpers — extracted to avoid circular imports between bot.ts and commands.\n */\n\nimport { AgentBridge } from \"./agent-bridge.js\";\nimport type { Config } from \"./config.js\";\nimport type { UserState } from \"./types.js\";\n\n/**\n * Ensure the user has an active agent bridge, starting one if needed.\n * Does NOT handle session selection — that's up to the caller.\n */\nexport async function ensureBridge(config: Config, userState: UserState): Promise<AgentBridge> {\n\tif (!userState.bridge || !userState.bridge.isAlive) {\n\t\t// Use effectiveCwd if set — preserves custom cwd across bridge crashes\n\t\tconst effectiveConfig =\n\t\t\tuserState.effectiveCwd && userState.effectiveCwd !== config.workingDir\n\t\t\t\t? { ...config, workingDir: userState.effectiveCwd }\n\t\t\t\t: config;\n\t\tconst bridge = new AgentBridge(effectiveConfig);\n\t\tawait bridge.start();\n\t\tuserState.bridge = bridge;\n\n\t\t// Wire up background agent tracking\n\t\tbridge.onEvent((event: any) => {\n\t\t\tif (event.type === \"background_agent_start\") {\n\t\t\t\tuserState.backgroundAgents.set(event.agentId, {\n\t\t\t\t\tagentId: event.agentId,\n\t\t\t\t\tagentType: event.agentType,\n\t\t\t\t\ttaskSummary: event.taskSummary,\n\t\t\t\t\tstartTime: Date.now(),\n\t\t\t\t});\n\t\t\t} else if (event.type === \"background_agent_end\") {\n\t\t\t\tuserState.backgroundAgents.delete(event.agentId);\n\t\t\t}\n\t\t});\n\t}\n\n\treturn userState.bridge;\n}\n\n/**\n * Ensure bridge is alive AND a session is selected.\n * Used by message/file handlers and skill commands before prompting.\n */\nexport async function ensureBridgeWithSession(config: Config, userState: UserState): Promise<AgentBridge> {\n\t// Handle new session — always kill and recreate the bridge for clean state.\n\t// For /new <path>: uses the user-specified directory.\n\t// For /new (bare): preserves the current effectiveCwd.\n\tif (userState.newSessionFlag) {\n\t\tconst cwd = userState.newSessionCwd ?? userState.effectiveCwd ?? config.workingDir;\n\t\tuserState.newSessionFlag = false;\n\t\tuserState.newSessionCwd = null;\n\n\t\t// Kill existing bridge and start a new one with the resolved cwd\n\t\tif (userState.bridge?.isAlive) {\n\t\t\tawait userState.bridge.stop();\n\t\t}\n\t\tuserState.bridge = null;\n\n\t\tconst customConfig = { ...config, workingDir: cwd };\n\t\t// Set effectiveCwd BEFORE ensureBridge so the stale-cwd override\n\t\t// in ensureBridge doesn't clobber the resolved directory\n\t\tuserState.effectiveCwd = cwd;\n\t\tconst bridge = await ensureBridge(customConfig, userState);\n\t\tawait bridge.newSession();\n\t\treturn bridge;\n\t}\n\n\tconst hadBridge = !!userState.bridge?.isAlive;\n\tconst bridge = await ensureBridge(config, userState);\n\tconst freshBridge = !hadBridge;\n\n\t// Track effective cwd (default from config on first bridge creation)\n\tif (!userState.effectiveCwd) {\n\t\tuserState.effectiveCwd = config.workingDir;\n\t}\n\n\t// No session yet — try to resume latest\n\tif (!bridge.sessionId) {\n\t\tawait bridge.resumeLatest();\n\t}\n\n\t// Check for model fallback warning on fresh bridge (e.g. after crash)\n\tif (freshBridge) {\n\t\ttry {\n\t\t\tconst state = await bridge.getState();\n\t\t\tif (state?.modelFallbackMessage) {\n\t\t\t\tuserState.pendingModelFallbackWarning = state.modelFallbackMessage;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Non-critical — the warning is best-effort\n\t\t}\n\t}\n\n\treturn bridge;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/commands/agent.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,wBAAsB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBlF;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAcjF;AAED,wBAAsB,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CA8ChF;AAED,wBAAsB,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgE9F;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BjG","sourcesContent":["/**\n * Agent slash commands: /compact, /agents, /stats, /model, /thinking\n */\n\nimport type { Context } from \"grammy\";\nimport type { UserState } from \"../types.js\";\nimport { log, safeSend } from \"../util/telegram.js\";\n\nexport async function cmdCompact(ctx: Context, userState: UserState): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tif (!bridge?.isAlive) {\n\t\tawait safeSend(ctx.api, chatId, \"No active session.\");\n\t\treturn;\n\t}\n\n\tawait safeSend(ctx.api, chatId, \"🗜 _Compacting context..._\");\n\ttry {\n\t\tconst result = await bridge.compact();\n\t\tif (result) {\n\t\t\tconst before = (result as any).tokensBefore || 0;\n\t\t\tawait safeSend(ctx.api, chatId, `✅ Compacted (was ${Math.round(before / 1000)}k tokens)`);\n\t\t} else {\n\t\t\tawait safeSend(ctx.api, chatId, \"✅ Compacted.\");\n\t\t}\n\t} catch (e) {\n\t\tlog(`[CMD] /compact error: ${e}`);\n\t\tawait safeSend(ctx.api, chatId, `❌ Compaction failed: ${e}`);\n\t}\n}\n\nexport async function cmdAgents(ctx: Context, userState: UserState): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\n\tif (userState.backgroundAgents.size === 0) {\n\t\tawait safeSend(ctx.api, chatId, \"No background agents running.\");\n\t\treturn;\n\t}\n\n\tconst lines = [\"🤖 *Background Agents*:\\n\"];\n\tfor (const agent of userState.backgroundAgents.values()) {\n\t\tconst elapsed = Math.round((Date.now() - agent.startTime) / 1000);\n\t\tlines.push(`• *${agent.agentType}* (${elapsed}s)\\n ${agent.taskSummary.slice(0, 200)}`);\n\t}\n\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\"));\n}\n\nexport async function cmdStats(ctx: Context, userState: UserState): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tif (!bridge?.isAlive) {\n\t\tawait safeSend(ctx.api, chatId, \"No active session.\");\n\t\treturn;\n\t}\n\n\ttry {\n\t\tconst stats = await bridge.getSessionStats();\n\t\tif (!stats) {\n\t\t\tawait safeSend(ctx.api, chatId, \"No stats available.\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst lines = [\"📊 *Session Stats*:\\n\"];\n\t\tlines.push(`Messages: ${stats.userMessages || 0} user, ${stats.assistantMessages || 0} assistant`);\n\t\tlines.push(`Tool calls: ${stats.toolCalls || 0}`);\n\n\t\tif (stats.tokens) {\n\t\t\tconst t = stats.tokens;\n\t\t\tlines.push(`\\nTokens: ${Math.round((t.total || 0) / 1000)}k total`);\n\t\t\tlines.push(` Input: ${Math.round((t.input || 0) / 1000)}k`);\n\t\t\tlines.push(` Output: ${Math.round((t.output || 0) / 1000)}k`);\n\t\t\tif (t.cacheRead) lines.push(` Cache read: ${Math.round(t.cacheRead / 1000)}k`);\n\t\t}\n\n\t\tif (stats.cost != null) {\n\t\t\tlines.push(`\\n💰 Cost: $${stats.cost.toFixed(4)}`);\n\t\t}\n\n\t\tif (stats.contextUsage) {\n\t\t\tconst cu = stats.contextUsage;\n\t\t\tif (cu.percent != null) {\n\t\t\t\tlines.push(\n\t\t\t\t\t`\\n📏 Context: ${cu.percent}% (${Math.round((cu.tokens || 0) / 1000)}k / ${Math.round((cu.contextWindow || 0) / 1000)}k)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\"));\n\t} catch (e) {\n\t\tlog(`[CMD] /stats error: ${e}`);\n\t\tawait safeSend(ctx.api, chatId, `❌ Failed to get stats: ${e}`);\n\t}\n}\n\nexport async function cmdModel(ctx: Context, userState: UserState, args: string): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tif (!bridge?.isAlive) {\n\t\tawait safeSend(ctx.api, chatId, \"No active session.\");\n\t\treturn;\n\t}\n\n\ttry {\n\t\tif (!args.trim()) {\n\t\t\t// Show current model\n\t\t\tconst state = await bridge.getState();\n\t\t\tif (state?.model) {\n\t\t\t\tawait safeSend(ctx.api, chatId, `🧠 Current model: \\`${state.model.provider}/${state.model.id}\\``);\n\t\t\t} else {\n\t\t\t\tawait safeSend(ctx.api, chatId, \"🧠 No model set.\");\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Try to match and switch\n\t\tconst pattern = args.trim().toLowerCase();\n\t\tconst models = await bridge.getAvailableModels();\n\n\t\t// Score matches: exact id > exact provider/id > substring\n\t\tconst scored = models\n\t\t\t.map((m: any) => {\n\t\t\t\tconst id = m.id.toLowerCase();\n\t\t\t\tconst full = `${m.provider}/${m.id}`.toLowerCase();\n\t\t\t\tif (id === pattern || full === pattern) return { model: m, score: 0 };\n\t\t\t\tif (id.includes(pattern) || full.includes(pattern)) return { model: m, score: 1 };\n\t\t\t\treturn { model: m, score: -1 };\n\t\t\t})\n\t\t\t.filter((s) => s.score >= 0)\n\t\t\t.sort((a, b) => a.score - b.score);\n\n\t\tif (scored.length === 0) {\n\t\t\t// Group models by provider for readable display\n\t\t\tconst byProvider = new Map<string, string[]>();\n\t\t\tfor (const m of models as any[]) {\n\t\t\t\tconst list = byProvider.get(m.provider) || [];\n\t\t\t\tlist.push(m.id);\n\t\t\t\tbyProvider.set(m.provider, list);\n\t\t\t}\n\t\t\tconst lines = [`No model matching \"${pattern}\". Available:`];\n\t\t\tfor (const [provider, ids] of byProvider) {\n\t\t\t\tlines.push(`\\n*${provider}*:`);\n\t\t\t\tfor (const id of ids) {\n\t\t\t\t\tlines.push(` \\`${id}\\``);\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\").slice(0, 4000));\n\t\t\treturn;\n\t\t}\n\n\t\t// If multiple matches, prefer exact over substring\n\t\tconst match = scored[0].model;\n\t\tawait bridge.setModel((match as any).provider, (match as any).id);\n\t\tawait safeSend(ctx.api, chatId, `🧠 Switched to \\`${(match as any).provider}/${(match as any).id}\\``);\n\t} catch (e) {\n\t\tlog(`[CMD] /model error: ${e}`);\n\t\tawait safeSend(ctx.api, chatId, `❌ ${e}`);\n\t}\n}\n\nexport async function cmdThinking(ctx: Context, userState: UserState, args: string): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tif (!bridge?.isAlive) {\n\t\tawait safeSend(ctx.api, chatId, \"No active session.\");\n\t\treturn;\n\t}\n\n\ttry {\n\t\tif (!args.trim()) {\n\t\t\tconst state = await bridge.getState();\n\t\t\tawait safeSend(ctx.api, chatId, `💭 Thinking level: \\`${state?.thinkingLevel || \"unknown\"}\\``);\n\t\t\treturn;\n\t\t}\n\n\t\tconst level = args.trim().toLowerCase();\n\t\tconst valid = [\"off\", \"minimal\", \"low\", \"medium\", \"high\"];\n\t\tif (!valid.includes(level)) {\n\t\t\tawait safeSend(ctx.api, chatId, `Invalid level. Options: ${valid.join(\", \")}`);\n\t\t\treturn;\n\t\t}\n\n\t\tawait bridge.setThinkingLevel(level);\n\t\tawait safeSend(ctx.api, chatId, `💭 Thinking level set to \\`${level}\\``);\n\t} catch (e) {\n\t\tlog(`[CMD] /thinking error: ${e}`);\n\t\tawait safeSend(ctx.api, chatId, `❌ ${e}`);\n\t}\n}\n"]}
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/commands/agent.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,wBAAsB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBlF;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAcjF;AAED,wBAAsB,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CA8ChF;AAED,wBAAsB,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqD9F;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BjG","sourcesContent":["/**\n * Agent slash commands: /compact, /agents, /stats, /model, /thinking\n */\n\nimport type { Context } from \"grammy\";\nimport type { UserState } from \"../types.js\";\nimport { log, safeSend } from \"../util/telegram.js\";\n\nexport async function cmdCompact(ctx: Context, userState: UserState): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tif (!bridge?.isAlive) {\n\t\tawait safeSend(ctx.api, chatId, \"No active session.\");\n\t\treturn;\n\t}\n\n\tawait safeSend(ctx.api, chatId, \"🗜 _Compacting context..._\");\n\ttry {\n\t\tconst result = await bridge.compact();\n\t\tif (result) {\n\t\t\tconst before = (result as any).tokensBefore || 0;\n\t\t\tawait safeSend(ctx.api, chatId, `✅ Compacted (was ${Math.round(before / 1000)}k tokens)`);\n\t\t} else {\n\t\t\tawait safeSend(ctx.api, chatId, \"✅ Compacted.\");\n\t\t}\n\t} catch (e) {\n\t\tlog(`[CMD] /compact error: ${e}`);\n\t\tawait safeSend(ctx.api, chatId, `❌ Compaction failed: ${e}`);\n\t}\n}\n\nexport async function cmdAgents(ctx: Context, userState: UserState): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\n\tif (userState.backgroundAgents.size === 0) {\n\t\tawait safeSend(ctx.api, chatId, \"No background agents running.\");\n\t\treturn;\n\t}\n\n\tconst lines = [\"🤖 *Background Agents*:\\n\"];\n\tfor (const agent of userState.backgroundAgents.values()) {\n\t\tconst elapsed = Math.round((Date.now() - agent.startTime) / 1000);\n\t\tlines.push(`• *${agent.agentType}* (${elapsed}s)\\n ${agent.taskSummary.slice(0, 200)}`);\n\t}\n\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\"));\n}\n\nexport async function cmdStats(ctx: Context, userState: UserState): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tif (!bridge?.isAlive) {\n\t\tawait safeSend(ctx.api, chatId, \"No active session.\");\n\t\treturn;\n\t}\n\n\ttry {\n\t\tconst stats = await bridge.getSessionStats();\n\t\tif (!stats) {\n\t\t\tawait safeSend(ctx.api, chatId, \"No stats available.\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst lines = [\"📊 *Session Stats*:\\n\"];\n\t\tlines.push(`Messages: ${stats.userMessages || 0} user, ${stats.assistantMessages || 0} assistant`);\n\t\tlines.push(`Tool calls: ${stats.toolCalls || 0}`);\n\n\t\tif (stats.tokens) {\n\t\t\tconst t = stats.tokens;\n\t\t\tlines.push(`\\nTokens: ${Math.round((t.total || 0) / 1000)}k total`);\n\t\t\tlines.push(` Input: ${Math.round((t.input || 0) / 1000)}k`);\n\t\t\tlines.push(` Output: ${Math.round((t.output || 0) / 1000)}k`);\n\t\t\tif (t.cacheRead) lines.push(` Cache read: ${Math.round(t.cacheRead / 1000)}k`);\n\t\t}\n\n\t\tif (stats.cost != null) {\n\t\t\tlines.push(`\\n💰 Cost: $${stats.cost.toFixed(4)}`);\n\t\t}\n\n\t\tif (stats.contextUsage) {\n\t\t\tconst cu = stats.contextUsage;\n\t\t\tif (cu.percent != null) {\n\t\t\t\tlines.push(\n\t\t\t\t\t`\\n📏 Context: ${cu.percent}% (${Math.round((cu.tokens || 0) / 1000)}k / ${Math.round((cu.contextWindow || 0) / 1000)}k)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\"));\n\t} catch (e) {\n\t\tlog(`[CMD] /stats error: ${e}`);\n\t\tawait safeSend(ctx.api, chatId, `❌ Failed to get stats: ${e}`);\n\t}\n}\n\nexport async function cmdModel(ctx: Context, userState: UserState, args: string): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tif (!bridge?.isAlive) {\n\t\tawait safeSend(ctx.api, chatId, \"No active session.\");\n\t\treturn;\n\t}\n\n\ttry {\n\t\tif (!args.trim()) {\n\t\t\t// Show current model\n\t\t\tconst state = await bridge.getState();\n\t\t\tif (state?.model) {\n\t\t\t\tawait safeSend(ctx.api, chatId, `🧠 Current model: \\`${state.model.provider}/${state.model.id}\\``);\n\t\t\t} else {\n\t\t\t\tawait safeSend(ctx.api, chatId, \"🧠 No model set.\");\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Resolve pattern using the same logic as CLI/TUI\n\t\tconst pattern = args.trim();\n\t\tconst result = await bridge.resolveModel(pattern);\n\n\t\tif (!result) {\n\t\t\t// No match list available models grouped by provider\n\t\t\tconst models = await bridge.getAvailableModels();\n\t\t\tconst byProvider = new Map<string, string[]>();\n\t\t\tfor (const m of models as any[]) {\n\t\t\t\tconst list = byProvider.get(m.provider) || [];\n\t\t\t\tlist.push(m.id);\n\t\t\t\tbyProvider.set(m.provider, list);\n\t\t\t}\n\t\t\tconst lines = [`No model matching \"${pattern}\". Available:`];\n\t\t\tfor (const [provider, ids] of byProvider) {\n\t\t\t\tlines.push(`\\n*${provider}*:`);\n\t\t\t\tfor (const id of ids) {\n\t\t\t\t\tlines.push(` \\`${id}\\``);\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\").slice(0, 4000));\n\t\t\treturn;\n\t\t}\n\n\t\tconst model = result.model as any;\n\t\tawait bridge.setModel(model.provider, model.id);\n\t\tconst warning = result.warning ? ` ⚠️ ${result.warning}` : \"\";\n\t\tawait safeSend(ctx.api, chatId, `🧠 Switched to \\`${model.provider}/${model.id}\\`${warning}`);\n\t} catch (e) {\n\t\tlog(`[CMD] /model error: ${e}`);\n\t\tawait safeSend(ctx.api, chatId, `❌ ${e}`);\n\t}\n}\n\nexport async function cmdThinking(ctx: Context, userState: UserState, args: string): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tif (!bridge?.isAlive) {\n\t\tawait safeSend(ctx.api, chatId, \"No active session.\");\n\t\treturn;\n\t}\n\n\ttry {\n\t\tif (!args.trim()) {\n\t\t\tconst state = await bridge.getState();\n\t\t\tawait safeSend(ctx.api, chatId, `💭 Thinking level: \\`${state?.thinkingLevel || \"unknown\"}\\``);\n\t\t\treturn;\n\t\t}\n\n\t\tconst level = args.trim().toLowerCase();\n\t\tconst valid = [\"off\", \"minimal\", \"low\", \"medium\", \"high\"];\n\t\tif (!valid.includes(level)) {\n\t\t\tawait safeSend(ctx.api, chatId, `Invalid level. Options: ${valid.join(\", \")}`);\n\t\t\treturn;\n\t\t}\n\n\t\tawait bridge.setThinkingLevel(level);\n\t\tawait safeSend(ctx.api, chatId, `💭 Thinking level set to \\`${level}\\``);\n\t} catch (e) {\n\t\tlog(`[CMD] /thinking error: ${e}`);\n\t\tawait safeSend(ctx.api, chatId, `❌ ${e}`);\n\t}\n}\n"]}
@@ -97,24 +97,12 @@ export async function cmdModel(ctx, userState, args) {
97
97
  }
98
98
  return;
99
99
  }
100
- // Try to match and switch
101
- const pattern = args.trim().toLowerCase();
102
- const models = await bridge.getAvailableModels();
103
- // Score matches: exact id > exact provider/id > substring
104
- const scored = models
105
- .map((m) => {
106
- const id = m.id.toLowerCase();
107
- const full = `${m.provider}/${m.id}`.toLowerCase();
108
- if (id === pattern || full === pattern)
109
- return { model: m, score: 0 };
110
- if (id.includes(pattern) || full.includes(pattern))
111
- return { model: m, score: 1 };
112
- return { model: m, score: -1 };
113
- })
114
- .filter((s) => s.score >= 0)
115
- .sort((a, b) => a.score - b.score);
116
- if (scored.length === 0) {
117
- // Group models by provider for readable display
100
+ // Resolve pattern using the same logic as CLI/TUI
101
+ const pattern = args.trim();
102
+ const result = await bridge.resolveModel(pattern);
103
+ if (!result) {
104
+ // No match — list available models grouped by provider
105
+ const models = await bridge.getAvailableModels();
118
106
  const byProvider = new Map();
119
107
  for (const m of models) {
120
108
  const list = byProvider.get(m.provider) || [];
@@ -131,10 +119,10 @@ export async function cmdModel(ctx, userState, args) {
131
119
  await safeSend(ctx.api, chatId, lines.join("\n").slice(0, 4000));
132
120
  return;
133
121
  }
134
- // If multiple matches, prefer exact over substring
135
- const match = scored[0].model;
136
- await bridge.setModel(match.provider, match.id);
137
- await safeSend(ctx.api, chatId, `🧠 Switched to \`${match.provider}/${match.id}\``);
122
+ const model = result.model;
123
+ await bridge.setModel(model.provider, model.id);
124
+ const warning = result.warning ? ` ⚠️ ${result.warning}` : "";
125
+ await safeSend(ctx.api, chatId, `🧠 Switched to \`${model.provider}/${model.id}\`${warning}`);
138
126
  }
139
127
  catch (e) {
140
128
  log(`[CMD] /model error: ${e}`);
@@ -1 +1 @@
1
- {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/commands/agent.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Agent slash commands: /compact, /agents, /stats, /model, /thinking\n */\n\nimport type { Context } from \"grammy\";\nimport type { UserState } from \"../types.js\";\nimport { log, safeSend } from \"../util/telegram.js\";\n\nexport async function cmdCompact(ctx: Context, userState: UserState): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tif (!bridge?.isAlive) {\n\t\tawait safeSend(ctx.api, chatId, \"No active session.\");\n\t\treturn;\n\t}\n\n\tawait safeSend(ctx.api, chatId, \"🗜 _Compacting context..._\");\n\ttry {\n\t\tconst result = await bridge.compact();\n\t\tif (result) {\n\t\t\tconst before = (result as any).tokensBefore || 0;\n\t\t\tawait safeSend(ctx.api, chatId, `✅ Compacted (was ${Math.round(before / 1000)}k tokens)`);\n\t\t} else {\n\t\t\tawait safeSend(ctx.api, chatId, \"✅ Compacted.\");\n\t\t}\n\t} catch (e) {\n\t\tlog(`[CMD] /compact error: ${e}`);\n\t\tawait safeSend(ctx.api, chatId, `❌ Compaction failed: ${e}`);\n\t}\n}\n\nexport async function cmdAgents(ctx: Context, userState: UserState): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\n\tif (userState.backgroundAgents.size === 0) {\n\t\tawait safeSend(ctx.api, chatId, \"No background agents running.\");\n\t\treturn;\n\t}\n\n\tconst lines = [\"🤖 *Background Agents*:\\n\"];\n\tfor (const agent of userState.backgroundAgents.values()) {\n\t\tconst elapsed = Math.round((Date.now() - agent.startTime) / 1000);\n\t\tlines.push(`• *${agent.agentType}* (${elapsed}s)\\n ${agent.taskSummary.slice(0, 200)}`);\n\t}\n\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\"));\n}\n\nexport async function cmdStats(ctx: Context, userState: UserState): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tif (!bridge?.isAlive) {\n\t\tawait safeSend(ctx.api, chatId, \"No active session.\");\n\t\treturn;\n\t}\n\n\ttry {\n\t\tconst stats = await bridge.getSessionStats();\n\t\tif (!stats) {\n\t\t\tawait safeSend(ctx.api, chatId, \"No stats available.\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst lines = [\"📊 *Session Stats*:\\n\"];\n\t\tlines.push(`Messages: ${stats.userMessages || 0} user, ${stats.assistantMessages || 0} assistant`);\n\t\tlines.push(`Tool calls: ${stats.toolCalls || 0}`);\n\n\t\tif (stats.tokens) {\n\t\t\tconst t = stats.tokens;\n\t\t\tlines.push(`\\nTokens: ${Math.round((t.total || 0) / 1000)}k total`);\n\t\t\tlines.push(` Input: ${Math.round((t.input || 0) / 1000)}k`);\n\t\t\tlines.push(` Output: ${Math.round((t.output || 0) / 1000)}k`);\n\t\t\tif (t.cacheRead) lines.push(` Cache read: ${Math.round(t.cacheRead / 1000)}k`);\n\t\t}\n\n\t\tif (stats.cost != null) {\n\t\t\tlines.push(`\\n💰 Cost: $${stats.cost.toFixed(4)}`);\n\t\t}\n\n\t\tif (stats.contextUsage) {\n\t\t\tconst cu = stats.contextUsage;\n\t\t\tif (cu.percent != null) {\n\t\t\t\tlines.push(\n\t\t\t\t\t`\\n📏 Context: ${cu.percent}% (${Math.round((cu.tokens || 0) / 1000)}k / ${Math.round((cu.contextWindow || 0) / 1000)}k)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\"));\n\t} catch (e) {\n\t\tlog(`[CMD] /stats error: ${e}`);\n\t\tawait safeSend(ctx.api, chatId, `❌ Failed to get stats: ${e}`);\n\t}\n}\n\nexport async function cmdModel(ctx: Context, userState: UserState, args: string): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tif (!bridge?.isAlive) {\n\t\tawait safeSend(ctx.api, chatId, \"No active session.\");\n\t\treturn;\n\t}\n\n\ttry {\n\t\tif (!args.trim()) {\n\t\t\t// Show current model\n\t\t\tconst state = await bridge.getState();\n\t\t\tif (state?.model) {\n\t\t\t\tawait safeSend(ctx.api, chatId, `🧠 Current model: \\`${state.model.provider}/${state.model.id}\\``);\n\t\t\t} else {\n\t\t\t\tawait safeSend(ctx.api, chatId, \"🧠 No model set.\");\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Try to match and switch\n\t\tconst pattern = args.trim().toLowerCase();\n\t\tconst models = await bridge.getAvailableModels();\n\n\t\t// Score matches: exact id > exact provider/id > substring\n\t\tconst scored = models\n\t\t\t.map((m: any) => {\n\t\t\t\tconst id = m.id.toLowerCase();\n\t\t\t\tconst full = `${m.provider}/${m.id}`.toLowerCase();\n\t\t\t\tif (id === pattern || full === pattern) return { model: m, score: 0 };\n\t\t\t\tif (id.includes(pattern) || full.includes(pattern)) return { model: m, score: 1 };\n\t\t\t\treturn { model: m, score: -1 };\n\t\t\t})\n\t\t\t.filter((s) => s.score >= 0)\n\t\t\t.sort((a, b) => a.score - b.score);\n\n\t\tif (scored.length === 0) {\n\t\t\t// Group models by provider for readable display\n\t\t\tconst byProvider = new Map<string, string[]>();\n\t\t\tfor (const m of models as any[]) {\n\t\t\t\tconst list = byProvider.get(m.provider) || [];\n\t\t\t\tlist.push(m.id);\n\t\t\t\tbyProvider.set(m.provider, list);\n\t\t\t}\n\t\t\tconst lines = [`No model matching \"${pattern}\". Available:`];\n\t\t\tfor (const [provider, ids] of byProvider) {\n\t\t\t\tlines.push(`\\n*${provider}*:`);\n\t\t\t\tfor (const id of ids) {\n\t\t\t\t\tlines.push(` \\`${id}\\``);\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\").slice(0, 4000));\n\t\t\treturn;\n\t\t}\n\n\t\t// If multiple matches, prefer exact over substring\n\t\tconst match = scored[0].model;\n\t\tawait bridge.setModel((match as any).provider, (match as any).id);\n\t\tawait safeSend(ctx.api, chatId, `🧠 Switched to \\`${(match as any).provider}/${(match as any).id}\\``);\n\t} catch (e) {\n\t\tlog(`[CMD] /model error: ${e}`);\n\t\tawait safeSend(ctx.api, chatId, `❌ ${e}`);\n\t}\n}\n\nexport async function cmdThinking(ctx: Context, userState: UserState, args: string): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tif (!bridge?.isAlive) {\n\t\tawait safeSend(ctx.api, chatId, \"No active session.\");\n\t\treturn;\n\t}\n\n\ttry {\n\t\tif (!args.trim()) {\n\t\t\tconst state = await bridge.getState();\n\t\t\tawait safeSend(ctx.api, chatId, `💭 Thinking level: \\`${state?.thinkingLevel || \"unknown\"}\\``);\n\t\t\treturn;\n\t\t}\n\n\t\tconst level = args.trim().toLowerCase();\n\t\tconst valid = [\"off\", \"minimal\", \"low\", \"medium\", \"high\"];\n\t\tif (!valid.includes(level)) {\n\t\t\tawait safeSend(ctx.api, chatId, `Invalid level. Options: ${valid.join(\", \")}`);\n\t\t\treturn;\n\t\t}\n\n\t\tawait bridge.setThinkingLevel(level);\n\t\tawait safeSend(ctx.api, chatId, `💭 Thinking level set to \\`${level}\\``);\n\t} catch (e) {\n\t\tlog(`[CMD] /thinking error: ${e}`);\n\t\tawait safeSend(ctx.api, chatId, `❌ ${e}`);\n\t}\n}\n"]}
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/commands/agent.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Agent slash commands: /compact, /agents, /stats, /model, /thinking\n */\n\nimport type { Context } from \"grammy\";\nimport type { UserState } from \"../types.js\";\nimport { log, safeSend } from \"../util/telegram.js\";\n\nexport async function cmdCompact(ctx: Context, userState: UserState): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tif (!bridge?.isAlive) {\n\t\tawait safeSend(ctx.api, chatId, \"No active session.\");\n\t\treturn;\n\t}\n\n\tawait safeSend(ctx.api, chatId, \"🗜 _Compacting context..._\");\n\ttry {\n\t\tconst result = await bridge.compact();\n\t\tif (result) {\n\t\t\tconst before = (result as any).tokensBefore || 0;\n\t\t\tawait safeSend(ctx.api, chatId, `✅ Compacted (was ${Math.round(before / 1000)}k tokens)`);\n\t\t} else {\n\t\t\tawait safeSend(ctx.api, chatId, \"✅ Compacted.\");\n\t\t}\n\t} catch (e) {\n\t\tlog(`[CMD] /compact error: ${e}`);\n\t\tawait safeSend(ctx.api, chatId, `❌ Compaction failed: ${e}`);\n\t}\n}\n\nexport async function cmdAgents(ctx: Context, userState: UserState): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\n\tif (userState.backgroundAgents.size === 0) {\n\t\tawait safeSend(ctx.api, chatId, \"No background agents running.\");\n\t\treturn;\n\t}\n\n\tconst lines = [\"🤖 *Background Agents*:\\n\"];\n\tfor (const agent of userState.backgroundAgents.values()) {\n\t\tconst elapsed = Math.round((Date.now() - agent.startTime) / 1000);\n\t\tlines.push(`• *${agent.agentType}* (${elapsed}s)\\n ${agent.taskSummary.slice(0, 200)}`);\n\t}\n\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\"));\n}\n\nexport async function cmdStats(ctx: Context, userState: UserState): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tif (!bridge?.isAlive) {\n\t\tawait safeSend(ctx.api, chatId, \"No active session.\");\n\t\treturn;\n\t}\n\n\ttry {\n\t\tconst stats = await bridge.getSessionStats();\n\t\tif (!stats) {\n\t\t\tawait safeSend(ctx.api, chatId, \"No stats available.\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst lines = [\"📊 *Session Stats*:\\n\"];\n\t\tlines.push(`Messages: ${stats.userMessages || 0} user, ${stats.assistantMessages || 0} assistant`);\n\t\tlines.push(`Tool calls: ${stats.toolCalls || 0}`);\n\n\t\tif (stats.tokens) {\n\t\t\tconst t = stats.tokens;\n\t\t\tlines.push(`\\nTokens: ${Math.round((t.total || 0) / 1000)}k total`);\n\t\t\tlines.push(` Input: ${Math.round((t.input || 0) / 1000)}k`);\n\t\t\tlines.push(` Output: ${Math.round((t.output || 0) / 1000)}k`);\n\t\t\tif (t.cacheRead) lines.push(` Cache read: ${Math.round(t.cacheRead / 1000)}k`);\n\t\t}\n\n\t\tif (stats.cost != null) {\n\t\t\tlines.push(`\\n💰 Cost: $${stats.cost.toFixed(4)}`);\n\t\t}\n\n\t\tif (stats.contextUsage) {\n\t\t\tconst cu = stats.contextUsage;\n\t\t\tif (cu.percent != null) {\n\t\t\t\tlines.push(\n\t\t\t\t\t`\\n📏 Context: ${cu.percent}% (${Math.round((cu.tokens || 0) / 1000)}k / ${Math.round((cu.contextWindow || 0) / 1000)}k)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\"));\n\t} catch (e) {\n\t\tlog(`[CMD] /stats error: ${e}`);\n\t\tawait safeSend(ctx.api, chatId, `❌ Failed to get stats: ${e}`);\n\t}\n}\n\nexport async function cmdModel(ctx: Context, userState: UserState, args: string): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tif (!bridge?.isAlive) {\n\t\tawait safeSend(ctx.api, chatId, \"No active session.\");\n\t\treturn;\n\t}\n\n\ttry {\n\t\tif (!args.trim()) {\n\t\t\t// Show current model\n\t\t\tconst state = await bridge.getState();\n\t\t\tif (state?.model) {\n\t\t\t\tawait safeSend(ctx.api, chatId, `🧠 Current model: \\`${state.model.provider}/${state.model.id}\\``);\n\t\t\t} else {\n\t\t\t\tawait safeSend(ctx.api, chatId, \"🧠 No model set.\");\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Resolve pattern using the same logic as CLI/TUI\n\t\tconst pattern = args.trim();\n\t\tconst result = await bridge.resolveModel(pattern);\n\n\t\tif (!result) {\n\t\t\t// No match — list available models grouped by provider\n\t\t\tconst models = await bridge.getAvailableModels();\n\t\t\tconst byProvider = new Map<string, string[]>();\n\t\t\tfor (const m of models as any[]) {\n\t\t\t\tconst list = byProvider.get(m.provider) || [];\n\t\t\t\tlist.push(m.id);\n\t\t\t\tbyProvider.set(m.provider, list);\n\t\t\t}\n\t\t\tconst lines = [`No model matching \"${pattern}\". Available:`];\n\t\t\tfor (const [provider, ids] of byProvider) {\n\t\t\t\tlines.push(`\\n*${provider}*:`);\n\t\t\t\tfor (const id of ids) {\n\t\t\t\t\tlines.push(` \\`${id}\\``);\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\").slice(0, 4000));\n\t\t\treturn;\n\t\t}\n\n\t\tconst model = result.model as any;\n\t\tawait bridge.setModel(model.provider, model.id);\n\t\tconst warning = result.warning ? ` ⚠️ ${result.warning}` : \"\";\n\t\tawait safeSend(ctx.api, chatId, `🧠 Switched to \\`${model.provider}/${model.id}\\`${warning}`);\n\t} catch (e) {\n\t\tlog(`[CMD] /model error: ${e}`);\n\t\tawait safeSend(ctx.api, chatId, `❌ ${e}`);\n\t}\n}\n\nexport async function cmdThinking(ctx: Context, userState: UserState, args: string): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tif (!bridge?.isAlive) {\n\t\tawait safeSend(ctx.api, chatId, \"No active session.\");\n\t\treturn;\n\t}\n\n\ttry {\n\t\tif (!args.trim()) {\n\t\t\tconst state = await bridge.getState();\n\t\t\tawait safeSend(ctx.api, chatId, `💭 Thinking level: \\`${state?.thinkingLevel || \"unknown\"}\\``);\n\t\t\treturn;\n\t\t}\n\n\t\tconst level = args.trim().toLowerCase();\n\t\tconst valid = [\"off\", \"minimal\", \"low\", \"medium\", \"high\"];\n\t\tif (!valid.includes(level)) {\n\t\t\tawait safeSend(ctx.api, chatId, `Invalid level. Options: ${valid.join(\", \")}`);\n\t\t\treturn;\n\t\t}\n\n\t\tawait bridge.setThinkingLevel(level);\n\t\tawait safeSend(ctx.api, chatId, `💭 Thinking level set to \\`${level}\\``);\n\t} catch (e) {\n\t\tlog(`[CMD] /thinking error: ${e}`);\n\t\tawait safeSend(ctx.api, chatId, `❌ ${e}`);\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/handlers/events.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAElC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,eAAe,EAAmB,MAAM,qBAAqB,CAAC;AAEvE,gFAA8E;AAC9E,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAE5D;;;;;GAKG;AACH,KAAK,QAAQ,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,CAAC;AAmErD,MAAM,WAAW,iBAAiB;IACjC,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,gDAAgD;IAChD,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,uBAAuB;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,+BAA+B;IAC/B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,wBAAwB;IACxB,KAAK,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5D,wBAAwB;IACxB,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC5C,iCAAiC;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,gCAAgC;IAChC,MAAM,EAAE,eAAe,CAAC;IACxB,wFAAsF;IACtF,eAAe,EAAE,OAAO,CAAC;IACzB,wGAAsG;IACtG,YAAY,EAAE,OAAO,CAAC;IACtB,+CAA+C;IAC/C,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAuE;IACvE,eAAe,CAAC,EAAE,GAAG,CAAC;CACtB;AAaD;;GAEG;AACH,wBAAgB,kBAAkB,CACjC,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,MAAM,GAAG,IAAI,GAC5B,iBAAiB,CAgBnB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACrC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,iBAAiB,EACxB,KAAK,EAAE,QAAQ,GACb,OAAO,CAAC,IAAI,CAAC,CA6Qf","sourcesContent":["/**\n * Event display — translates RPC agent events into Telegram messages.\n *\n * Manages an ephemeral status message that shows tool use, task lists,\n * and subagent activity. Text from the agent is sent as permanent messages.\n */\n\nimport { existsSync } from \"node:fs\";\nimport type { Api } from \"grammy\";\nimport { InputFile } from \"grammy\";\nimport type { TrackedAgent } from \"../types.js\";\nimport { extractSendFiles } from \"../util/files.js\";\nimport { DebouncedEditor, log, safeDelete } from \"../util/telegram.js\";\n\n/** Callback to queue a message for delivery — never blocks the event chain */\nexport type SendFn = (text: string, long?: boolean) => void;\n\n/**\n * RPC events include both core AgentEvent and session-specific events\n * (tasks_update, background_agent_*, auto_compaction_*).\n * We type loosely here since the RPC client types onEvent as AgentEvent\n * but actually forwards all AgentSessionEvent types.\n */\ntype RpcEvent = { type: string; [key: string]: any };\n\n// Tool emoji mapping (tool names are lowercase in definitions)\nconst TOOL_EMOJI: Record<string, string> = {\n\tbash: \"🔧\",\n\tread: \"📖\",\n\tedit: \"✏️\",\n\twrite: \"📝\",\n\tgrep: \"🔎\",\n\tfind: \"🔍\",\n\tls: \"📂\",\n\tweb_search: \"🌐\",\n\tweb_fetch: \"🌐\",\n\tsubagent: \"🤖\",\n\ttasks_update: \"📋\",\n\tskill: \"⚡\",\n};\n\nfunction toolEmoji(name: string): string {\n\treturn TOOL_EMOJI[name] || \"🔧\";\n}\n\n/** Format a tool call for display */\nfunction formatTool(name: string, args: Record<string, any>): string {\n\tconst emoji = toolEmoji(name);\n\tswitch (name) {\n\t\tcase \"bash\": {\n\t\t\tconst cmd = args.command || \"\";\n\t\t\treturn `${emoji} *bash*\\n\\`${cmd.slice(0, 500)}\\``;\n\t\t}\n\t\tcase \"read\":\n\t\t\treturn `${emoji} *read*: \\`${args.path || \"?\"}\\``;\n\t\tcase \"edit\":\n\t\t\treturn `${emoji} *edit*: \\`${args.path || \"?\"}\\``;\n\t\tcase \"write\":\n\t\t\treturn `${emoji} *write*: \\`${args.path || \"?\"}\\``;\n\t\tcase \"grep\":\n\t\t\treturn `${emoji} *grep*: \\`${args.pattern || \"?\"}\\``;\n\t\tcase \"find\":\n\t\t\treturn `${emoji} *find*: \\`${args.pattern || \"?\"}\\``;\n\t\tcase \"ls\":\n\t\t\treturn `${emoji} *ls*: \\`${args.path || \".\"}\\``;\n\t\tcase \"web_search\":\n\t\t\treturn `${emoji} *web\\\\_search*: ${args.query || \"?\"}`;\n\t\tcase \"web_fetch\":\n\t\t\treturn `${emoji} *web\\\\_fetch*: ${(args.url || \"?\").slice(0, 80)}`;\n\t\tcase \"subagent\":\n\t\t\treturn `${emoji} *subagent* (${args.agent || \"?\"}): ${(args.task || args.tasks?.[0]?.task || \"?\").slice(0, 200)}`;\n\t\tcase \"skill\":\n\t\t\treturn `${emoji} *skill*: ${args.skill || \"?\"}`;\n\t\tdefault:\n\t\t\treturn `${emoji} *${name}*`;\n\t}\n}\n\n/** Format task list as checklist */\nfunction formatTaskList(tasks: Array<{ id: string; title: string; status: string }>): string {\n\tif (!tasks.length) return \"📋 *Tasks*: (empty)\";\n\tconst lines = [\"📋 *Tasks*:\"];\n\tfor (const task of tasks) {\n\t\tif (task.status === \"completed\") lines.push(` ✅ ${task.title}`);\n\t\telse if (task.status === \"in_progress\") lines.push(` 🔄 ${task.title}`);\n\t\telse lines.push(` ⬜ ${task.title}`);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\nexport interface EventDisplayState {\n\t/** Chat ID to send messages to */\n\tchatId: number;\n\t/** Message ID to reply to */\n\treplyToId: number;\n\t/** Ephemeral status message ID (edited in-place) */\n\tstatusMessageId: number | null;\n\t/** Tool messages accumulated since last text */\n\ttoolsSinceText: string[];\n\t/** Total tool count */\n\ttoolCount: number;\n\t/** All text blocks received */\n\ttextBlocks: string[];\n\t/** Current task list */\n\ttasks: Array<{ id: string; title: string; status: string }>;\n\t/** Background agents */\n\tbackgroundAgents: Map<string, TrackedAgent>;\n\t/** Whether agent has finished */\n\tdone: boolean;\n\t/** Debounced editor instance */\n\teditor: DebouncedEditor;\n\t/** Whether auto-retry is in progress (Layer 1: reactive — set by auto_retry_start) */\n\tretryInProgress: boolean;\n\t/** Whether a retry is expected (Layer 2: predictive — set by agent_end when error looks retryable) */\n\tpendingRetry: boolean;\n\t/** Current retry attempt number for display */\n\tretryAttempt: number;\n\t/** Buddy controller — receives agent events for context + reactions */\n\tbuddyController?: any;\n}\n\n/**\n * Check if an error message looks retryable (overloaded, rate limit, server errors).\n * Mirrors the core's _isRetryableError check as a defensive Layer 2.\n */\nconst RETRYABLE_ERROR_PATTERN =\n\t/overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|timed? out|timeout|terminated|retry delay/i;\n\nfunction isRetryableError(errorMessage: string): boolean {\n\treturn RETRYABLE_ERROR_PATTERN.test(errorMessage);\n}\n\n/**\n * Create a fresh event display state for a new agent run.\n */\nexport function createEventDisplay(\n\tapi: Api,\n\tchatId: number,\n\treplyToId: number,\n\tstatusMessageId: number | null,\n): EventDisplayState {\n\treturn {\n\t\tchatId,\n\t\treplyToId,\n\t\tstatusMessageId,\n\t\ttoolsSinceText: [],\n\t\ttoolCount: 0,\n\t\ttextBlocks: [],\n\t\ttasks: [],\n\t\tbackgroundAgents: new Map(),\n\t\tdone: false,\n\t\teditor: new DebouncedEditor(api),\n\t\tretryInProgress: false,\n\t\tpendingRetry: false,\n\t\tretryAttempt: 0,\n\t};\n}\n\n/**\n * Process an agent event and update the display.\n */\nexport async function handleAgentEvent(\n\tsend: SendFn,\n\tapi: Api,\n\tstate: EventDisplayState,\n\tevent: RpcEvent,\n): Promise<void> {\n\tswitch (event.type) {\n\t\tcase \"tool_execution_start\": {\n\t\t\tconst name = event.toolName || \"?\";\n\t\t\tconst args = event.args || {};\n\t\t\tstate.toolCount++;\n\n\t\t\t// tasks_update is shown via the separate tasks_update event — skip from tool summary\n\t\t\tif (name !== \"tasks_update\") {\n\t\t\t\tconst toolMsg = formatTool(name, args);\n\t\t\t\tstate.toolsSinceText.push(toolMsg);\n\t\t\t}\n\n\t\t\t// Update status with tool count and recent tools\n\t\t\tupdateStatus(state);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"tool_execution_end\": {\n\t\t\t// Feed event to buddy controller for context capture + error reactions\n\t\t\tstate.buddyController?.handleEvent(event);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"message_end\": {\n\t\t\tconst msg = event.message;\n\n\t\t\t// Show subagent results — the parent agent references these but the\n\t\t\t// Telegram user can't see them otherwise. Send the full content.\n\t\t\tif (msg?.role === \"toolResult\" && msg?.toolName === \"subagent\") {\n\t\t\t\tconst content = msg?.content;\n\t\t\t\tif (content && Array.isArray(content)) {\n\t\t\t\t\tfor (const block of content) {\n\t\t\t\t\t\tif (block.type === \"text\" && block.text?.trim()) {\n\t\t\t\t\t\t\tsend(`🤖 *Subagent result:*\\n${block.text.trim()}`, true);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Show background agent completion results — these arrive as user\n\t\t\t// messages injected by agent-session.ts via prompt()/steer() and\n\t\t\t// contain the actual subagent output the model sees.\n\t\t\tif (msg?.role === \"user\") {\n\t\t\t\tconst content = msg?.content;\n\t\t\t\tif (content && Array.isArray(content)) {\n\t\t\t\t\tfor (const block of content) {\n\t\t\t\t\t\tif (block.type === \"text\" && block.text?.includes(\"<background-agent-complete>\")) {\n\t\t\t\t\t\t\t// Extract the content between the XML tags\n\t\t\t\t\t\t\tconst match = block.text.match(\n\t\t\t\t\t\t\t\t/<background-agent-complete>\\n?([\\s\\S]*?)\\n?<\\/background-agent-complete>/,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (match?.[1]?.trim()) {\n\t\t\t\t\t\t\t\tsend(`🤖 *Background agent complete:*\\n${match[1].trim()}`, true);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Only display assistant messages — user messages are echoed back by RPC\n\t\t\tif (msg?.role !== \"assistant\") break;\n\t\t\tconst content = msg?.content;\n\t\t\tif (!content || !Array.isArray(content)) break;\n\n\t\t\tfor (const block of content) {\n\t\t\t\t// Display thinking blocks (collapsed summary)\n\t\t\t\tif (block.type === \"thinking\" && block.thinking?.trim() && !block.redacted) {\n\t\t\t\t\tconst thinking = block.thinking.trim();\n\t\t\t\t\tsend(`💭 _${thinking}_`, true);\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"text\" && block.text?.trim()) {\n\t\t\t\t\tconst text = block.text.trim();\n\n\t\t\t\t\t// Flush accumulated tools as permanent summary\n\t\t\t\t\tif (state.toolsSinceText.length > 0) {\n\t\t\t\t\t\tconst summary = `📋 *${state.toolsSinceText.length} tools*:\\n${state.toolsSinceText.join(\"\\n\")}`;\n\t\t\t\t\t\tsend(summary, true);\n\t\t\t\t\t\tstate.toolsSinceText = [];\n\t\t\t\t\t}\n\n\t\t\t\t\t// Send the text as a permanent message\n\t\t\t\t\tstate.textBlocks.push(text);\n\n\t\t\t\t\t// Check for file send markers\n\t\t\t\t\tconst [cleanText, filePaths] = extractSendFiles(text);\n\t\t\t\t\tif (cleanText) {\n\t\t\t\t\t\tsend(cleanText, true);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Send any requested files (silently skip non-existent paths —\n\t\t\t\t\t// the pattern may appear in explanatory text)\n\t\t\t\t\tfor (const filePath of filePaths) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tif (existsSync(filePath)) {\n\t\t\t\t\t\t\t\tawait api.sendDocument(state.chatId, new InputFile(filePath));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tlog(`[EVENTS] Failed to send file ${filePath}: ${e}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Feed event to buddy controller for context capture + reactions\n\t\t\tstate.buddyController?.handleEvent(event);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"tasks_update\": {\n\t\t\tstate.tasks = (event as any).tasks || [];\n\t\t\tupdateStatus(state);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"background_agent_start\": {\n\t\t\tconst { agentId, agentType, taskSummary } = event as any;\n\t\t\tstate.backgroundAgents.set(agentId, {\n\t\t\t\tagentId,\n\t\t\t\tagentType,\n\t\t\t\ttaskSummary,\n\t\t\t\tstartTime: Date.now(),\n\t\t\t});\n\t\t\tupdateStatus(state);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"background_agent_end\": {\n\t\t\tconst { agentId } = event as any;\n\t\t\tstate.backgroundAgents.delete(agentId);\n\t\t\t// Background agents completing does not end the parent's turn.\n\t\t\t// Only agent_end sets done — same as TUI behavior.\n\t\t\tupdateStatus(state);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"auto_compaction_start\": {\n\t\t\tupdateStatusText(state, \"🗜 _Compacting context..._\");\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"auto_compaction_end\": {\n\t\t\tconst result = (event as any).result;\n\t\t\tif (result) {\n\t\t\t\tconst before = result.tokensBefore || 0;\n\t\t\t\tconst msg = `🗜 Context compacted (was ${Math.round(before / 1000)}k tokens)`;\n\t\t\t\tsend(msg);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\t// =====================================================================\n\t\t// Auto-retry — prevents agent_end from marking done during retries\n\t\t// =====================================================================\n\n\t\tcase \"auto_retry_start\": {\n\t\t\tconst { attempt, maxAttempts, delayMs, errorMessage } = event as any;\n\t\t\tstate.retryInProgress = true;\n\t\t\tstate.pendingRetry = false; // Layer 1 has taken over from Layer 2\n\t\t\tstate.retryAttempt = attempt;\n\t\t\tconst delaySec = Math.round(delayMs / 1000);\n\t\t\tconst shortErr = errorMessage?.length > 80 ? `${errorMessage.slice(0, 80)}…` : errorMessage;\n\t\t\tupdateStatusText(state, `🔄 _Retrying (${attempt}/${maxAttempts}) in ${delaySec}s — ${shortErr || \"error\"}_`);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"auto_retry_end\": {\n\t\t\tconst { success, attempt, finalError } = event as any;\n\t\t\tstate.retryInProgress = false;\n\t\t\tstate.retryAttempt = 0;\n\t\t\tif (!success && finalError) {\n\t\t\t\t// Max retries exhausted — show final error\n\t\t\t\tsend(`❌ _Retry failed (${attempt} attempts):_ ${finalError}`, true);\n\t\t\t}\n\t\t\t// On success, the retry's agent_start/agent_end cycle will handle display normally\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"agent_end\": {\n\t\t\t// Flush any remaining tools\n\t\t\tif (state.toolsSinceText.length > 0) {\n\t\t\t\tconst summary = `📋 *${state.toolsSinceText.length} tools*:\\n${state.toolsSinceText.join(\"\\n\")}`;\n\t\t\t\tsend(summary, true);\n\t\t\t\tstate.toolsSinceText = [];\n\t\t\t}\n\n\t\t\t// Check for error in agent_end messages\n\t\t\tconst errorMsg = (event.messages as any[])?.find(\n\t\t\t\t(m: any) => m.stopReason === \"error\" || m.stopReason === \"aborted\",\n\t\t\t);\n\n\t\t\t// Layer 2 (defensive): If this error looks retryable and we're not already\n\t\t\t// tracking a retry via Layer 1, don't mark done — the core will auto-retry\n\t\t\t// and emit a new agent_start/agent_end cycle.\n\t\t\tconst errorIsRetryable = errorMsg?.errorMessage && isRetryableError(errorMsg.errorMessage);\n\n\t\t\tif (errorMsg?.errorMessage) {\n\t\t\t\t// Suppress the scary error message during retry — user already saw the\n\t\t\t\t// auto_retry_start status. Only show the error if retry tracking missed it\n\t\t\t\t// (defensive: shouldn't happen, but better than silence).\n\t\t\t\tif (!state.retryInProgress && !errorIsRetryable) {\n\t\t\t\t\tconst provider = errorMsg.provider ? `${errorMsg.provider}/${errorMsg.model}` : \"\";\n\t\t\t\t\tconst prefix = provider ? `${provider}: ` : \"\";\n\t\t\t\t\tconst errLower = errorMsg.errorMessage.toLowerCase();\n\t\t\t\t\tconst hint =\n\t\t\t\t\t\terrLower.includes(\"connection\") || errLower.includes(\"timeout\") || errLower.includes(\"network\")\n\t\t\t\t\t\t\t? \"\\n_Provider may be down — try /model to switch._\"\n\t\t\t\t\t\t\t: \"\";\n\t\t\t\t\tsend(`❌ ${prefix}${errorMsg.errorMessage}${hint}`, true);\n\t\t\t\t}\n\t\t\t} else if (state.textBlocks.length === 0 && state.backgroundAgents.size === 0) {\n\t\t\t\t// Only show \"(No response)\" when truly done — not between agent cycles\n\t\t\t\tif (!state.retryInProgress && !errorIsRetryable) {\n\t\t\t\t\tsend(\"(No response)\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Feed event to buddy controller for context capture + reactions\n\t\t\tstate.buddyController?.handleEvent(event);\n\n\t\t\t// Don't mark done if auto-retry is in progress (Layer 1) or the error\n\t\t\t// looks retryable (Layer 2 — defensive catch in case events were missed).\n\t\t\t// The core will emit a new agent_start/agent_end cycle for the retry.\n\t\t\tif (state.retryInProgress || errorIsRetryable) {\n\t\t\t\t// Signal that a retry is expected — the completion check in\n\t\t\t\t// ensureSubscribed needs this because it runs in the eventChain\n\t\t\t\t// BEFORE auto_retry_start has been processed.\n\t\t\t\tif (errorIsRetryable) state.pendingRetry = true;\n\t\t\t\t// Reset per-cycle state for the next agent loop\n\t\t\t\tstate.textBlocks = [];\n\t\t\t\tstate.toolCount = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// If background agents are still running, keep the subscription alive\n\t\t\t// and reset per-cycle state for the next agent loop\n\t\t\tif (state.backgroundAgents.size > 0) {\n\t\t\t\tstate.textBlocks = [];\n\t\t\t\tstate.toolCount = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Delete ephemeral status before signaling done\n\t\t\tif (state.statusMessageId) {\n\t\t\t\tawait state.editor.flush(state.chatId, state.statusMessageId);\n\t\t\t\tawait safeDelete(api, state.chatId, state.statusMessageId);\n\t\t\t\tstate.statusMessageId = null;\n\t\t\t}\n\n\t\t\t// Clean up editor\n\t\t\tstate.editor.clear();\n\n\t\t\t// Signal done AFTER cleanup — waitForCompletion checks this flag,\n\t\t\t// so setting it last ensures status message is deleted before DONE is sent\n\t\t\tstate.done = true;\n\t\t\tbreak;\n\t\t}\n\n\t\t// Handle error responses that leak through RPC (async prompt errors)\n\t\tcase \"response\": {\n\t\t\tconst resp = event as any;\n\t\t\tif (!resp.success && resp.error) {\n\t\t\t\tsend(`❌ ${resp.error}`, true);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/**\n * Build and push a status update to the ephemeral message.\n */\nfunction updateStatus(state: EventDisplayState): void {\n\tif (!state.statusMessageId) return;\n\n\tconst parts: string[] = [];\n\n\t// Tool count header\n\tif (state.toolCount > 0) {\n\t\tparts.push(`🔧 *Tool ${state.toolCount}*`);\n\t}\n\n\t// Task list\n\tif (state.tasks.length > 0) {\n\t\tparts.push(formatTaskList(state.tasks));\n\t}\n\n\t// Background agents\n\tif (state.backgroundAgents.size > 0) {\n\t\tfor (const agent of state.backgroundAgents.values()) {\n\t\t\tparts.push(`🤖 *${agent.agentType}*: ${agent.taskSummary.slice(0, 200)}`);\n\t\t}\n\t}\n\n\t// Recent tools (last 5)\n\tif (state.toolsSinceText.length > 0) {\n\t\tconst recent = state.toolsSinceText.slice(-5);\n\t\tparts.push(recent.join(\"\\n\\n\"));\n\t}\n\n\tif (parts.length === 0) return;\n\n\tconst text = parts.join(\"\\n\\n\").slice(0, 4000);\n\tstate.editor.edit(state.chatId, state.statusMessageId, text);\n}\n\nfunction updateStatusText(state: EventDisplayState, text: string): void {\n\tif (!state.statusMessageId) return;\n\tstate.editor.edit(state.chatId, state.statusMessageId, text);\n}\n"]}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/handlers/events.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAElC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,eAAe,EAAmB,MAAM,qBAAqB,CAAC;AAEvE,gFAA8E;AAC9E,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAE5D;;;;;GAKG;AACH,KAAK,QAAQ,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,CAAC;AAmErD,MAAM,WAAW,iBAAiB;IACjC,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,gDAAgD;IAChD,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,uBAAuB;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,+BAA+B;IAC/B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,wBAAwB;IACxB,KAAK,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5D,wBAAwB;IACxB,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC5C,iCAAiC;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,gCAAgC;IAChC,MAAM,EAAE,eAAe,CAAC;IACxB,wFAAsF;IACtF,eAAe,EAAE,OAAO,CAAC;IACzB,wGAAsG;IACtG,YAAY,EAAE,OAAO,CAAC;IACtB,+CAA+C;IAC/C,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAuE;IACvE,eAAe,CAAC,EAAE,GAAG,CAAC;CACtB;AAaD;;GAEG;AACH,wBAAgB,kBAAkB,CACjC,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,MAAM,GAAG,IAAI,GAC5B,iBAAiB,CAgBnB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACrC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,iBAAiB,EACxB,KAAK,EAAE,QAAQ,GACb,OAAO,CAAC,IAAI,CAAC,CA6Qf","sourcesContent":["/**\n * Event display — translates RPC agent events into Telegram messages.\n *\n * Manages an ephemeral status message that shows tool use, task lists,\n * and subagent activity. Text from the agent is sent as permanent messages.\n */\n\nimport { existsSync } from \"node:fs\";\nimport type { Api } from \"grammy\";\nimport { InputFile } from \"grammy\";\nimport type { TrackedAgent } from \"../types.js\";\nimport { extractSendFiles } from \"../util/files.js\";\nimport { DebouncedEditor, log, safeDelete } from \"../util/telegram.js\";\n\n/** Callback to queue a message for delivery — never blocks the event chain */\nexport type SendFn = (text: string, long?: boolean) => void;\n\n/**\n * RPC events include both core AgentEvent and session-specific events\n * (tasks_update, background_agent_*, auto_compaction_*).\n * We type loosely here since the RPC client types onEvent as AgentEvent\n * but actually forwards all AgentSessionEvent types.\n */\ntype RpcEvent = { type: string; [key: string]: any };\n\n// Tool emoji mapping (tool names are lowercase in definitions)\nconst TOOL_EMOJI: Record<string, string> = {\n\tbash: \"🔧\",\n\tread: \"📖\",\n\tedit: \"✏️\",\n\twrite: \"📝\",\n\tgrep: \"🔎\",\n\tfind: \"🔍\",\n\tls: \"📂\",\n\tweb_search: \"🌐\",\n\tweb_fetch: \"🌐\",\n\tsubagent: \"🤖\",\n\ttasks_update: \"📋\",\n\tskill: \"⚡\",\n};\n\nfunction toolEmoji(name: string): string {\n\treturn TOOL_EMOJI[name] || \"🔧\";\n}\n\n/** Format a tool call for display */\nfunction formatTool(name: string, args: Record<string, any>): string {\n\tconst emoji = toolEmoji(name);\n\tswitch (name) {\n\t\tcase \"bash\": {\n\t\t\tconst cmd = args.command || \"\";\n\t\t\treturn `${emoji} *bash*\\n\\`${cmd.slice(0, 500)}\\``;\n\t\t}\n\t\tcase \"read\":\n\t\t\treturn `${emoji} *read*: \\`${args.path || \"?\"}\\``;\n\t\tcase \"edit\":\n\t\t\treturn `${emoji} *edit*: \\`${args.path || \"?\"}\\``;\n\t\tcase \"write\":\n\t\t\treturn `${emoji} *write*: \\`${args.path || \"?\"}\\``;\n\t\tcase \"grep\":\n\t\t\treturn `${emoji} *grep*: \\`${args.pattern || \"?\"}\\``;\n\t\tcase \"find\":\n\t\t\treturn `${emoji} *find*: \\`${args.pattern || \"?\"}\\``;\n\t\tcase \"ls\":\n\t\t\treturn `${emoji} *ls*: \\`${args.path || \".\"}\\``;\n\t\tcase \"web_search\":\n\t\t\treturn `${emoji} *web\\\\_search*: ${args.query || \"?\"}`;\n\t\tcase \"web_fetch\":\n\t\t\treturn `${emoji} *web\\\\_fetch*: ${(args.url || \"?\").slice(0, 80)}`;\n\t\tcase \"subagent\":\n\t\t\treturn `${emoji} *subagent* (${args.agent || \"?\"}): ${(args.task || args.tasks?.[0]?.task || \"?\").slice(0, 200)}`;\n\t\tcase \"skill\":\n\t\t\treturn `${emoji} *skill*: ${args.skill || \"?\"}`;\n\t\tdefault:\n\t\t\treturn `${emoji} *${name}*`;\n\t}\n}\n\n/** Format task list as checklist */\nfunction formatTaskList(tasks: Array<{ id: string; title: string; status: string }>): string {\n\tif (!tasks.length) return \"📋 *Tasks*: (empty)\";\n\tconst lines = [\"📋 *Tasks*:\"];\n\tfor (const task of tasks) {\n\t\tif (task.status === \"completed\") lines.push(` ✅ ${task.title}`);\n\t\telse if (task.status === \"in_progress\") lines.push(` 🔄 ${task.title}`);\n\t\telse lines.push(` ⬜ ${task.title}`);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\nexport interface EventDisplayState {\n\t/** Chat ID to send messages to */\n\tchatId: number;\n\t/** Message ID to reply to */\n\treplyToId: number;\n\t/** Ephemeral status message ID (edited in-place) */\n\tstatusMessageId: number | null;\n\t/** Tool messages accumulated since last text */\n\ttoolsSinceText: string[];\n\t/** Total tool count */\n\ttoolCount: number;\n\t/** All text blocks received */\n\ttextBlocks: string[];\n\t/** Current task list */\n\ttasks: Array<{ id: string; title: string; status: string }>;\n\t/** Background agents */\n\tbackgroundAgents: Map<string, TrackedAgent>;\n\t/** Whether agent has finished */\n\tdone: boolean;\n\t/** Debounced editor instance */\n\teditor: DebouncedEditor;\n\t/** Whether auto-retry is in progress (Layer 1: reactive — set by auto_retry_start) */\n\tretryInProgress: boolean;\n\t/** Whether a retry is expected (Layer 2: predictive — set by agent_end when error looks retryable) */\n\tpendingRetry: boolean;\n\t/** Current retry attempt number for display */\n\tretryAttempt: number;\n\t/** Buddy controller — receives agent events for context + reactions */\n\tbuddyController?: any;\n}\n\n/**\n * Check if an error message looks retryable (overloaded, rate limit, server errors).\n * Mirrors the core's _isRetryableError check as a defensive Layer 2.\n */\nconst RETRYABLE_ERROR_PATTERN =\n\t/overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|other side closed|fetch failed|ended without|upstream.?connect|reset before headers|socket hang up|timed? out|timeout|terminated|retry delay/i;\n\nfunction isRetryableError(errorMessage: string): boolean {\n\treturn RETRYABLE_ERROR_PATTERN.test(errorMessage);\n}\n\n/**\n * Create a fresh event display state for a new agent run.\n */\nexport function createEventDisplay(\n\tapi: Api,\n\tchatId: number,\n\treplyToId: number,\n\tstatusMessageId: number | null,\n): EventDisplayState {\n\treturn {\n\t\tchatId,\n\t\treplyToId,\n\t\tstatusMessageId,\n\t\ttoolsSinceText: [],\n\t\ttoolCount: 0,\n\t\ttextBlocks: [],\n\t\ttasks: [],\n\t\tbackgroundAgents: new Map(),\n\t\tdone: false,\n\t\teditor: new DebouncedEditor(api),\n\t\tretryInProgress: false,\n\t\tpendingRetry: false,\n\t\tretryAttempt: 0,\n\t};\n}\n\n/**\n * Process an agent event and update the display.\n */\nexport async function handleAgentEvent(\n\tsend: SendFn,\n\tapi: Api,\n\tstate: EventDisplayState,\n\tevent: RpcEvent,\n): Promise<void> {\n\tswitch (event.type) {\n\t\tcase \"tool_execution_start\": {\n\t\t\tconst name = event.toolName || \"?\";\n\t\t\tconst args = event.args || {};\n\t\t\tstate.toolCount++;\n\n\t\t\t// tasks_update is shown via the separate tasks_update event — skip from tool summary\n\t\t\tif (name !== \"tasks_update\") {\n\t\t\t\tconst toolMsg = formatTool(name, args);\n\t\t\t\tstate.toolsSinceText.push(toolMsg);\n\t\t\t}\n\n\t\t\t// Update status with tool count and recent tools\n\t\t\tupdateStatus(state);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"tool_execution_end\": {\n\t\t\t// Feed event to buddy controller for context capture + error reactions\n\t\t\tstate.buddyController?.handleEvent(event);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"message_end\": {\n\t\t\tconst msg = event.message;\n\n\t\t\t// Show subagent results — the parent agent references these but the\n\t\t\t// Telegram user can't see them otherwise. Send the full content.\n\t\t\tif (msg?.role === \"toolResult\" && msg?.toolName === \"subagent\") {\n\t\t\t\tconst content = msg?.content;\n\t\t\t\tif (content && Array.isArray(content)) {\n\t\t\t\t\tfor (const block of content) {\n\t\t\t\t\t\tif (block.type === \"text\" && block.text?.trim()) {\n\t\t\t\t\t\t\tsend(`🤖 *Subagent result:*\\n${block.text.trim()}`, true);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Show background agent completion results — these arrive as user\n\t\t\t// messages injected by agent-session.ts via prompt()/steer() and\n\t\t\t// contain the actual subagent output the model sees.\n\t\t\tif (msg?.role === \"user\") {\n\t\t\t\tconst content = msg?.content;\n\t\t\t\tif (content && Array.isArray(content)) {\n\t\t\t\t\tfor (const block of content) {\n\t\t\t\t\t\tif (block.type === \"text\" && block.text?.includes(\"<background-agent-complete>\")) {\n\t\t\t\t\t\t\t// Extract the content between the XML tags\n\t\t\t\t\t\t\tconst match = block.text.match(\n\t\t\t\t\t\t\t\t/<background-agent-complete>\\n?([\\s\\S]*?)\\n?<\\/background-agent-complete>/,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (match?.[1]?.trim()) {\n\t\t\t\t\t\t\t\tsend(`🤖 *Background agent complete:*\\n${match[1].trim()}`, true);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Only display assistant messages — user messages are echoed back by RPC\n\t\t\tif (msg?.role !== \"assistant\") break;\n\t\t\tconst content = msg?.content;\n\t\t\tif (!content || !Array.isArray(content)) break;\n\n\t\t\tfor (const block of content) {\n\t\t\t\t// Display thinking blocks (collapsed summary)\n\t\t\t\tif (block.type === \"thinking\" && block.thinking?.trim() && !block.redacted) {\n\t\t\t\t\tconst thinking = block.thinking.trim();\n\t\t\t\t\tsend(`💭 _${thinking}_`, true);\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"text\" && block.text?.trim()) {\n\t\t\t\t\tconst text = block.text.trim();\n\n\t\t\t\t\t// Flush accumulated tools as permanent summary\n\t\t\t\t\tif (state.toolsSinceText.length > 0) {\n\t\t\t\t\t\tconst summary = `📋 *${state.toolsSinceText.length} tools*:\\n${state.toolsSinceText.join(\"\\n\")}`;\n\t\t\t\t\t\tsend(summary, true);\n\t\t\t\t\t\tstate.toolsSinceText = [];\n\t\t\t\t\t}\n\n\t\t\t\t\t// Send the text as a permanent message\n\t\t\t\t\tstate.textBlocks.push(text);\n\n\t\t\t\t\t// Check for file send markers\n\t\t\t\t\tconst [cleanText, filePaths] = extractSendFiles(text);\n\t\t\t\t\tif (cleanText) {\n\t\t\t\t\t\tsend(cleanText, true);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Send any requested files (silently skip non-existent paths —\n\t\t\t\t\t// the pattern may appear in explanatory text)\n\t\t\t\t\tfor (const filePath of filePaths) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tif (existsSync(filePath)) {\n\t\t\t\t\t\t\t\tawait api.sendDocument(state.chatId, new InputFile(filePath));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tlog(`[EVENTS] Failed to send file ${filePath}: ${e}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Feed event to buddy controller for context capture + reactions\n\t\t\tstate.buddyController?.handleEvent(event);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"tasks_update\": {\n\t\t\tstate.tasks = (event as any).tasks || [];\n\t\t\tupdateStatus(state);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"background_agent_start\": {\n\t\t\tconst { agentId, agentType, taskSummary } = event as any;\n\t\t\tstate.backgroundAgents.set(agentId, {\n\t\t\t\tagentId,\n\t\t\t\tagentType,\n\t\t\t\ttaskSummary,\n\t\t\t\tstartTime: Date.now(),\n\t\t\t});\n\t\t\tupdateStatus(state);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"background_agent_end\": {\n\t\t\tconst { agentId } = event as any;\n\t\t\tstate.backgroundAgents.delete(agentId);\n\t\t\t// Background agents completing does not end the parent's turn.\n\t\t\t// Only agent_end sets done — same as TUI behavior.\n\t\t\tupdateStatus(state);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"auto_compaction_start\": {\n\t\t\tupdateStatusText(state, \"🗜 _Compacting context..._\");\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"auto_compaction_end\": {\n\t\t\tconst result = (event as any).result;\n\t\t\tif (result) {\n\t\t\t\tconst before = result.tokensBefore || 0;\n\t\t\t\tconst msg = `🗜 Context compacted (was ${Math.round(before / 1000)}k tokens)`;\n\t\t\t\tsend(msg);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\t// =====================================================================\n\t\t// Auto-retry — prevents agent_end from marking done during retries\n\t\t// =====================================================================\n\n\t\tcase \"auto_retry_start\": {\n\t\t\tconst { attempt, maxAttempts, delayMs, errorMessage } = event as any;\n\t\t\tstate.retryInProgress = true;\n\t\t\tstate.pendingRetry = false; // Layer 1 has taken over from Layer 2\n\t\t\tstate.retryAttempt = attempt;\n\t\t\tconst delaySec = Math.round(delayMs / 1000);\n\t\t\tconst shortErr = errorMessage?.length > 80 ? `${errorMessage.slice(0, 80)}…` : errorMessage;\n\t\t\tupdateStatusText(state, `🔄 _Retrying (${attempt}/${maxAttempts}) in ${delaySec}s — ${shortErr || \"error\"}_`);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"auto_retry_end\": {\n\t\t\tconst { success, attempt, finalError } = event as any;\n\t\t\tstate.retryInProgress = false;\n\t\t\tstate.retryAttempt = 0;\n\t\t\tif (!success && finalError) {\n\t\t\t\t// Max retries exhausted — show final error\n\t\t\t\tsend(`❌ _Retry failed (${attempt} attempts):_ ${finalError}`, true);\n\t\t\t}\n\t\t\t// On success, the retry's agent_start/agent_end cycle will handle display normally\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"agent_end\": {\n\t\t\t// Flush any remaining tools\n\t\t\tif (state.toolsSinceText.length > 0) {\n\t\t\t\tconst summary = `📋 *${state.toolsSinceText.length} tools*:\\n${state.toolsSinceText.join(\"\\n\")}`;\n\t\t\t\tsend(summary, true);\n\t\t\t\tstate.toolsSinceText = [];\n\t\t\t}\n\n\t\t\t// Check for error in agent_end messages\n\t\t\tconst errorMsg = (event.messages as any[])?.find(\n\t\t\t\t(m: any) => m.stopReason === \"error\" || m.stopReason === \"aborted\",\n\t\t\t);\n\n\t\t\t// Layer 2 (defensive): If this error looks retryable and we're not already\n\t\t\t// tracking a retry via Layer 1, don't mark done — the core will auto-retry\n\t\t\t// and emit a new agent_start/agent_end cycle.\n\t\t\tconst errorIsRetryable = errorMsg?.errorMessage && isRetryableError(errorMsg.errorMessage);\n\n\t\t\tif (errorMsg?.errorMessage) {\n\t\t\t\t// Suppress the scary error message during retry — user already saw the\n\t\t\t\t// auto_retry_start status. Only show the error if retry tracking missed it\n\t\t\t\t// (defensive: shouldn't happen, but better than silence).\n\t\t\t\tif (!state.retryInProgress && !errorIsRetryable) {\n\t\t\t\t\tconst provider = errorMsg.provider ? `${errorMsg.provider}/${errorMsg.model}` : \"\";\n\t\t\t\t\tconst prefix = provider ? `${provider}: ` : \"\";\n\t\t\t\t\tconst errLower = errorMsg.errorMessage.toLowerCase();\n\t\t\t\t\tconst hint =\n\t\t\t\t\t\terrLower.includes(\"connection\") || errLower.includes(\"timeout\") || errLower.includes(\"network\")\n\t\t\t\t\t\t\t? \"\\n_Provider may be down — try /model to switch._\"\n\t\t\t\t\t\t\t: \"\";\n\t\t\t\t\tsend(`❌ ${prefix}${errorMsg.errorMessage}${hint}`, true);\n\t\t\t\t}\n\t\t\t} else if (state.textBlocks.length === 0 && state.backgroundAgents.size === 0) {\n\t\t\t\t// Only show \"(No response)\" when truly done — not between agent cycles\n\t\t\t\tif (!state.retryInProgress && !errorIsRetryable) {\n\t\t\t\t\tsend(\"(No response)\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Feed event to buddy controller for context capture + reactions\n\t\t\tstate.buddyController?.handleEvent(event);\n\n\t\t\t// Don't mark done if auto-retry is in progress (Layer 1) or the error\n\t\t\t// looks retryable (Layer 2 — defensive catch in case events were missed).\n\t\t\t// The core will emit a new agent_start/agent_end cycle for the retry.\n\t\t\tif (state.retryInProgress || errorIsRetryable) {\n\t\t\t\t// Signal that a retry is expected — the completion check in\n\t\t\t\t// ensureSubscribed needs this because it runs in the eventChain\n\t\t\t\t// BEFORE auto_retry_start has been processed.\n\t\t\t\tif (errorIsRetryable) state.pendingRetry = true;\n\t\t\t\t// Reset per-cycle state for the next agent loop\n\t\t\t\tstate.textBlocks = [];\n\t\t\t\tstate.toolCount = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// If background agents are still running, keep the subscription alive\n\t\t\t// and reset per-cycle state for the next agent loop\n\t\t\tif (state.backgroundAgents.size > 0) {\n\t\t\t\tstate.textBlocks = [];\n\t\t\t\tstate.toolCount = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Delete ephemeral status before signaling done\n\t\t\tif (state.statusMessageId) {\n\t\t\t\tawait state.editor.flush(state.chatId, state.statusMessageId);\n\t\t\t\tawait safeDelete(api, state.chatId, state.statusMessageId);\n\t\t\t\tstate.statusMessageId = null;\n\t\t\t}\n\n\t\t\t// Clean up editor\n\t\t\tstate.editor.clear();\n\n\t\t\t// Signal done AFTER cleanup — waitForCompletion checks this flag,\n\t\t\t// so setting it last ensures status message is deleted before DONE is sent\n\t\t\tstate.done = true;\n\t\t\tbreak;\n\t\t}\n\n\t\t// Handle error responses that leak through RPC (async prompt errors)\n\t\tcase \"response\": {\n\t\t\tconst resp = event as any;\n\t\t\tif (!resp.success && resp.error) {\n\t\t\t\tsend(`❌ ${resp.error}`, true);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/**\n * Build and push a status update to the ephemeral message.\n */\nfunction updateStatus(state: EventDisplayState): void {\n\tif (!state.statusMessageId) return;\n\n\tconst parts: string[] = [];\n\n\t// Tool count header\n\tif (state.toolCount > 0) {\n\t\tparts.push(`🔧 *Tool ${state.toolCount}*`);\n\t}\n\n\t// Task list\n\tif (state.tasks.length > 0) {\n\t\tparts.push(formatTaskList(state.tasks));\n\t}\n\n\t// Background agents\n\tif (state.backgroundAgents.size > 0) {\n\t\tfor (const agent of state.backgroundAgents.values()) {\n\t\t\tparts.push(`🤖 *${agent.agentType}*: ${agent.taskSummary.slice(0, 200)}`);\n\t\t}\n\t}\n\n\t// Recent tools (last 5)\n\tif (state.toolsSinceText.length > 0) {\n\t\tconst recent = state.toolsSinceText.slice(-5);\n\t\tparts.push(recent.join(\"\\n\\n\"));\n\t}\n\n\tif (parts.length === 0) return;\n\n\tconst text = parts.join(\"\\n\\n\").slice(0, 4000);\n\tstate.editor.edit(state.chatId, state.statusMessageId, text);\n}\n\nfunction updateStatusText(state: EventDisplayState, text: string): void {\n\tif (!state.statusMessageId) return;\n\tstate.editor.edit(state.chatId, state.statusMessageId, text);\n}\n"]}
@@ -77,7 +77,7 @@ function formatTaskList(tasks) {
77
77
  * Check if an error message looks retryable (overloaded, rate limit, server errors).
78
78
  * Mirrors the core's _isRetryableError check as a defensive Layer 2.
79
79
  */
80
- const RETRYABLE_ERROR_PATTERN = /overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|timed? out|timeout|terminated|retry delay/i;
80
+ const RETRYABLE_ERROR_PATTERN = /overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|other side closed|fetch failed|ended without|upstream.?connect|reset before headers|socket hang up|timed? out|timeout|terminated|retry delay/i;
81
81
  function isRetryableError(errorMessage) {
82
82
  return RETRYABLE_ERROR_PATTERN.test(errorMessage);
83
83
  }
@@ -1 +1 @@
1
- {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/handlers/events.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Event display — translates RPC agent events into Telegram messages.\n *\n * Manages an ephemeral status message that shows tool use, task lists,\n * and subagent activity. Text from the agent is sent as permanent messages.\n */\n\nimport { existsSync } from \"node:fs\";\nimport type { Api } from \"grammy\";\nimport { InputFile } from \"grammy\";\nimport type { TrackedAgent } from \"../types.js\";\nimport { extractSendFiles } from \"../util/files.js\";\nimport { DebouncedEditor, log, safeDelete } from \"../util/telegram.js\";\n\n/** Callback to queue a message for delivery — never blocks the event chain */\nexport type SendFn = (text: string, long?: boolean) => void;\n\n/**\n * RPC events include both core AgentEvent and session-specific events\n * (tasks_update, background_agent_*, auto_compaction_*).\n * We type loosely here since the RPC client types onEvent as AgentEvent\n * but actually forwards all AgentSessionEvent types.\n */\ntype RpcEvent = { type: string; [key: string]: any };\n\n// Tool emoji mapping (tool names are lowercase in definitions)\nconst TOOL_EMOJI: Record<string, string> = {\n\tbash: \"🔧\",\n\tread: \"📖\",\n\tedit: \"✏️\",\n\twrite: \"📝\",\n\tgrep: \"🔎\",\n\tfind: \"🔍\",\n\tls: \"📂\",\n\tweb_search: \"🌐\",\n\tweb_fetch: \"🌐\",\n\tsubagent: \"🤖\",\n\ttasks_update: \"📋\",\n\tskill: \"⚡\",\n};\n\nfunction toolEmoji(name: string): string {\n\treturn TOOL_EMOJI[name] || \"🔧\";\n}\n\n/** Format a tool call for display */\nfunction formatTool(name: string, args: Record<string, any>): string {\n\tconst emoji = toolEmoji(name);\n\tswitch (name) {\n\t\tcase \"bash\": {\n\t\t\tconst cmd = args.command || \"\";\n\t\t\treturn `${emoji} *bash*\\n\\`${cmd.slice(0, 500)}\\``;\n\t\t}\n\t\tcase \"read\":\n\t\t\treturn `${emoji} *read*: \\`${args.path || \"?\"}\\``;\n\t\tcase \"edit\":\n\t\t\treturn `${emoji} *edit*: \\`${args.path || \"?\"}\\``;\n\t\tcase \"write\":\n\t\t\treturn `${emoji} *write*: \\`${args.path || \"?\"}\\``;\n\t\tcase \"grep\":\n\t\t\treturn `${emoji} *grep*: \\`${args.pattern || \"?\"}\\``;\n\t\tcase \"find\":\n\t\t\treturn `${emoji} *find*: \\`${args.pattern || \"?\"}\\``;\n\t\tcase \"ls\":\n\t\t\treturn `${emoji} *ls*: \\`${args.path || \".\"}\\``;\n\t\tcase \"web_search\":\n\t\t\treturn `${emoji} *web\\\\_search*: ${args.query || \"?\"}`;\n\t\tcase \"web_fetch\":\n\t\t\treturn `${emoji} *web\\\\_fetch*: ${(args.url || \"?\").slice(0, 80)}`;\n\t\tcase \"subagent\":\n\t\t\treturn `${emoji} *subagent* (${args.agent || \"?\"}): ${(args.task || args.tasks?.[0]?.task || \"?\").slice(0, 200)}`;\n\t\tcase \"skill\":\n\t\t\treturn `${emoji} *skill*: ${args.skill || \"?\"}`;\n\t\tdefault:\n\t\t\treturn `${emoji} *${name}*`;\n\t}\n}\n\n/** Format task list as checklist */\nfunction formatTaskList(tasks: Array<{ id: string; title: string; status: string }>): string {\n\tif (!tasks.length) return \"📋 *Tasks*: (empty)\";\n\tconst lines = [\"📋 *Tasks*:\"];\n\tfor (const task of tasks) {\n\t\tif (task.status === \"completed\") lines.push(` ✅ ${task.title}`);\n\t\telse if (task.status === \"in_progress\") lines.push(` 🔄 ${task.title}`);\n\t\telse lines.push(` ⬜ ${task.title}`);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\nexport interface EventDisplayState {\n\t/** Chat ID to send messages to */\n\tchatId: number;\n\t/** Message ID to reply to */\n\treplyToId: number;\n\t/** Ephemeral status message ID (edited in-place) */\n\tstatusMessageId: number | null;\n\t/** Tool messages accumulated since last text */\n\ttoolsSinceText: string[];\n\t/** Total tool count */\n\ttoolCount: number;\n\t/** All text blocks received */\n\ttextBlocks: string[];\n\t/** Current task list */\n\ttasks: Array<{ id: string; title: string; status: string }>;\n\t/** Background agents */\n\tbackgroundAgents: Map<string, TrackedAgent>;\n\t/** Whether agent has finished */\n\tdone: boolean;\n\t/** Debounced editor instance */\n\teditor: DebouncedEditor;\n\t/** Whether auto-retry is in progress (Layer 1: reactive — set by auto_retry_start) */\n\tretryInProgress: boolean;\n\t/** Whether a retry is expected (Layer 2: predictive — set by agent_end when error looks retryable) */\n\tpendingRetry: boolean;\n\t/** Current retry attempt number for display */\n\tretryAttempt: number;\n\t/** Buddy controller — receives agent events for context + reactions */\n\tbuddyController?: any;\n}\n\n/**\n * Check if an error message looks retryable (overloaded, rate limit, server errors).\n * Mirrors the core's _isRetryableError check as a defensive Layer 2.\n */\nconst RETRYABLE_ERROR_PATTERN =\n\t/overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|timed? out|timeout|terminated|retry delay/i;\n\nfunction isRetryableError(errorMessage: string): boolean {\n\treturn RETRYABLE_ERROR_PATTERN.test(errorMessage);\n}\n\n/**\n * Create a fresh event display state for a new agent run.\n */\nexport function createEventDisplay(\n\tapi: Api,\n\tchatId: number,\n\treplyToId: number,\n\tstatusMessageId: number | null,\n): EventDisplayState {\n\treturn {\n\t\tchatId,\n\t\treplyToId,\n\t\tstatusMessageId,\n\t\ttoolsSinceText: [],\n\t\ttoolCount: 0,\n\t\ttextBlocks: [],\n\t\ttasks: [],\n\t\tbackgroundAgents: new Map(),\n\t\tdone: false,\n\t\teditor: new DebouncedEditor(api),\n\t\tretryInProgress: false,\n\t\tpendingRetry: false,\n\t\tretryAttempt: 0,\n\t};\n}\n\n/**\n * Process an agent event and update the display.\n */\nexport async function handleAgentEvent(\n\tsend: SendFn,\n\tapi: Api,\n\tstate: EventDisplayState,\n\tevent: RpcEvent,\n): Promise<void> {\n\tswitch (event.type) {\n\t\tcase \"tool_execution_start\": {\n\t\t\tconst name = event.toolName || \"?\";\n\t\t\tconst args = event.args || {};\n\t\t\tstate.toolCount++;\n\n\t\t\t// tasks_update is shown via the separate tasks_update event — skip from tool summary\n\t\t\tif (name !== \"tasks_update\") {\n\t\t\t\tconst toolMsg = formatTool(name, args);\n\t\t\t\tstate.toolsSinceText.push(toolMsg);\n\t\t\t}\n\n\t\t\t// Update status with tool count and recent tools\n\t\t\tupdateStatus(state);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"tool_execution_end\": {\n\t\t\t// Feed event to buddy controller for context capture + error reactions\n\t\t\tstate.buddyController?.handleEvent(event);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"message_end\": {\n\t\t\tconst msg = event.message;\n\n\t\t\t// Show subagent results — the parent agent references these but the\n\t\t\t// Telegram user can't see them otherwise. Send the full content.\n\t\t\tif (msg?.role === \"toolResult\" && msg?.toolName === \"subagent\") {\n\t\t\t\tconst content = msg?.content;\n\t\t\t\tif (content && Array.isArray(content)) {\n\t\t\t\t\tfor (const block of content) {\n\t\t\t\t\t\tif (block.type === \"text\" && block.text?.trim()) {\n\t\t\t\t\t\t\tsend(`🤖 *Subagent result:*\\n${block.text.trim()}`, true);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Show background agent completion results — these arrive as user\n\t\t\t// messages injected by agent-session.ts via prompt()/steer() and\n\t\t\t// contain the actual subagent output the model sees.\n\t\t\tif (msg?.role === \"user\") {\n\t\t\t\tconst content = msg?.content;\n\t\t\t\tif (content && Array.isArray(content)) {\n\t\t\t\t\tfor (const block of content) {\n\t\t\t\t\t\tif (block.type === \"text\" && block.text?.includes(\"<background-agent-complete>\")) {\n\t\t\t\t\t\t\t// Extract the content between the XML tags\n\t\t\t\t\t\t\tconst match = block.text.match(\n\t\t\t\t\t\t\t\t/<background-agent-complete>\\n?([\\s\\S]*?)\\n?<\\/background-agent-complete>/,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (match?.[1]?.trim()) {\n\t\t\t\t\t\t\t\tsend(`🤖 *Background agent complete:*\\n${match[1].trim()}`, true);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Only display assistant messages — user messages are echoed back by RPC\n\t\t\tif (msg?.role !== \"assistant\") break;\n\t\t\tconst content = msg?.content;\n\t\t\tif (!content || !Array.isArray(content)) break;\n\n\t\t\tfor (const block of content) {\n\t\t\t\t// Display thinking blocks (collapsed summary)\n\t\t\t\tif (block.type === \"thinking\" && block.thinking?.trim() && !block.redacted) {\n\t\t\t\t\tconst thinking = block.thinking.trim();\n\t\t\t\t\tsend(`💭 _${thinking}_`, true);\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"text\" && block.text?.trim()) {\n\t\t\t\t\tconst text = block.text.trim();\n\n\t\t\t\t\t// Flush accumulated tools as permanent summary\n\t\t\t\t\tif (state.toolsSinceText.length > 0) {\n\t\t\t\t\t\tconst summary = `📋 *${state.toolsSinceText.length} tools*:\\n${state.toolsSinceText.join(\"\\n\")}`;\n\t\t\t\t\t\tsend(summary, true);\n\t\t\t\t\t\tstate.toolsSinceText = [];\n\t\t\t\t\t}\n\n\t\t\t\t\t// Send the text as a permanent message\n\t\t\t\t\tstate.textBlocks.push(text);\n\n\t\t\t\t\t// Check for file send markers\n\t\t\t\t\tconst [cleanText, filePaths] = extractSendFiles(text);\n\t\t\t\t\tif (cleanText) {\n\t\t\t\t\t\tsend(cleanText, true);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Send any requested files (silently skip non-existent paths —\n\t\t\t\t\t// the pattern may appear in explanatory text)\n\t\t\t\t\tfor (const filePath of filePaths) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tif (existsSync(filePath)) {\n\t\t\t\t\t\t\t\tawait api.sendDocument(state.chatId, new InputFile(filePath));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tlog(`[EVENTS] Failed to send file ${filePath}: ${e}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Feed event to buddy controller for context capture + reactions\n\t\t\tstate.buddyController?.handleEvent(event);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"tasks_update\": {\n\t\t\tstate.tasks = (event as any).tasks || [];\n\t\t\tupdateStatus(state);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"background_agent_start\": {\n\t\t\tconst { agentId, agentType, taskSummary } = event as any;\n\t\t\tstate.backgroundAgents.set(agentId, {\n\t\t\t\tagentId,\n\t\t\t\tagentType,\n\t\t\t\ttaskSummary,\n\t\t\t\tstartTime: Date.now(),\n\t\t\t});\n\t\t\tupdateStatus(state);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"background_agent_end\": {\n\t\t\tconst { agentId } = event as any;\n\t\t\tstate.backgroundAgents.delete(agentId);\n\t\t\t// Background agents completing does not end the parent's turn.\n\t\t\t// Only agent_end sets done — same as TUI behavior.\n\t\t\tupdateStatus(state);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"auto_compaction_start\": {\n\t\t\tupdateStatusText(state, \"🗜 _Compacting context..._\");\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"auto_compaction_end\": {\n\t\t\tconst result = (event as any).result;\n\t\t\tif (result) {\n\t\t\t\tconst before = result.tokensBefore || 0;\n\t\t\t\tconst msg = `🗜 Context compacted (was ${Math.round(before / 1000)}k tokens)`;\n\t\t\t\tsend(msg);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\t// =====================================================================\n\t\t// Auto-retry — prevents agent_end from marking done during retries\n\t\t// =====================================================================\n\n\t\tcase \"auto_retry_start\": {\n\t\t\tconst { attempt, maxAttempts, delayMs, errorMessage } = event as any;\n\t\t\tstate.retryInProgress = true;\n\t\t\tstate.pendingRetry = false; // Layer 1 has taken over from Layer 2\n\t\t\tstate.retryAttempt = attempt;\n\t\t\tconst delaySec = Math.round(delayMs / 1000);\n\t\t\tconst shortErr = errorMessage?.length > 80 ? `${errorMessage.slice(0, 80)}…` : errorMessage;\n\t\t\tupdateStatusText(state, `🔄 _Retrying (${attempt}/${maxAttempts}) in ${delaySec}s — ${shortErr || \"error\"}_`);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"auto_retry_end\": {\n\t\t\tconst { success, attempt, finalError } = event as any;\n\t\t\tstate.retryInProgress = false;\n\t\t\tstate.retryAttempt = 0;\n\t\t\tif (!success && finalError) {\n\t\t\t\t// Max retries exhausted — show final error\n\t\t\t\tsend(`❌ _Retry failed (${attempt} attempts):_ ${finalError}`, true);\n\t\t\t}\n\t\t\t// On success, the retry's agent_start/agent_end cycle will handle display normally\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"agent_end\": {\n\t\t\t// Flush any remaining tools\n\t\t\tif (state.toolsSinceText.length > 0) {\n\t\t\t\tconst summary = `📋 *${state.toolsSinceText.length} tools*:\\n${state.toolsSinceText.join(\"\\n\")}`;\n\t\t\t\tsend(summary, true);\n\t\t\t\tstate.toolsSinceText = [];\n\t\t\t}\n\n\t\t\t// Check for error in agent_end messages\n\t\t\tconst errorMsg = (event.messages as any[])?.find(\n\t\t\t\t(m: any) => m.stopReason === \"error\" || m.stopReason === \"aborted\",\n\t\t\t);\n\n\t\t\t// Layer 2 (defensive): If this error looks retryable and we're not already\n\t\t\t// tracking a retry via Layer 1, don't mark done — the core will auto-retry\n\t\t\t// and emit a new agent_start/agent_end cycle.\n\t\t\tconst errorIsRetryable = errorMsg?.errorMessage && isRetryableError(errorMsg.errorMessage);\n\n\t\t\tif (errorMsg?.errorMessage) {\n\t\t\t\t// Suppress the scary error message during retry — user already saw the\n\t\t\t\t// auto_retry_start status. Only show the error if retry tracking missed it\n\t\t\t\t// (defensive: shouldn't happen, but better than silence).\n\t\t\t\tif (!state.retryInProgress && !errorIsRetryable) {\n\t\t\t\t\tconst provider = errorMsg.provider ? `${errorMsg.provider}/${errorMsg.model}` : \"\";\n\t\t\t\t\tconst prefix = provider ? `${provider}: ` : \"\";\n\t\t\t\t\tconst errLower = errorMsg.errorMessage.toLowerCase();\n\t\t\t\t\tconst hint =\n\t\t\t\t\t\terrLower.includes(\"connection\") || errLower.includes(\"timeout\") || errLower.includes(\"network\")\n\t\t\t\t\t\t\t? \"\\n_Provider may be down — try /model to switch._\"\n\t\t\t\t\t\t\t: \"\";\n\t\t\t\t\tsend(`❌ ${prefix}${errorMsg.errorMessage}${hint}`, true);\n\t\t\t\t}\n\t\t\t} else if (state.textBlocks.length === 0 && state.backgroundAgents.size === 0) {\n\t\t\t\t// Only show \"(No response)\" when truly done — not between agent cycles\n\t\t\t\tif (!state.retryInProgress && !errorIsRetryable) {\n\t\t\t\t\tsend(\"(No response)\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Feed event to buddy controller for context capture + reactions\n\t\t\tstate.buddyController?.handleEvent(event);\n\n\t\t\t// Don't mark done if auto-retry is in progress (Layer 1) or the error\n\t\t\t// looks retryable (Layer 2 — defensive catch in case events were missed).\n\t\t\t// The core will emit a new agent_start/agent_end cycle for the retry.\n\t\t\tif (state.retryInProgress || errorIsRetryable) {\n\t\t\t\t// Signal that a retry is expected — the completion check in\n\t\t\t\t// ensureSubscribed needs this because it runs in the eventChain\n\t\t\t\t// BEFORE auto_retry_start has been processed.\n\t\t\t\tif (errorIsRetryable) state.pendingRetry = true;\n\t\t\t\t// Reset per-cycle state for the next agent loop\n\t\t\t\tstate.textBlocks = [];\n\t\t\t\tstate.toolCount = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// If background agents are still running, keep the subscription alive\n\t\t\t// and reset per-cycle state for the next agent loop\n\t\t\tif (state.backgroundAgents.size > 0) {\n\t\t\t\tstate.textBlocks = [];\n\t\t\t\tstate.toolCount = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Delete ephemeral status before signaling done\n\t\t\tif (state.statusMessageId) {\n\t\t\t\tawait state.editor.flush(state.chatId, state.statusMessageId);\n\t\t\t\tawait safeDelete(api, state.chatId, state.statusMessageId);\n\t\t\t\tstate.statusMessageId = null;\n\t\t\t}\n\n\t\t\t// Clean up editor\n\t\t\tstate.editor.clear();\n\n\t\t\t// Signal done AFTER cleanup — waitForCompletion checks this flag,\n\t\t\t// so setting it last ensures status message is deleted before DONE is sent\n\t\t\tstate.done = true;\n\t\t\tbreak;\n\t\t}\n\n\t\t// Handle error responses that leak through RPC (async prompt errors)\n\t\tcase \"response\": {\n\t\t\tconst resp = event as any;\n\t\t\tif (!resp.success && resp.error) {\n\t\t\t\tsend(`❌ ${resp.error}`, true);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/**\n * Build and push a status update to the ephemeral message.\n */\nfunction updateStatus(state: EventDisplayState): void {\n\tif (!state.statusMessageId) return;\n\n\tconst parts: string[] = [];\n\n\t// Tool count header\n\tif (state.toolCount > 0) {\n\t\tparts.push(`🔧 *Tool ${state.toolCount}*`);\n\t}\n\n\t// Task list\n\tif (state.tasks.length > 0) {\n\t\tparts.push(formatTaskList(state.tasks));\n\t}\n\n\t// Background agents\n\tif (state.backgroundAgents.size > 0) {\n\t\tfor (const agent of state.backgroundAgents.values()) {\n\t\t\tparts.push(`🤖 *${agent.agentType}*: ${agent.taskSummary.slice(0, 200)}`);\n\t\t}\n\t}\n\n\t// Recent tools (last 5)\n\tif (state.toolsSinceText.length > 0) {\n\t\tconst recent = state.toolsSinceText.slice(-5);\n\t\tparts.push(recent.join(\"\\n\\n\"));\n\t}\n\n\tif (parts.length === 0) return;\n\n\tconst text = parts.join(\"\\n\\n\").slice(0, 4000);\n\tstate.editor.edit(state.chatId, state.statusMessageId, text);\n}\n\nfunction updateStatusText(state: EventDisplayState, text: string): void {\n\tif (!state.statusMessageId) return;\n\tstate.editor.edit(state.chatId, state.statusMessageId, text);\n}\n"]}
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/handlers/events.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Event display — translates RPC agent events into Telegram messages.\n *\n * Manages an ephemeral status message that shows tool use, task lists,\n * and subagent activity. Text from the agent is sent as permanent messages.\n */\n\nimport { existsSync } from \"node:fs\";\nimport type { Api } from \"grammy\";\nimport { InputFile } from \"grammy\";\nimport type { TrackedAgent } from \"../types.js\";\nimport { extractSendFiles } from \"../util/files.js\";\nimport { DebouncedEditor, log, safeDelete } from \"../util/telegram.js\";\n\n/** Callback to queue a message for delivery — never blocks the event chain */\nexport type SendFn = (text: string, long?: boolean) => void;\n\n/**\n * RPC events include both core AgentEvent and session-specific events\n * (tasks_update, background_agent_*, auto_compaction_*).\n * We type loosely here since the RPC client types onEvent as AgentEvent\n * but actually forwards all AgentSessionEvent types.\n */\ntype RpcEvent = { type: string; [key: string]: any };\n\n// Tool emoji mapping (tool names are lowercase in definitions)\nconst TOOL_EMOJI: Record<string, string> = {\n\tbash: \"🔧\",\n\tread: \"📖\",\n\tedit: \"✏️\",\n\twrite: \"📝\",\n\tgrep: \"🔎\",\n\tfind: \"🔍\",\n\tls: \"📂\",\n\tweb_search: \"🌐\",\n\tweb_fetch: \"🌐\",\n\tsubagent: \"🤖\",\n\ttasks_update: \"📋\",\n\tskill: \"⚡\",\n};\n\nfunction toolEmoji(name: string): string {\n\treturn TOOL_EMOJI[name] || \"🔧\";\n}\n\n/** Format a tool call for display */\nfunction formatTool(name: string, args: Record<string, any>): string {\n\tconst emoji = toolEmoji(name);\n\tswitch (name) {\n\t\tcase \"bash\": {\n\t\t\tconst cmd = args.command || \"\";\n\t\t\treturn `${emoji} *bash*\\n\\`${cmd.slice(0, 500)}\\``;\n\t\t}\n\t\tcase \"read\":\n\t\t\treturn `${emoji} *read*: \\`${args.path || \"?\"}\\``;\n\t\tcase \"edit\":\n\t\t\treturn `${emoji} *edit*: \\`${args.path || \"?\"}\\``;\n\t\tcase \"write\":\n\t\t\treturn `${emoji} *write*: \\`${args.path || \"?\"}\\``;\n\t\tcase \"grep\":\n\t\t\treturn `${emoji} *grep*: \\`${args.pattern || \"?\"}\\``;\n\t\tcase \"find\":\n\t\t\treturn `${emoji} *find*: \\`${args.pattern || \"?\"}\\``;\n\t\tcase \"ls\":\n\t\t\treturn `${emoji} *ls*: \\`${args.path || \".\"}\\``;\n\t\tcase \"web_search\":\n\t\t\treturn `${emoji} *web\\\\_search*: ${args.query || \"?\"}`;\n\t\tcase \"web_fetch\":\n\t\t\treturn `${emoji} *web\\\\_fetch*: ${(args.url || \"?\").slice(0, 80)}`;\n\t\tcase \"subagent\":\n\t\t\treturn `${emoji} *subagent* (${args.agent || \"?\"}): ${(args.task || args.tasks?.[0]?.task || \"?\").slice(0, 200)}`;\n\t\tcase \"skill\":\n\t\t\treturn `${emoji} *skill*: ${args.skill || \"?\"}`;\n\t\tdefault:\n\t\t\treturn `${emoji} *${name}*`;\n\t}\n}\n\n/** Format task list as checklist */\nfunction formatTaskList(tasks: Array<{ id: string; title: string; status: string }>): string {\n\tif (!tasks.length) return \"📋 *Tasks*: (empty)\";\n\tconst lines = [\"📋 *Tasks*:\"];\n\tfor (const task of tasks) {\n\t\tif (task.status === \"completed\") lines.push(` ✅ ${task.title}`);\n\t\telse if (task.status === \"in_progress\") lines.push(` 🔄 ${task.title}`);\n\t\telse lines.push(` ⬜ ${task.title}`);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\nexport interface EventDisplayState {\n\t/** Chat ID to send messages to */\n\tchatId: number;\n\t/** Message ID to reply to */\n\treplyToId: number;\n\t/** Ephemeral status message ID (edited in-place) */\n\tstatusMessageId: number | null;\n\t/** Tool messages accumulated since last text */\n\ttoolsSinceText: string[];\n\t/** Total tool count */\n\ttoolCount: number;\n\t/** All text blocks received */\n\ttextBlocks: string[];\n\t/** Current task list */\n\ttasks: Array<{ id: string; title: string; status: string }>;\n\t/** Background agents */\n\tbackgroundAgents: Map<string, TrackedAgent>;\n\t/** Whether agent has finished */\n\tdone: boolean;\n\t/** Debounced editor instance */\n\teditor: DebouncedEditor;\n\t/** Whether auto-retry is in progress (Layer 1: reactive — set by auto_retry_start) */\n\tretryInProgress: boolean;\n\t/** Whether a retry is expected (Layer 2: predictive — set by agent_end when error looks retryable) */\n\tpendingRetry: boolean;\n\t/** Current retry attempt number for display */\n\tretryAttempt: number;\n\t/** Buddy controller — receives agent events for context + reactions */\n\tbuddyController?: any;\n}\n\n/**\n * Check if an error message looks retryable (overloaded, rate limit, server errors).\n * Mirrors the core's _isRetryableError check as a defensive Layer 2.\n */\nconst RETRYABLE_ERROR_PATTERN =\n\t/overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|other side closed|fetch failed|ended without|upstream.?connect|reset before headers|socket hang up|timed? out|timeout|terminated|retry delay/i;\n\nfunction isRetryableError(errorMessage: string): boolean {\n\treturn RETRYABLE_ERROR_PATTERN.test(errorMessage);\n}\n\n/**\n * Create a fresh event display state for a new agent run.\n */\nexport function createEventDisplay(\n\tapi: Api,\n\tchatId: number,\n\treplyToId: number,\n\tstatusMessageId: number | null,\n): EventDisplayState {\n\treturn {\n\t\tchatId,\n\t\treplyToId,\n\t\tstatusMessageId,\n\t\ttoolsSinceText: [],\n\t\ttoolCount: 0,\n\t\ttextBlocks: [],\n\t\ttasks: [],\n\t\tbackgroundAgents: new Map(),\n\t\tdone: false,\n\t\teditor: new DebouncedEditor(api),\n\t\tretryInProgress: false,\n\t\tpendingRetry: false,\n\t\tretryAttempt: 0,\n\t};\n}\n\n/**\n * Process an agent event and update the display.\n */\nexport async function handleAgentEvent(\n\tsend: SendFn,\n\tapi: Api,\n\tstate: EventDisplayState,\n\tevent: RpcEvent,\n): Promise<void> {\n\tswitch (event.type) {\n\t\tcase \"tool_execution_start\": {\n\t\t\tconst name = event.toolName || \"?\";\n\t\t\tconst args = event.args || {};\n\t\t\tstate.toolCount++;\n\n\t\t\t// tasks_update is shown via the separate tasks_update event — skip from tool summary\n\t\t\tif (name !== \"tasks_update\") {\n\t\t\t\tconst toolMsg = formatTool(name, args);\n\t\t\t\tstate.toolsSinceText.push(toolMsg);\n\t\t\t}\n\n\t\t\t// Update status with tool count and recent tools\n\t\t\tupdateStatus(state);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"tool_execution_end\": {\n\t\t\t// Feed event to buddy controller for context capture + error reactions\n\t\t\tstate.buddyController?.handleEvent(event);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"message_end\": {\n\t\t\tconst msg = event.message;\n\n\t\t\t// Show subagent results — the parent agent references these but the\n\t\t\t// Telegram user can't see them otherwise. Send the full content.\n\t\t\tif (msg?.role === \"toolResult\" && msg?.toolName === \"subagent\") {\n\t\t\t\tconst content = msg?.content;\n\t\t\t\tif (content && Array.isArray(content)) {\n\t\t\t\t\tfor (const block of content) {\n\t\t\t\t\t\tif (block.type === \"text\" && block.text?.trim()) {\n\t\t\t\t\t\t\tsend(`🤖 *Subagent result:*\\n${block.text.trim()}`, true);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Show background agent completion results — these arrive as user\n\t\t\t// messages injected by agent-session.ts via prompt()/steer() and\n\t\t\t// contain the actual subagent output the model sees.\n\t\t\tif (msg?.role === \"user\") {\n\t\t\t\tconst content = msg?.content;\n\t\t\t\tif (content && Array.isArray(content)) {\n\t\t\t\t\tfor (const block of content) {\n\t\t\t\t\t\tif (block.type === \"text\" && block.text?.includes(\"<background-agent-complete>\")) {\n\t\t\t\t\t\t\t// Extract the content between the XML tags\n\t\t\t\t\t\t\tconst match = block.text.match(\n\t\t\t\t\t\t\t\t/<background-agent-complete>\\n?([\\s\\S]*?)\\n?<\\/background-agent-complete>/,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (match?.[1]?.trim()) {\n\t\t\t\t\t\t\t\tsend(`🤖 *Background agent complete:*\\n${match[1].trim()}`, true);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Only display assistant messages — user messages are echoed back by RPC\n\t\t\tif (msg?.role !== \"assistant\") break;\n\t\t\tconst content = msg?.content;\n\t\t\tif (!content || !Array.isArray(content)) break;\n\n\t\t\tfor (const block of content) {\n\t\t\t\t// Display thinking blocks (collapsed summary)\n\t\t\t\tif (block.type === \"thinking\" && block.thinking?.trim() && !block.redacted) {\n\t\t\t\t\tconst thinking = block.thinking.trim();\n\t\t\t\t\tsend(`💭 _${thinking}_`, true);\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"text\" && block.text?.trim()) {\n\t\t\t\t\tconst text = block.text.trim();\n\n\t\t\t\t\t// Flush accumulated tools as permanent summary\n\t\t\t\t\tif (state.toolsSinceText.length > 0) {\n\t\t\t\t\t\tconst summary = `📋 *${state.toolsSinceText.length} tools*:\\n${state.toolsSinceText.join(\"\\n\")}`;\n\t\t\t\t\t\tsend(summary, true);\n\t\t\t\t\t\tstate.toolsSinceText = [];\n\t\t\t\t\t}\n\n\t\t\t\t\t// Send the text as a permanent message\n\t\t\t\t\tstate.textBlocks.push(text);\n\n\t\t\t\t\t// Check for file send markers\n\t\t\t\t\tconst [cleanText, filePaths] = extractSendFiles(text);\n\t\t\t\t\tif (cleanText) {\n\t\t\t\t\t\tsend(cleanText, true);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Send any requested files (silently skip non-existent paths —\n\t\t\t\t\t// the pattern may appear in explanatory text)\n\t\t\t\t\tfor (const filePath of filePaths) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tif (existsSync(filePath)) {\n\t\t\t\t\t\t\t\tawait api.sendDocument(state.chatId, new InputFile(filePath));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tlog(`[EVENTS] Failed to send file ${filePath}: ${e}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Feed event to buddy controller for context capture + reactions\n\t\t\tstate.buddyController?.handleEvent(event);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"tasks_update\": {\n\t\t\tstate.tasks = (event as any).tasks || [];\n\t\t\tupdateStatus(state);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"background_agent_start\": {\n\t\t\tconst { agentId, agentType, taskSummary } = event as any;\n\t\t\tstate.backgroundAgents.set(agentId, {\n\t\t\t\tagentId,\n\t\t\t\tagentType,\n\t\t\t\ttaskSummary,\n\t\t\t\tstartTime: Date.now(),\n\t\t\t});\n\t\t\tupdateStatus(state);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"background_agent_end\": {\n\t\t\tconst { agentId } = event as any;\n\t\t\tstate.backgroundAgents.delete(agentId);\n\t\t\t// Background agents completing does not end the parent's turn.\n\t\t\t// Only agent_end sets done — same as TUI behavior.\n\t\t\tupdateStatus(state);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"auto_compaction_start\": {\n\t\t\tupdateStatusText(state, \"🗜 _Compacting context..._\");\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"auto_compaction_end\": {\n\t\t\tconst result = (event as any).result;\n\t\t\tif (result) {\n\t\t\t\tconst before = result.tokensBefore || 0;\n\t\t\t\tconst msg = `🗜 Context compacted (was ${Math.round(before / 1000)}k tokens)`;\n\t\t\t\tsend(msg);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\t// =====================================================================\n\t\t// Auto-retry — prevents agent_end from marking done during retries\n\t\t// =====================================================================\n\n\t\tcase \"auto_retry_start\": {\n\t\t\tconst { attempt, maxAttempts, delayMs, errorMessage } = event as any;\n\t\t\tstate.retryInProgress = true;\n\t\t\tstate.pendingRetry = false; // Layer 1 has taken over from Layer 2\n\t\t\tstate.retryAttempt = attempt;\n\t\t\tconst delaySec = Math.round(delayMs / 1000);\n\t\t\tconst shortErr = errorMessage?.length > 80 ? `${errorMessage.slice(0, 80)}…` : errorMessage;\n\t\t\tupdateStatusText(state, `🔄 _Retrying (${attempt}/${maxAttempts}) in ${delaySec}s — ${shortErr || \"error\"}_`);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"auto_retry_end\": {\n\t\t\tconst { success, attempt, finalError } = event as any;\n\t\t\tstate.retryInProgress = false;\n\t\t\tstate.retryAttempt = 0;\n\t\t\tif (!success && finalError) {\n\t\t\t\t// Max retries exhausted — show final error\n\t\t\t\tsend(`❌ _Retry failed (${attempt} attempts):_ ${finalError}`, true);\n\t\t\t}\n\t\t\t// On success, the retry's agent_start/agent_end cycle will handle display normally\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"agent_end\": {\n\t\t\t// Flush any remaining tools\n\t\t\tif (state.toolsSinceText.length > 0) {\n\t\t\t\tconst summary = `📋 *${state.toolsSinceText.length} tools*:\\n${state.toolsSinceText.join(\"\\n\")}`;\n\t\t\t\tsend(summary, true);\n\t\t\t\tstate.toolsSinceText = [];\n\t\t\t}\n\n\t\t\t// Check for error in agent_end messages\n\t\t\tconst errorMsg = (event.messages as any[])?.find(\n\t\t\t\t(m: any) => m.stopReason === \"error\" || m.stopReason === \"aborted\",\n\t\t\t);\n\n\t\t\t// Layer 2 (defensive): If this error looks retryable and we're not already\n\t\t\t// tracking a retry via Layer 1, don't mark done — the core will auto-retry\n\t\t\t// and emit a new agent_start/agent_end cycle.\n\t\t\tconst errorIsRetryable = errorMsg?.errorMessage && isRetryableError(errorMsg.errorMessage);\n\n\t\t\tif (errorMsg?.errorMessage) {\n\t\t\t\t// Suppress the scary error message during retry — user already saw the\n\t\t\t\t// auto_retry_start status. Only show the error if retry tracking missed it\n\t\t\t\t// (defensive: shouldn't happen, but better than silence).\n\t\t\t\tif (!state.retryInProgress && !errorIsRetryable) {\n\t\t\t\t\tconst provider = errorMsg.provider ? `${errorMsg.provider}/${errorMsg.model}` : \"\";\n\t\t\t\t\tconst prefix = provider ? `${provider}: ` : \"\";\n\t\t\t\t\tconst errLower = errorMsg.errorMessage.toLowerCase();\n\t\t\t\t\tconst hint =\n\t\t\t\t\t\terrLower.includes(\"connection\") || errLower.includes(\"timeout\") || errLower.includes(\"network\")\n\t\t\t\t\t\t\t? \"\\n_Provider may be down — try /model to switch._\"\n\t\t\t\t\t\t\t: \"\";\n\t\t\t\t\tsend(`❌ ${prefix}${errorMsg.errorMessage}${hint}`, true);\n\t\t\t\t}\n\t\t\t} else if (state.textBlocks.length === 0 && state.backgroundAgents.size === 0) {\n\t\t\t\t// Only show \"(No response)\" when truly done — not between agent cycles\n\t\t\t\tif (!state.retryInProgress && !errorIsRetryable) {\n\t\t\t\t\tsend(\"(No response)\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Feed event to buddy controller for context capture + reactions\n\t\t\tstate.buddyController?.handleEvent(event);\n\n\t\t\t// Don't mark done if auto-retry is in progress (Layer 1) or the error\n\t\t\t// looks retryable (Layer 2 — defensive catch in case events were missed).\n\t\t\t// The core will emit a new agent_start/agent_end cycle for the retry.\n\t\t\tif (state.retryInProgress || errorIsRetryable) {\n\t\t\t\t// Signal that a retry is expected — the completion check in\n\t\t\t\t// ensureSubscribed needs this because it runs in the eventChain\n\t\t\t\t// BEFORE auto_retry_start has been processed.\n\t\t\t\tif (errorIsRetryable) state.pendingRetry = true;\n\t\t\t\t// Reset per-cycle state for the next agent loop\n\t\t\t\tstate.textBlocks = [];\n\t\t\t\tstate.toolCount = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// If background agents are still running, keep the subscription alive\n\t\t\t// and reset per-cycle state for the next agent loop\n\t\t\tif (state.backgroundAgents.size > 0) {\n\t\t\t\tstate.textBlocks = [];\n\t\t\t\tstate.toolCount = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Delete ephemeral status before signaling done\n\t\t\tif (state.statusMessageId) {\n\t\t\t\tawait state.editor.flush(state.chatId, state.statusMessageId);\n\t\t\t\tawait safeDelete(api, state.chatId, state.statusMessageId);\n\t\t\t\tstate.statusMessageId = null;\n\t\t\t}\n\n\t\t\t// Clean up editor\n\t\t\tstate.editor.clear();\n\n\t\t\t// Signal done AFTER cleanup — waitForCompletion checks this flag,\n\t\t\t// so setting it last ensures status message is deleted before DONE is sent\n\t\t\tstate.done = true;\n\t\t\tbreak;\n\t\t}\n\n\t\t// Handle error responses that leak through RPC (async prompt errors)\n\t\tcase \"response\": {\n\t\t\tconst resp = event as any;\n\t\t\tif (!resp.success && resp.error) {\n\t\t\t\tsend(`❌ ${resp.error}`, true);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/**\n * Build and push a status update to the ephemeral message.\n */\nfunction updateStatus(state: EventDisplayState): void {\n\tif (!state.statusMessageId) return;\n\n\tconst parts: string[] = [];\n\n\t// Tool count header\n\tif (state.toolCount > 0) {\n\t\tparts.push(`🔧 *Tool ${state.toolCount}*`);\n\t}\n\n\t// Task list\n\tif (state.tasks.length > 0) {\n\t\tparts.push(formatTaskList(state.tasks));\n\t}\n\n\t// Background agents\n\tif (state.backgroundAgents.size > 0) {\n\t\tfor (const agent of state.backgroundAgents.values()) {\n\t\t\tparts.push(`🤖 *${agent.agentType}*: ${agent.taskSummary.slice(0, 200)}`);\n\t\t}\n\t}\n\n\t// Recent tools (last 5)\n\tif (state.toolsSinceText.length > 0) {\n\t\tconst recent = state.toolsSinceText.slice(-5);\n\t\tparts.push(recent.join(\"\\n\\n\"));\n\t}\n\n\tif (parts.length === 0) return;\n\n\tconst text = parts.join(\"\\n\\n\").slice(0, 4000);\n\tstate.editor.edit(state.chatId, state.statusMessageId, text);\n}\n\nfunction updateStatusText(state: EventDisplayState, text: string): void {\n\tif (!state.statusMessageId) return;\n\tstate.editor.edit(state.chatId, state.statusMessageId, text);\n}\n"]}
package/dist/types.d.ts CHANGED
@@ -37,6 +37,8 @@ export interface UserState {
37
37
  }>;
38
38
  /** Buddy controller — any to avoid import of @dreb/coding-agent/buddy */
39
39
  buddyController: any;
40
+ /** Pending model fallback warning to show the user (set once after bridge start) */
41
+ pendingModelFallbackWarning?: string;
40
42
  }
41
43
  /** Session info for persistence across bot restarts */
42
44
  export interface SavedSessions {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,+BAA+B;AAC/B,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,wCAAwC;AACxC,MAAM,WAAW,SAAS;IACzB,wCAAwC;IACxC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3B,qEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,cAAc,EAAE,OAAO,CAAC;IACxB,oDAAoD;IACpD,cAAc,EAAE,OAAO,CAAC;IACxB,mEAAmE;IACnE,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,0FAA0F;IAC1F,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,0CAA0C;IAC1C,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC5C,oDAAoD;IACpD,aAAa,EAAE,OAAO,CAAC;IACvB,kFAAgF;IAChF,MAAM,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClF,2EAAyE;IACzE,eAAe,EAAE,GAAG,CAAC;CACrB;AAED,uDAAuD;AACvD,MAAM,WAAW,aAAa;IAC7B,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;QACvB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;CACH","sourcesContent":["/**\n * Bot-specific types for the Telegram frontend.\n */\n\nimport type { AgentBridge } from \"./agent-bridge.js\";\nimport type { Config } from \"./config.js\";\n\n/** Tracked background agent */\nexport interface TrackedAgent {\n\tagentId: string;\n\tagentType: string;\n\ttaskSummary: string;\n\tstartTime: number;\n}\n\n/** Per-user state managed by the bot */\nexport interface UserState {\n\t/** Active agent bridge (RPC process) */\n\tbridge: AgentBridge | null;\n\t/** Bot config — needed by buddy controller for RPC hatch/reroll */\n\tconfig: Config;\n\t/** Covers the race window between prompt() call and agent_start event */\n\tpromptInFlight: boolean;\n\t/** Flag to start a fresh session on next message */\n\tnewSessionFlag: boolean;\n\t/** Optional working directory override for the next new session */\n\tnewSessionCwd: string | null;\n\t/** The actual working directory of the current bridge (may differ from config default) */\n\teffectiveCwd: string | null;\n\t/** Currently running background agents */\n\tbackgroundAgents: Map<string, TrackedAgent>;\n\t/** Whether /stop was used (suppress DONE marker) */\n\tstopRequested: boolean;\n\t/** Messages waiting to be delivered to Telegram — drained by a delivery loop */\n\toutbox: Array<{ chatId: number; text: string; long?: boolean; retries?: number }>;\n\t/** Buddy controller — any to avoid import of @dreb/coding-agent/buddy */\n\tbuddyController: any;\n}\n\n/** Session info for persistence across bot restarts */\nexport interface SavedSessions {\n\t[userId: string]: Array<{\n\t\tsessionPath: string;\n\t\tsessionId: string;\n\t\ttimestamp: number;\n\t\tpreview: string;\n\t}>;\n}\n"]}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,+BAA+B;AAC/B,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,wCAAwC;AACxC,MAAM,WAAW,SAAS;IACzB,wCAAwC;IACxC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3B,qEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,cAAc,EAAE,OAAO,CAAC;IACxB,oDAAoD;IACpD,cAAc,EAAE,OAAO,CAAC;IACxB,mEAAmE;IACnE,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,0FAA0F;IAC1F,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,0CAA0C;IAC1C,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC5C,oDAAoD;IACpD,aAAa,EAAE,OAAO,CAAC;IACvB,kFAAgF;IAChF,MAAM,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClF,2EAAyE;IACzE,eAAe,EAAE,GAAG,CAAC;IACrB,oFAAoF;IACpF,2BAA2B,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,uDAAuD;AACvD,MAAM,WAAW,aAAa;IAC7B,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;QACvB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;CACH","sourcesContent":["/**\n * Bot-specific types for the Telegram frontend.\n */\n\nimport type { AgentBridge } from \"./agent-bridge.js\";\nimport type { Config } from \"./config.js\";\n\n/** Tracked background agent */\nexport interface TrackedAgent {\n\tagentId: string;\n\tagentType: string;\n\ttaskSummary: string;\n\tstartTime: number;\n}\n\n/** Per-user state managed by the bot */\nexport interface UserState {\n\t/** Active agent bridge (RPC process) */\n\tbridge: AgentBridge | null;\n\t/** Bot config — needed by buddy controller for RPC hatch/reroll */\n\tconfig: Config;\n\t/** Covers the race window between prompt() call and agent_start event */\n\tpromptInFlight: boolean;\n\t/** Flag to start a fresh session on next message */\n\tnewSessionFlag: boolean;\n\t/** Optional working directory override for the next new session */\n\tnewSessionCwd: string | null;\n\t/** The actual working directory of the current bridge (may differ from config default) */\n\teffectiveCwd: string | null;\n\t/** Currently running background agents */\n\tbackgroundAgents: Map<string, TrackedAgent>;\n\t/** Whether /stop was used (suppress DONE marker) */\n\tstopRequested: boolean;\n\t/** Messages waiting to be delivered to Telegram — drained by a delivery loop */\n\toutbox: Array<{ chatId: number; text: string; long?: boolean; retries?: number }>;\n\t/** Buddy controller — any to avoid import of @dreb/coding-agent/buddy */\n\tbuddyController: any;\n\t/** Pending model fallback warning to show the user (set once after bridge start) */\n\tpendingModelFallbackWarning?: string;\n}\n\n/** Session info for persistence across bot restarts */\nexport interface SavedSessions {\n\t[userId: string]: Array<{\n\t\tsessionPath: string;\n\t\tsessionId: string;\n\t\ttimestamp: number;\n\t\tpreview: string;\n\t}>;\n}\n"]}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG","sourcesContent":["/**\n * Bot-specific types for the Telegram frontend.\n */\n\nimport type { AgentBridge } from \"./agent-bridge.js\";\nimport type { Config } from \"./config.js\";\n\n/** Tracked background agent */\nexport interface TrackedAgent {\n\tagentId: string;\n\tagentType: string;\n\ttaskSummary: string;\n\tstartTime: number;\n}\n\n/** Per-user state managed by the bot */\nexport interface UserState {\n\t/** Active agent bridge (RPC process) */\n\tbridge: AgentBridge | null;\n\t/** Bot config — needed by buddy controller for RPC hatch/reroll */\n\tconfig: Config;\n\t/** Covers the race window between prompt() call and agent_start event */\n\tpromptInFlight: boolean;\n\t/** Flag to start a fresh session on next message */\n\tnewSessionFlag: boolean;\n\t/** Optional working directory override for the next new session */\n\tnewSessionCwd: string | null;\n\t/** The actual working directory of the current bridge (may differ from config default) */\n\teffectiveCwd: string | null;\n\t/** Currently running background agents */\n\tbackgroundAgents: Map<string, TrackedAgent>;\n\t/** Whether /stop was used (suppress DONE marker) */\n\tstopRequested: boolean;\n\t/** Messages waiting to be delivered to Telegram — drained by a delivery loop */\n\toutbox: Array<{ chatId: number; text: string; long?: boolean; retries?: number }>;\n\t/** Buddy controller — any to avoid import of @dreb/coding-agent/buddy */\n\tbuddyController: any;\n}\n\n/** Session info for persistence across bot restarts */\nexport interface SavedSessions {\n\t[userId: string]: Array<{\n\t\tsessionPath: string;\n\t\tsessionId: string;\n\t\ttimestamp: number;\n\t\tpreview: string;\n\t}>;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG","sourcesContent":["/**\n * Bot-specific types for the Telegram frontend.\n */\n\nimport type { AgentBridge } from \"./agent-bridge.js\";\nimport type { Config } from \"./config.js\";\n\n/** Tracked background agent */\nexport interface TrackedAgent {\n\tagentId: string;\n\tagentType: string;\n\ttaskSummary: string;\n\tstartTime: number;\n}\n\n/** Per-user state managed by the bot */\nexport interface UserState {\n\t/** Active agent bridge (RPC process) */\n\tbridge: AgentBridge | null;\n\t/** Bot config — needed by buddy controller for RPC hatch/reroll */\n\tconfig: Config;\n\t/** Covers the race window between prompt() call and agent_start event */\n\tpromptInFlight: boolean;\n\t/** Flag to start a fresh session on next message */\n\tnewSessionFlag: boolean;\n\t/** Optional working directory override for the next new session */\n\tnewSessionCwd: string | null;\n\t/** The actual working directory of the current bridge (may differ from config default) */\n\teffectiveCwd: string | null;\n\t/** Currently running background agents */\n\tbackgroundAgents: Map<string, TrackedAgent>;\n\t/** Whether /stop was used (suppress DONE marker) */\n\tstopRequested: boolean;\n\t/** Messages waiting to be delivered to Telegram — drained by a delivery loop */\n\toutbox: Array<{ chatId: number; text: string; long?: boolean; retries?: number }>;\n\t/** Buddy controller — any to avoid import of @dreb/coding-agent/buddy */\n\tbuddyController: any;\n\t/** Pending model fallback warning to show the user (set once after bridge start) */\n\tpendingModelFallbackWarning?: string;\n}\n\n/** Session info for persistence across bot restarts */\nexport interface SavedSessions {\n\t[userId: string]: Array<{\n\t\tsessionPath: string;\n\t\tsessionId: string;\n\t\ttimestamp: number;\n\t\tpreview: string;\n\t}>;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreb/telegram",
3
- "version": "2.4.1",
3
+ "version": "2.4.3",
4
4
  "description": "Telegram bot frontend for dreb coding agent",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",