diff --git a/src/cli/run/event-handlers.ts b/src/cli/run/event-handlers.ts index ba6559cdd..d32d0cd41 100644 --- a/src/cli/run/event-handlers.ts +++ b/src/cli/run/event-handlers.ts @@ -103,7 +103,6 @@ export function handleMessagePartUpdated(ctx: RunContext, payload: EventPayload, if (payload.type !== "message.part.updated") return const props = payload.properties as MessagePartUpdatedProps | undefined - // Current OpenCode puts sessionID inside part; legacy puts it in info const partSid = getPartSessionId(props) const infoSid = getInfoSessionId(props) if ((partSid ?? infoSid) !== ctx.sessionID) return diff --git a/src/cli/run/event-state.ts b/src/cli/run/event-state.ts index eee23f5f3..9c0f5b315 100644 --- a/src/cli/run/event-state.ts +++ b/src/cli/run/event-state.ts @@ -17,7 +17,6 @@ export interface EventState { currentModel: string | null /** Current model variant from the latest assistant message */ currentVariant: string | null - /** Current message role (user/assistant) — used to filter user messages from display */ currentMessageRole: string | null /** Agent profile colors keyed by display name */ agentColorsByName: Record @@ -39,7 +38,6 @@ export interface EventState { textAtLineStart: boolean /** Whether reasoning stream is currently at line start (for padding) */ thinkingAtLineStart: boolean - /** Current assistant message ID — prevents counter resets on repeated message.updated for same message */ currentMessageId: string | null /** Assistant message start timestamp by message ID */ messageStartedAtById: Record diff --git a/src/cli/run/poll-for-completion.ts b/src/cli/run/poll-for-completion.ts index 529221094..51c1dfc13 100644 --- a/src/cli/run/poll-for-completion.ts +++ b/src/cli/run/poll-for-completion.ts @@ -50,7 +50,6 @@ export async function pollForCompletion( return 130 } - // ERROR CHECK FIRST — errors must not be masked by other gates if (eventState.mainSessionError) { errorCycleCount++ if (errorCycleCount >= ERROR_GRACE_CYCLES) { @@ -62,19 +61,15 @@ export async function pollForCompletion( ) return 1 } - // Continue polling during grace period to allow recovery continue } else { - // Reset error counter when error clears (recovery succeeded) errorCycleCount = 0 } - // Watchdog: if no events received for N seconds, verify session status via API let mainSessionStatus: "idle" | "busy" | "retry" | null = null if (eventState.lastEventTimestamp !== null) { const timeSinceLastEvent = Date.now() - eventState.lastEventTimestamp if (timeSinceLastEvent > eventWatchdogMs) { - // Events stopped coming - verify actual session state console.log( pc.yellow( `\n No events for ${Math.round( @@ -83,7 +78,6 @@ export async function pollForCompletion( ) ) - // Force check session status directly mainSessionStatus = await getMainSessionStatus(ctx) if (mainSessionStatus === "idle") { eventState.mainSessionIdle = true @@ -91,12 +85,10 @@ export async function pollForCompletion( eventState.mainSessionIdle = false } - // Reset timestamp to avoid repeated checks eventState.lastEventTimestamp = Date.now() } } - // Only call getMainSessionStatus if watchdog didn't already check if (mainSessionStatus === null) { mainSessionStatus = await getMainSessionStatus(ctx) } @@ -122,15 +114,11 @@ export async function pollForCompletion( continue } - // Secondary timeout: if we've been polling for reasonable time but haven't - // received meaningful work via events, check if there's active work via API - // Only check once to avoid unnecessary API calls every poll cycle if ( Date.now() - pollStartTimestamp > secondaryMeaningfulWorkTimeoutMs && !secondaryTimeoutChecked ) { secondaryTimeoutChecked = true - // Check if session actually has pending work (children, todos, etc.) const childrenRes = await ctx.client.session.children({ path: { id: ctx.sessionID }, query: { directory: ctx.directory }, @@ -154,7 +142,6 @@ export async function pollForCompletion( const hasActiveWork = hasActiveChildren || hasActiveTodos if (hasActiveWork) { - // Assume meaningful work is happening even without events eventState.hasReceivedMeaningfulWork = true console.log( pc.yellow( @@ -166,12 +153,10 @@ export async function pollForCompletion( } } } else { - // Track when first meaningful work was received if (firstWorkTimestamp === null) { firstWorkTimestamp = Date.now() } - // Don't check completion during stabilization period if (Date.now() - firstWorkTimestamp < minStabilizationMs) { consecutiveCompleteChecks = 0 continue diff --git a/src/cli/run/runner.ts b/src/cli/run/runner.ts index 247726fa8..bd3547482 100644 --- a/src/cli/run/runner.ts +++ b/src/cli/run/runner.ts @@ -114,7 +114,6 @@ export async function run(options: RunOptions): Promise { }) const exitCode = await pollForCompletion(ctx, eventState, abortController) - // Abort the event stream to stop the processor abortController.abort() await waitForEventProcessorShutdown(eventProcessor) diff --git a/src/cli/run/session-resolver.ts b/src/cli/run/session-resolver.ts index c5d9cb5e4..9246f43d0 100644 --- a/src/cli/run/session-resolver.ts +++ b/src/cli/run/session-resolver.ts @@ -27,7 +27,6 @@ export async function resolveSession(options: { const res = await client.session.create({ body: { title: "oh-my-opencode run", - // In CLI run mode there's no TUI to answer questions. permission: [ { permission: "question", action: "deny" as const, pattern: "*" }, ], diff --git a/src/cli/run/types.ts b/src/cli/run/types.ts index 30bacaee7..eedd8e153 100644 --- a/src/cli/run/types.ts +++ b/src/cli/run/types.ts @@ -81,7 +81,6 @@ export interface MessageUpdatedProps { } export interface MessagePartUpdatedProps { - /** @deprecated Legacy structure — current OpenCode puts sessionID inside part */ info?: { sessionID?: string; sessionId?: string; role?: string } part?: { id?: string diff --git a/src/mcp/context7.ts b/src/mcp/context7.ts index 4843e28fe..738c350ab 100644 --- a/src/mcp/context7.ts +++ b/src/mcp/context7.ts @@ -5,6 +5,5 @@ export const context7 = { headers: process.env.CONTEXT7_API_KEY ? { Authorization: `Bearer ${process.env.CONTEXT7_API_KEY}` } : undefined, - // Disable OAuth auto-detection - Context7 uses API key header, not OAuth oauth: false as const, } diff --git a/src/mcp/websearch.ts b/src/mcp/websearch.ts index a1ab4600e..be3bad4b2 100644 --- a/src/mcp/websearch.ts +++ b/src/mcp/websearch.ts @@ -30,7 +30,6 @@ export function createWebsearchConfig(config?: WebsearchConfig): RemoteMcpConfig } } - // Default to Exa return { type: "remote" as const, url: process.env.EXA_API_KEY @@ -42,5 +41,4 @@ export function createWebsearchConfig(config?: WebsearchConfig): RemoteMcpConfig } } -// Backward compatibility: export static instance using default config export const websearch = createWebsearchConfig() diff --git a/src/openclaw/config.ts b/src/openclaw/config.ts index 946b11e69..846f6d912 100644 --- a/src/openclaw/config.ts +++ b/src/openclaw/config.ts @@ -89,11 +89,9 @@ export function resolveGateway( return null } - // Validate based on gateway type if (gateway.type === "command") { if (!gateway.command) return null } else { - // HTTP gateway if (!gateway.url) return null } diff --git a/src/openclaw/dispatcher.ts b/src/openclaw/dispatcher.ts index d7dd5efda..75cab8c23 100644 --- a/src/openclaw/dispatcher.ts +++ b/src/openclaw/dispatcher.ts @@ -134,7 +134,6 @@ export async function wakeCommandGateway( try { const timeout = resolveCommandTimeoutMs(gatewayConfig.timeout) - // Interpolate variables with shell escaping const interpolated = gatewayConfig.command.replace(/\{\{(\w+)\}\}/g, (_match, key) => { const value = variables[key] if (value === undefined) return _match diff --git a/src/openclaw/reply-listener.ts b/src/openclaw/reply-listener.ts index f6c8e015b..4c1f10008 100644 --- a/src/openclaw/reply-listener.ts +++ b/src/openclaw/reply-listener.ts @@ -82,7 +82,6 @@ function writeSecureFile(filePath: string, content: string): void { try { chmodSync(filePath, SECURE_FILE_MODE) } catch { - // Ignore } } @@ -98,7 +97,6 @@ function rotateLogIfNeeded(logPath: string): void { renameSync(logPath, backupPath) } } catch { - // Ignore } } @@ -110,7 +108,6 @@ function log(message: string): void { const logLine = `[${timestamp}] ${message}\n` appendFileSync(LOG_FILE_PATH, logLine, { mode: SECURE_FILE_MODE }) } catch { - // Ignore } } @@ -130,6 +127,31 @@ interface DaemonState { lastError?: string } +interface TelegramMessage { + message_id?: number + chat?: { id?: number | string } + text?: string + reply_to_message?: { message_id?: number } +} + +interface TelegramUpdate { + update_id?: number + message?: TelegramMessage +} + +interface TelegramUpdatesResponse { + result?: TelegramUpdate[] +} + +function parseTelegramUpdatesResponse(body: unknown): TelegramUpdate[] { + if (typeof body !== "object" || body === null) { + return [] + } + + const result = (body as TelegramUpdatesResponse).result + return Array.isArray(result) ? result : [] +} + function readDaemonState(): DaemonState | null { try { if (!existsSync(STATE_FILE_PATH)) return null @@ -195,7 +217,6 @@ export async function isReplyListenerProcess(pid: number): Promise { const cmdline = readFileSync(`/proc/${pid}/cmdline`, "utf-8") return cmdline.includes(DAEMON_IDENTITY_MARKER) } - // macOS const proc = spawn(["ps", "-p", String(pid), "-o", "args="], { stdout: "pipe", stderr: "ignore", @@ -222,7 +243,6 @@ export async function isDaemonRunning(): Promise { return true } -// Input Sanitization export function sanitizeReplyInput(text: string): string { return text .replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "") @@ -387,7 +407,6 @@ async function pollDiscord( }, ) } catch { - // Ignore } } else { state.errors++ @@ -427,52 +446,52 @@ async function pollTelegram( return } - const body = await response.json() as any - const updates = body.result || [] + const body = await response.json() + const updates = parseTelegramUpdatesResponse(body) for (const update of updates) { const msg = update.message if (!msg) { - state.telegramLastUpdateId = update.update_id + state.telegramLastUpdateId = update.update_id ?? state.telegramLastUpdateId writeDaemonState(state) continue } - if (!msg.reply_to_message?.message_id) { - state.telegramLastUpdateId = update.update_id + if (msg.reply_to_message?.message_id === undefined) { + state.telegramLastUpdateId = update.update_id ?? state.telegramLastUpdateId writeDaemonState(state) continue } - if (String(msg.chat.id) !== replyListener.telegramChatId) { - state.telegramLastUpdateId = update.update_id + if (String(msg.chat?.id) !== replyListener.telegramChatId) { + state.telegramLastUpdateId = update.update_id ?? state.telegramLastUpdateId writeDaemonState(state) continue } const mapping = lookupByMessageId("telegram", String(msg.reply_to_message.message_id)) if (!mapping) { - state.telegramLastUpdateId = update.update_id + state.telegramLastUpdateId = update.update_id ?? state.telegramLastUpdateId writeDaemonState(state) continue } const text = msg.text || "" if (!text) { - state.telegramLastUpdateId = update.update_id + state.telegramLastUpdateId = update.update_id ?? state.telegramLastUpdateId writeDaemonState(state) continue } if (!rateLimiter.canProceed()) { log(`WARN: Rate limit exceeded, dropping Telegram message ${msg.message_id}`) - state.telegramLastUpdateId = update.update_id + state.telegramLastUpdateId = update.update_id ?? state.telegramLastUpdateId writeDaemonState(state) state.errors++ continue } - state.telegramLastUpdateId = update.update_id + state.telegramLastUpdateId = update.update_id ?? state.telegramLastUpdateId writeDaemonState(state) const success = await injectReply(mapping.tmuxPaneId, text, "telegram", config) @@ -604,9 +623,6 @@ export async function startReplyListener(config: OpenClawConfig): Promise<{ succ const normalizedConfig = normalizeReplyListenerConfig(config) const replyListener = normalizedConfig.replyListener if (!replyListener?.discordBotToken && !replyListener?.telegramBotToken) { - // Only warn if no platforms enabled, but user might just want outbound - // Actually, instructions say: "Fire-and-forget for outbound, daemon process for inbound" - // So if no inbound config, we shouldn't start daemon. return { success: false, message: "No enabled reply listener platforms configured (missing bot tokens/channels)", diff --git a/src/openclaw/session-registry.ts b/src/openclaw/session-registry.ts index 4f0b37979..969b59e06 100644 --- a/src/openclaw/session-registry.ts +++ b/src/openclaw/session-registry.ts @@ -44,7 +44,6 @@ function ensureRegistryDir(): void { } function sleepMs(ms: number): void { - // Use Atomics.wait for synchronous sleep Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms) } @@ -79,7 +78,6 @@ function readLockSnapshot(): LockSnapshot | null { typeof parsed.token === "string" && parsed.token.length > 0 ? parsed.token : null return { raw, pid, token } } catch { - // Legacy format or plain PID const [pidStr] = trimmed.split(":") const parsedPid = Number.parseInt(pidStr ?? "", 10) return { @@ -132,12 +130,10 @@ function acquireRegistryLock(): LockHandle | null { try { closeSync(fd) } catch { - // Ignore } try { unlinkSync(REGISTRY_LOCK_PATH) } catch { - // Ignore } throw writeError } @@ -164,7 +160,6 @@ function acquireRegistryLock(): LockHandle | null { } } } catch { - // Ignore errors } sleepMs(LOCK_RETRY_MS) } @@ -188,8 +183,7 @@ function releaseRegistryLock(lock: LockHandle): void { try { closeSync(lock.fd) } catch { - // Ignore - } + } const snapshot = readLockSnapshot() if (!snapshot || snapshot.token !== lock.token) return removeLockIfUnchanged(snapshot) @@ -298,7 +292,6 @@ export function removeSession(sessionId: string): void { rewriteRegistryUnsafe(filtered) }, () => { - // Best-effort }, ) } @@ -312,7 +305,6 @@ export function removeMessagesByPane(paneId: string): void { rewriteRegistryUnsafe(filtered) }, () => { - // Best-effort }, ) } @@ -334,7 +326,6 @@ export function pruneStale(): void { rewriteRegistryUnsafe(filtered) }, () => { - // Best-effort }, ) } diff --git a/src/openclaw/tmux.ts b/src/openclaw/tmux.ts index 6b575e662..9bdb6212a 100644 --- a/src/openclaw/tmux.ts +++ b/src/openclaw/tmux.ts @@ -4,8 +4,7 @@ export function getCurrentTmuxSession(): string | null { const env = process.env.TMUX if (!env) return null const match = env.match(/(\d+)$/) - return match ? `session-${match[1]}` : null // Wait, TMUX env is /tmp/tmux-501/default,1234,0 - // Reference tmux.js gets session name via `tmux display-message -p '#S'` + return match ? `session-${match[1]}` : null } export async function getTmuxSessionName(): Promise { @@ -17,7 +16,6 @@ export async function getTmuxSessionName(): Promise { const outputPromise = new Response(proc.stdout).text() await proc.exited const output = await outputPromise - // Await proc.exited ensures exitCode is set; avoid race condition if (proc.exitCode !== 0) return null return output.trim() || null } catch {