Skip to content

Fix Telegram markdown and agent responsiveness#1668

Merged
zerob13 merged 6 commits into
devfrom
codex/steer-exec
May 25, 2026
Merged

Fix Telegram markdown and agent responsiveness#1668
zerob13 merged 6 commits into
devfrom
codex/steer-exec

Conversation

@zerob13
Copy link
Copy Markdown
Collaborator

@zerob13 zerob13 commented May 25, 2026

Summary

  • Sync this branch with the latest dev, including upstream Telegram HTML markdown rendering from fix(telegram): render markdown as html #1667.
  • Harden Telegram markdown delivery with GFM pipe-table fallback to fixed-width <pre> text and plain-text retry when Telegram rejects converted HTML entities.
  • Keep the existing steer input responsiveness changes in this branch: visible pending-lane input, normal user-turn insertion at the next agent-loop boundary, and utility-process backed long-running exec sessions.

Tests

  • pnpm vitest run test/main/presenter/remoteControlPresenter/telegramMarkdown.test.ts test/main/presenter/remoteControlPresenter/telegramClient.test.ts test/main/presenter/remoteControlPresenter/telegramPoller.test.ts
  • pnpm run format
  • pnpm run i18n
  • pnpm run lint
  • pnpm run typecheck

Summary by CodeRabbit

  • New Features

    • Agent steering input now displays as visible user messages in conversations without interrupting active requests.
    • Long-running shell commands now yield faster to keep the interface responsive while continuing execution in the background.
  • Improvements

    • Telegram messages gracefully fall back to plain text when HTML entity parsing fails.
    • Enhanced reliability of background operations with better error handling.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 25, 2026

📝 Walkthrough

Walkthrough

This change adds Telegram Markdown table conversion and entity-parse fallback, implements a steer pending-input lane with yielding in the agent loop, moves background exec into a utility-process RPC proxy, relocates Electron bootstrap to appMain, updates renderer pending-input capacity, and adjusts tests to cover the new flows.

Changes

Telegram Markdown + Agent Steer + Exec Isolation

Layer / File(s) Summary
Feature documentation
docs/issues/agent-loop-input-exec-responsiveness/{plan,spec,tasks}.md, docs/issues/telegram-message-markdown-render/{plan,spec,tasks}.md
Plans, specs, and task checklists for agent loop input/exec responsiveness and Telegram Markdown rendering with table fallback and entity-parse retry.
Telegram Markdown + Poller fallback
src/main/presenter/remoteControlPresenter/telegram/telegramMarkdown.ts, src/main/presenter/remoteControlPresenter/telegram/telegramPoller.ts, test/main/presenter/remoteControlPresenter/telegramMarkdown.test.ts, test/main/presenter/remoteControlPresenter/telegramPoller.test.ts
Convert GFM pipe tables to fixed-width pre blocks before HTML conversion; send/edit using parse_mode: 'HTML' and retry as plain text on Telegram entity-parse (HTTP 400) errors.
Exec utility host entrypoint
src/main/lib/agentRuntime/backgroundExecUtilityHost.ts
Add gated utility-host entry that listens on parentPort and dispatches BackgroundExecSessionManager RPC requests with error serialization and shutdown handling.
Exec isolation: proxy & RPC transport
src/main/lib/agentRuntime/backgroundExecSessionManager.ts
Replace in-process manager with BackgroundExecUtilityProxy that lazy-starts a utilityProcess, exchanges typed RPC requests/responses via postMessage, tracks active/crashed sessions, and rejects pending RPCs on host exit.
Electron bootstrap: appMain + index gate
src/main/appMain.ts, src/main/index.ts
Extract Electron startup, lifecycle and deeplink handling into appMain; index.ts gates running the utility host and dynamically imports appMain when not running as the utility host.
Pending input store: steer-mode APIs
src/main/presenter/agentRuntimePresenter/pendingInputStore.ts
Add get/create/append/getNext/claim/consume/release APIs for steer-mode pending inputs; treat claimed rows uniformly and count only queue-mode rows for capacity.
PendingInputCoordinator: per-item steer lifecycle
src/main/presenter/agentRuntimePresenter/pendingInputCoordinator.ts
Add queueSteerInput, getNextSteerInput, hasPendingTurnInput, claim/release/consume steer operations, steer validation, and use countActiveQueue for capacity checks.
Agent runtime: steer pending lane & source-aware processing
src/main/presenter/agentRuntimePresenter/index.ts
Queue steer via queueVisibleSteerInput when generation is active instead of aborting; add ProcessPendingInputSource, source-aware consume/rollback/release helpers, prioritize steer when draining, and add shouldYieldForPendingInput integration.
Streaming yield and exec writes
src/main/presenter/agentRuntimePresenter/{types,process}.ts, src/main/presenter/skillPresenter/skillExecutionService.ts, src/main/presenter/toolPresenter/agentTools/{agentBashHandler,agentToolManager}.ts
Add shouldYieldForPendingInput?: () => boolean to ProcessParams; processStream checks it after tools to yield with stopReason: 'pending_input'; await background exec writes and tool-manager list/write/clear operations.
Renderer pending input UI/store updates
src/renderer/src/stores/ui/pendingInput.ts, test/renderer/*
Count only queue items toward capacity in the renderer store and update component/tests to reflect steer/queue separation and UI behavior.
Tests: presenter, process, integration, exec proxy, telegram
test/main/presenter/**, test/main/lib/agentRuntime/backgroundExecSessionManager.test.ts, test/main/presenter/remoteControlPresenter/*, test/renderer/*
Add in-memory pendingInputsTable mock, rewrite steer flow tests to verify queued non-abort behavior and yield semantics, add processStream pending_input test, update integration tests for steer draining, add Telegram converter and poller fallback tests, and add tests for the exec utility proxy.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I queued my steer with gentle thump,
The stream went on without a jump.
Execs now hide in utility lairs,
Telegram tables show with care.
A tiny rabbit hops—no more slumps.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix Telegram markdown and agent responsiveness' directly addresses the two main objectives: Telegram markdown rendering hardening and agent loop input/exec responsiveness improvements. It is concise, specific, and clearly summarizes the primary changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/steer-exec

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
test/main/presenter/remoteControlPresenter/telegramPoller.test.ts (1)

469-555: ⚡ Quick win

Add a regression test for fallback edit “not modified” handling.

The new cases validate fallback on sendMessage, but not the editMessageText fallback branch. A targeted test here would lock in the fix for the Line 849 fallback edit path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/main/presenter/remoteControlPresenter/telegramPoller.test.ts` around
lines 469 - 555, Add a regression test in telegramPoller.test.ts that mirrors
the existing "retries formatted markdown chunks as plain text" case but
exercises the edit fallback path: mock client.editMessageText (not just
sendMessage) to throw a TelegramApiRequestError representing the "message is not
modified" / Bad Request response, ensure the TelegramPoller receives an update
and router.conversation.getSnapshot returns a completed snapshot that would
normally trigger editMessageText, then assert that after the editMessageText
error the poller falls back to calling client.sendMessage with plain-text
content; target symbols: TelegramPoller, client.editMessageText,
client.sendMessage, TelegramApiRequestError, poller.start()/poller.stop(), and
router.conversation.getSnapshot to locate and implement the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/appMain.ts`:
- Around line 49-52: Remove the unconditional TLS bypass by deleting or gating
the call to app.commandLine.appendSwitch('ignore-certificate-errors') so it only
runs in a controlled dev/debug mode (e.g., guarded by an isDev or explicit env
flag) and add a clear comment explaining why it is restricted; also inspect the
function/method pullModel in ollamaProvider (look for the insecure: true flag)
and change it to false or similarly gate it behind the same explicit
development-only condition to avoid broadly weakening TLS in production.

In `@src/main/lib/agentRuntime/backgroundExecSessionManager.ts`:
- Around line 1037-1065: waitForCompletionOrYield and getCompletionResult call
this.request unconditionally and therefore return a "session not found" RPC
error when the utility process crashed; detect and return the stored crash
result instead of calling RPC when handleHostExit moved the session into
crashedSessions. In both methods (waitForCompletionOrYield and
getCompletionResult) first check this.crashedSessions for sessionId and if
present return that SessionCompletionResult/WaitForCompletionOrYieldResult (and
still clean up this.activeSessions/delete as appropriate), otherwise proceed to
call this.request(...) as today.
- Around line 1157-1167: BackgroundExecUtilityProxy.startHost() is forking the
current module (via fileURLToPath(import.meta.url)) so the child never runs the
host entrypoint (runBackgroundExecUtilityHostIfRequested) wired elsewhere;
change startHost() to fork the actual utility-host entrypoint module used by the
main bootstrap so the child enters host mode and services background-exec RPCs.
Also update waitForCompletionOrYield() and getCompletionResult() to consult the
crashedSessions fallback (the same map used by list/poll/log) when the utility
RPC is unavailable or the process has exited so completion APIs return results
or errors for crashed sessions instead of hanging on RPC. Reference:
BackgroundExecUtilityProxy.startHost, runBackgroundExecUtilityHostIfRequested,
waitForCompletionOrYield, getCompletionResult, crashedSessions.

In `@src/main/presenter/agentRuntimePresenter/pendingInputCoordinator.ts`:
- Around line 126-145: The four methods (releaseClaimedQueueInput,
releaseClaimedInput, consumeQueuedInput, consumeSteerInput) currently mutate by
itemId only and must revalidate that the item is owned by the caller sessionId
before changing it; update each method to verify ownership (either by calling an
existing this.store accessor like getById/getPendingInput or by adding
store-level guarded methods such as releaseClaimedQueueInputIfOwned(sessionId,
itemId) / releaseClaimedInputIfOwned(sessionId, itemId) /
consumeQueueInputIfOwned(sessionId, itemId) /
consumeSteerInputIfOwned(sessionId, itemId)) and only perform the
mutate+emitUpdated(sessionId) when the fetched record.sessionId === sessionId
(otherwise throw or return without mutating). Ensure the checks use the
sessionId parameter and keep emitUpdated(sessionId) behavior unchanged when the
mutation succeeds.

In `@src/main/presenter/remoteControlPresenter/telegram/telegramPoller.ts`:
- Around line 848-854: The fallback editMessageText call inside the parse-mode
error branch can throw a Telegram "400: message is not modified" and break
delivery sync; update the block that calls this.deps.client.editMessageText({
target, messageId, text: chunk.fallbackText }) (inside the condition checking
chunk.parseMode && this.isTelegramEntityParseError(error)) to catch errors and
ignore only the "message is not modified" case (same check used elsewhere in
this file), while rethrowing other errors so normal error handling still
applies.

In `@test/main/presenter/agentRuntimePresenter/agentRuntimePresenter.test.ts`:
- Around line 152-155: The mock for countActiveBySession currently counts any
non-consumed row and therefore includes 'steer' items and already-claimed queue
rows; update it to only count actual pending/unclaimed input rows by filtering
pendingRows for rows with the matching session_id that are in the
unclaimed/pending state and not of type 'steer' (e.g. change the predicate to
something like row.session_id === sessionId && row.state === 'pending' &&
row.type !== 'steer' to exclude steer and claimed/consumed rows).

In `@test/main/presenter/agentSessionPresenter/integration.test.ts`:
- Around line 1071-1073: Replace the fixed 120ms setTimeout waits with a
condition-based wait that polls until providerInstance.coreStream has been
called the expected number of times; specifically, remove the await new
Promise((r) => setTimeout(r, 120)) and instead await a helper like waitFor or a
short-poll loop that repeatedly checks
expect(providerInstance.coreStream).toHaveBeenCalledTimes(3) (and the other
occurrence expecting a call count around lines 1153-1156) until success or a
timeout, so the test waits deterministically for providerInstance.coreStream to
reach the desired call count.

---

Nitpick comments:
In `@test/main/presenter/remoteControlPresenter/telegramPoller.test.ts`:
- Around line 469-555: Add a regression test in telegramPoller.test.ts that
mirrors the existing "retries formatted markdown chunks as plain text" case but
exercises the edit fallback path: mock client.editMessageText (not just
sendMessage) to throw a TelegramApiRequestError representing the "message is not
modified" / Bad Request response, ensure the TelegramPoller receives an update
and router.conversation.getSnapshot returns a completed snapshot that would
normally trigger editMessageText, then assert that after the editMessageText
error the poller falls back to calling client.sendMessage with plain-text
content; target symbols: TelegramPoller, client.editMessageText,
client.sendMessage, TelegramApiRequestError, poller.start()/poller.stop(), and
router.conversation.getSnapshot to locate and implement the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7862e914-2c56-442a-a7f1-7f234e67a8b9

📥 Commits

Reviewing files that changed from the base of the PR and between 0cd0821 and 79f89cd.

📒 Files selected for processing (31)
  • docs/features/telegram-markdown-rendering/plan.md
  • docs/features/telegram-markdown-rendering/spec.md
  • docs/features/telegram-markdown-rendering/tasks.md
  • docs/issues/agent-loop-input-exec-responsiveness/plan.md
  • docs/issues/agent-loop-input-exec-responsiveness/spec.md
  • docs/issues/agent-loop-input-exec-responsiveness/tasks.md
  • package.json
  • src/main/appMain.ts
  • src/main/index.ts
  • src/main/lib/agentRuntime/backgroundExecSessionManager.ts
  • src/main/lib/agentRuntime/backgroundExecUtilityHost.ts
  • src/main/presenter/agentRuntimePresenter/index.ts
  • src/main/presenter/agentRuntimePresenter/pendingInputCoordinator.ts
  • src/main/presenter/agentRuntimePresenter/pendingInputStore.ts
  • src/main/presenter/agentRuntimePresenter/process.ts
  • src/main/presenter/agentRuntimePresenter/types.ts
  • src/main/presenter/remoteControlPresenter/telegram/telegramClient.ts
  • src/main/presenter/remoteControlPresenter/telegram/telegramOutbound.ts
  • src/main/presenter/remoteControlPresenter/telegram/telegramPoller.ts
  • src/main/presenter/skillPresenter/skillExecutionService.ts
  • src/main/presenter/toolPresenter/agentTools/agentBashHandler.ts
  • src/main/presenter/toolPresenter/agentTools/agentToolManager.ts
  • src/renderer/src/stores/ui/pendingInput.ts
  • test/main/presenter/agentRuntimePresenter/agentRuntimePresenter.test.ts
  • test/main/presenter/agentRuntimePresenter/process.test.ts
  • test/main/presenter/agentSessionPresenter/integration.test.ts
  • test/main/presenter/remoteControlPresenter/telegramClient.test.ts
  • test/main/presenter/remoteControlPresenter/telegramOutbound.test.ts
  • test/main/presenter/remoteControlPresenter/telegramPoller.test.ts
  • test/renderer/components/PendingInputLane.test.ts
  • test/renderer/stores/pendingInputStore.test.ts

Comment thread src/main/appMain.ts Outdated
Comment on lines +49 to +52
app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required') // Allow video autoplay
app.commandLine.appendSwitch('webrtc-max-cpu-consumption-percentage', '100') // Set WebRTC max CPU usage
app.commandLine.appendSwitch('js-flags', '--max-old-space-size=4096') // Set V8 heap memory size
app.commandLine.appendSwitch('ignore-certificate-errors') // Ignore certificate errors (for dev or specific scenarios)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Remove unconditional TLS bypass (ignore-certificate-errors) from the Electron main process

src/main/appMain.ts (lines 49-52) unconditionally appends ignore-certificate-errors, which disables TLS validation for every HTTPS request the app makes. Remove it or gate it behind an explicit development-only path.

Suggested fix
 app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required') // Allow video autoplay
 app.commandLine.appendSwitch('webrtc-max-cpu-consumption-percentage', '100') // Set WebRTC max CPU usage
 app.commandLine.appendSwitch('js-flags', '--max-old-space-size=4096') // Set V8 heap memory size
-app.commandLine.appendSwitch('ignore-certificate-errors') // Ignore certificate errors (for dev or specific scenarios)
+if (process.env.NODE_ENV === 'development') {
+  app.commandLine.appendSwitch('ignore-certificate-errors')
+}

Also review src/main/presenter/llmProviderPresenter/providers/ollamaProvider.ts where pullModel() sets insecure: true to ensure it isn’t broadly weakening TLS without the same controlled intent.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required') // Allow video autoplay
app.commandLine.appendSwitch('webrtc-max-cpu-consumption-percentage', '100') // Set WebRTC max CPU usage
app.commandLine.appendSwitch('js-flags', '--max-old-space-size=4096') // Set V8 heap memory size
app.commandLine.appendSwitch('ignore-certificate-errors') // Ignore certificate errors (for dev or specific scenarios)
app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required') // Allow video autoplay
app.commandLine.appendSwitch('webrtc-max-cpu-consumption-percentage', '100') // Set WebRTC max CPU usage
app.commandLine.appendSwitch('js-flags', '--max-old-space-size=4096') // Set V8 heap memory size
if (process.env.NODE_ENV === 'development') {
app.commandLine.appendSwitch('ignore-certificate-errors')
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/appMain.ts` around lines 49 - 52, Remove the unconditional TLS
bypass by deleting or gating the call to
app.commandLine.appendSwitch('ignore-certificate-errors') so it only runs in a
controlled dev/debug mode (e.g., guarded by an isDev or explicit env flag) and
add a clear comment explaining why it is restricted; also inspect the
function/method pullModel in ollamaProvider (look for the insecure: true flag)
and change it to false or similarly gate it behind the same explicit
development-only condition to avoid broadly weakening TLS in production.

Comment thread src/main/lib/agentRuntime/backgroundExecSessionManager.ts
Comment thread src/main/lib/agentRuntime/backgroundExecSessionManager.ts
Comment thread src/main/presenter/agentRuntimePresenter/pendingInputCoordinator.ts
Comment thread src/main/presenter/remoteControlPresenter/telegram/telegramPoller.ts Outdated
Comment on lines +152 to +155
countActiveBySession: vi.fn(
(sessionId: string) =>
pendingRows.filter((row) => row.session_id === sessionId && row.state !== 'consumed').length
),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Align countActiveBySession mock with queue-capacity semantics.

Line 152 currently counts all non-consumed rows, which includes steer items and claimed queue rows. That diverges from the updated queue-capacity model and can mask regressions in pending-input flow tests.

Suggested fix
     countActiveBySession: vi.fn(
       (sessionId: string) =>
-        pendingRows.filter((row) => row.session_id === sessionId && row.state !== 'consumed').length
+        pendingRows.filter(
+          (row) =>
+            row.session_id === sessionId &&
+            row.mode === 'queue' &&
+            row.state !== 'consumed' &&
+            row.state !== 'claimed'
+        ).length
     ),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
countActiveBySession: vi.fn(
(sessionId: string) =>
pendingRows.filter((row) => row.session_id === sessionId && row.state !== 'consumed').length
),
countActiveBySession: vi.fn(
(sessionId: string) =>
pendingRows.filter(
(row) =>
row.session_id === sessionId &&
row.mode === 'queue' &&
row.state !== 'consumed' &&
row.state !== 'claimed'
).length
),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/main/presenter/agentRuntimePresenter/agentRuntimePresenter.test.ts`
around lines 152 - 155, The mock for countActiveBySession currently counts any
non-consumed row and therefore includes 'steer' items and already-claimed queue
rows; update it to only count actual pending/unclaimed input rows by filtering
pendingRows for rows with the matching session_id that are in the
unclaimed/pending state and not of type 'steer' (e.g. change the predicate to
something like row.session_id === sessionId && row.state === 'pending' &&
row.type !== 'steer' to exclude steer and claimed/consumed rows).

Comment thread test/main/presenter/agentSessionPresenter/integration.test.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/main/presenter/remoteControlPresenter/telegram/telegramPoller.ts (1)

806-814: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wrap fallback editMessageText in try-catch to handle "message is not modified" errors.

The fallback editMessageText call at Line 807 lacks protection against Telegram's "400: message is not modified" error. If the plain-text retry produces the same rendered output, Telegram will reject it and break delivery sync.

🛡️ Proposed fix
       if (this.isTelegramEntityParseError(error)) {
-        await this.deps.client.editMessageText({
-          target,
-          messageId: action.messageId,
-          text: action.text,
-          replyMarkup: action.replyMarkup ?? undefined
-        })
+        try {
+          await this.deps.client.editMessageText({
+            target,
+            messageId: action.messageId,
+            text: action.text,
+            replyMarkup: action.replyMarkup ?? undefined
+          })
+        } catch (fallbackError) {
+          if (this.isMessageNotModifiedError(fallbackError)) {
+            return
+          }
+          throw fallbackError
+        }
         return
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/presenter/remoteControlPresenter/telegram/telegramPoller.ts` around
lines 806 - 814, The fallback editMessageText call inside telegramPoller.ts (in
the isTelegramEntityParseError branch) must be wrapped in a try-catch: call
this.deps.client.editMessageText({ target, messageId: action.messageId, text:
action.text, replyMarkup: action.replyMarkup ?? undefined }) inside the try,
catch errors and suppress/ignore the Telegram "400: message is not modified"
case (or check error.code/status/message) so it doesn't break delivery sync, and
rethrow or log other errors; update the isTelegramEntityParseError handling to
use this guarded call.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/main/presenter/remoteControlPresenter/telegram/telegramPoller.ts`:
- Around line 806-814: The fallback editMessageText call inside
telegramPoller.ts (in the isTelegramEntityParseError branch) must be wrapped in
a try-catch: call this.deps.client.editMessageText({ target, messageId:
action.messageId, text: action.text, replyMarkup: action.replyMarkup ??
undefined }) inside the try, catch errors and suppress/ignore the Telegram "400:
message is not modified" case (or check error.code/status/message) so it doesn't
break delivery sync, and rethrow or log other errors; update the
isTelegramEntityParseError handling to use this guarded call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 68f1d177-4ae2-4951-9892-ff8525923bf2

📥 Commits

Reviewing files that changed from the base of the PR and between 79f89cd and facad1f.

📒 Files selected for processing (7)
  • docs/issues/telegram-message-markdown-render/plan.md
  • docs/issues/telegram-message-markdown-render/spec.md
  • docs/issues/telegram-message-markdown-render/tasks.md
  • src/main/presenter/remoteControlPresenter/telegram/telegramMarkdown.ts
  • src/main/presenter/remoteControlPresenter/telegram/telegramPoller.ts
  • test/main/presenter/remoteControlPresenter/telegramMarkdown.test.ts
  • test/main/presenter/remoteControlPresenter/telegramPoller.test.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/issues/telegram-message-markdown-render/plan.md

@zerob13
Copy link
Copy Markdown
Collaborator Author

zerob13 commented May 25, 2026

Handled the latest review feedback in 9ebb6e9.\n\nUpdated:\n- Gated global TLS bypass and Ollama insecure pull behind dev/explicit debug flag.\n- Fixed background exec utility host entrypoint resolution and crashed-session completion fallback.\n- Revalidated pending input ownership before release/consume operations.\n- Ignored Telegram not-modified errors on plain edit fallback.\n- Replaced fixed test sleeps with condition-based waits and added regression coverage.\n\nValidation:\n- pnpm vitest run test/main/lib/agentRuntime/backgroundExecSessionManager.test.ts test/main/presenter/agentRuntimePresenter/pendingInputCoordinator.test.ts test/main/presenter/agentRuntimePresenter/pendingInputStore.test.ts test/main/presenter/remoteControlPresenter/telegramPoller.test.ts test/main/presenter/llmProviderPresenter/ollamaProvider.test.ts test/main/presenter/agentSessionPresenter/integration.test.ts test/main/presenter/agentRuntimePresenter/agentRuntimePresenter.test.ts\n- pnpm run format\n- pnpm run i18n\n- pnpm run lint\n- pnpm run typecheck

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/lib/agentRuntime/backgroundExecSessionManager.ts (1)

1130-1154: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add a timeout for utility-process RPCs.

pendingRequests are only rejected on host exit or synchronous postMessage failure. If the utility process stays alive but stops replying, every proxied operation can hang indefinitely.

Suggested fix
   private async request<T = void>(method: BackgroundExecRpcMethod, args: unknown[]): Promise<T> {
     const host = await this.ensureHost()
     const id = `exec_rpc_${++this.requestId}`

     return await new Promise<T>((resolve, reject) => {
+      const timeoutId = setTimeout(() => {
+        this.pendingRequests.delete(id)
+        reject(new Error(`Background exec RPC timed out: ${method}`))
+      }, 30_000)
+
       this.pendingRequests.set(id, {
-        resolve: (value) => resolve(value as T),
-        reject
+        resolve: (value) => {
+          clearTimeout(timeoutId)
+          resolve(value as T)
+        },
+        reject: (error) => {
+          clearTimeout(timeoutId)
+          reject(error)
+        }
       })

       const payload: BackgroundExecRpcRequest = {
         type: 'background-exec:request',
         id,
@@
       try {
         host.postMessage(payload)
       } catch (error) {
+        clearTimeout(timeoutId)
         this.pendingRequests.delete(id)
         reject(error)
       }
     })
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/lib/agentRuntime/backgroundExecSessionManager.ts` around lines 1130
- 1154, The request method can hang indefinitely if the utility process never
replies; add a per-request timeout: when creating the pendingRequests entry in
request() (which uses requestId, pendingRequests, ensureHost, and
host.postMessage to send a BackgroundExecRpcRequest), set a setTimeout that
deletes the pendingRequests entry and calls its reject with a clear timeout
error after a configurable TIMEOUT_MS; store the timer reference in the
pendingRequests value so that both the resolve and reject wrappers clearTimeout
when the request completes; ensure the timeout is cleaned up on synchronous
postMessage failure as well.
♻️ Duplicate comments (2)
src/main/lib/agentRuntime/backgroundExecSessionManager.ts (1)

1042-1075: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the crashed-session fallback after RPC rejection too.

The new pre-check only handles sessions already present in crashedSessions. If the utility host exits after that check but before the RPC resolves, request() rejects and callers still see an exception instead of the synthesized terminal crash result.

Suggested fix
   async waitForCompletionOrYield(
     conversationId: string,
     sessionId: string,
     yieldMs = getConfig().backgroundMs
   ): Promise<WaitForCompletionOrYieldResult> {
     const crashed = this.getCrashedCompletionResult(conversationId, sessionId)
     if (crashed) {
       return {
         kind: 'completed',
         result: crashed
       }
     }

-    const result = await this.request<WaitForCompletionOrYieldResult>('waitForCompletionOrYield', [
-      conversationId,
-      sessionId,
-      yieldMs
-    ])
+    const result = await this.request<WaitForCompletionOrYieldResult>('waitForCompletionOrYield', [
+      conversationId,
+      sessionId,
+      yieldMs
+    ]).catch((error) => {
+      const crashedAfterExit = this.getCrashedCompletionResult(conversationId, sessionId)
+      if (crashedAfterExit) {
+        return {
+          kind: 'completed',
+          result: crashedAfterExit
+        }
+      }
+      throw error
+    })
     if (result.kind === 'completed') {
       this.activeSessions.delete(sessionId)
     }
     return result
   }

   async getCompletionResult(
     conversationId: string,
     sessionId: string,
     previewChars = FOREGROUND_PREVIEW_CHARS
   ): Promise<SessionCompletionResult> {
     const crashed = this.getCrashedCompletionResult(conversationId, sessionId)
     if (crashed) {
       return crashed
     }

-    const result = await this.request<SessionCompletionResult>('getCompletionResult', [
-      conversationId,
-      sessionId,
-      previewChars
-    ])
+    const result = await this.request<SessionCompletionResult>('getCompletionResult', [
+      conversationId,
+      sessionId,
+      previewChars
+    ]).catch((error) => {
+      const crashedAfterExit = this.getCrashedCompletionResult(conversationId, sessionId)
+      if (crashedAfterExit) {
+        return crashedAfterExit
+      }
+      throw error
+    })
     this.activeSessions.delete(sessionId)
     return result
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/lib/agentRuntime/backgroundExecSessionManager.ts` around lines 1042
- 1075, The RPC calls in getCompletionResult (and the earlier
waitForCompletionOrYield path where this.request(...) is used) need to be
wrapped in a try/catch so that if this.request rejects (utility host exits after
the pre-check), you call getCrashedCompletionResult(conversationId, sessionId)
inside the catch and return that synthesized terminal crash result instead of
letting the exception propagate; specifically, wrap the await
this.request<...>('getCompletionResult', [...]) (and the await
this.request<WaitForCompletionOrYieldResult>('waitForCompletionOrYield', [...])
call) in try/catch, on success keep the existing behavior (including
activeSessions.delete(sessionId) for completed), and on catch return the value
from getCrashedCompletionResult(conversationId, sessionId) if present, otherwise
rethrow.
src/main/presenter/agentRuntimePresenter/pendingInputCoordinator.ts (1)

60-65: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate mergeItemId against sessionId before appending.

appendSteerInput() only checks mode/state, so a stale or wrong mergeItemId can merge text/files into another session’s steer item while emitUpdated(sessionId) notifies the caller’s session instead of the mutated owner.

Suggested fix
   let record: PendingSessionInputRecord
   if (options?.mergeItemId) {
+    this.assertSteerInputForSession(sessionId, options.mergeItemId)
     record = this.store.appendSteerInput(options.mergeItemId, normalizeInput(input))
   } else {
     record = this.store.createSteerInput(sessionId, normalizeInput(input))
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/presenter/agentRuntimePresenter/pendingInputCoordinator.ts` around
lines 60 - 65, Validate that options?.mergeItemId actually belongs to the
current sessionId before calling appendSteerInput: when mergeItemId is provided,
look up the steer item owner from the store (e.g., a method like
this.store.getSteerItemOwner or this.store.findSteerItem by id) and if the owner
!== sessionId treat it as invalid (either fall back to
createSteerInput(sessionId, normalizeInput(input)) or return an error). If you
do append to the found steer item, ensure emitUpdated is invoked with the
mutated item’s true owner session id (not always the incoming sessionId) so
notifications go to the correct session; keep the existing normalizeInput and
record handling otherwise.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/main/lib/agentRuntime/backgroundExecSessionManager.ts`:
- Around line 1130-1154: The request method can hang indefinitely if the utility
process never replies; add a per-request timeout: when creating the
pendingRequests entry in request() (which uses requestId, pendingRequests,
ensureHost, and host.postMessage to send a BackgroundExecRpcRequest), set a
setTimeout that deletes the pendingRequests entry and calls its reject with a
clear timeout error after a configurable TIMEOUT_MS; store the timer reference
in the pendingRequests value so that both the resolve and reject wrappers
clearTimeout when the request completes; ensure the timeout is cleaned up on
synchronous postMessage failure as well.

---

Duplicate comments:
In `@src/main/lib/agentRuntime/backgroundExecSessionManager.ts`:
- Around line 1042-1075: The RPC calls in getCompletionResult (and the earlier
waitForCompletionOrYield path where this.request(...) is used) need to be
wrapped in a try/catch so that if this.request rejects (utility host exits after
the pre-check), you call getCrashedCompletionResult(conversationId, sessionId)
inside the catch and return that synthesized terminal crash result instead of
letting the exception propagate; specifically, wrap the await
this.request<...>('getCompletionResult', [...]) (and the await
this.request<WaitForCompletionOrYieldResult>('waitForCompletionOrYield', [...])
call) in try/catch, on success keep the existing behavior (including
activeSessions.delete(sessionId) for completed), and on catch return the value
from getCrashedCompletionResult(conversationId, sessionId) if present, otherwise
rethrow.

In `@src/main/presenter/agentRuntimePresenter/pendingInputCoordinator.ts`:
- Around line 60-65: Validate that options?.mergeItemId actually belongs to the
current sessionId before calling appendSteerInput: when mergeItemId is provided,
look up the steer item owner from the store (e.g., a method like
this.store.getSteerItemOwner or this.store.findSteerItem by id) and if the owner
!== sessionId treat it as invalid (either fall back to
createSteerInput(sessionId, normalizeInput(input)) or return an error). If you
do append to the found steer item, ensure emitUpdated is invoked with the
mutated item’s true owner session id (not always the incoming sessionId) so
notifications go to the correct session; keep the existing normalizeInput and
record handling otherwise.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4e64c829-cac8-4fbf-b92f-a6afb573eae7

📥 Commits

Reviewing files that changed from the base of the PR and between facad1f and 9ebb6e9.

📒 Files selected for processing (13)
  • src/main/appMain.ts
  • src/main/lib/agentRuntime/backgroundExecSessionManager.ts
  • src/main/lib/insecureTls.ts
  • src/main/presenter/agentRuntimePresenter/pendingInputCoordinator.ts
  • src/main/presenter/agentRuntimePresenter/pendingInputStore.ts
  • src/main/presenter/llmProviderPresenter/providers/ollamaProvider.ts
  • src/main/presenter/remoteControlPresenter/telegram/telegramPoller.ts
  • test/main/lib/agentRuntime/backgroundExecSessionManager.test.ts
  • test/main/presenter/agentRuntimePresenter/agentRuntimePresenter.test.ts
  • test/main/presenter/agentRuntimePresenter/pendingInputCoordinator.test.ts
  • test/main/presenter/agentSessionPresenter/integration.test.ts
  • test/main/presenter/llmProviderPresenter/ollamaProvider.test.ts
  • test/main/presenter/remoteControlPresenter/telegramPoller.test.ts

@zerob13 zerob13 merged commit 746e5c6 into dev May 25, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant