Skip to content

Fix repo-picker freeze + presets-only settings, Responses runner, Telegram relay#16

Merged
inhaq merged 5 commits into
mainfrom
feat/picker-fix-presets-responses-telegram
Jun 15, 2026
Merged

Fix repo-picker freeze + presets-only settings, Responses runner, Telegram relay#16
inhaq merged 5 commits into
mainfrom
feat/picker-fix-presets-responses-telegram

Conversation

@inhaq

@inhaq inhaq commented Jun 14, 2026

Copy link
Copy Markdown
Owner

This pull request was created by @kiro-agent on behalf of @inhaq 👻

Comment with /kiro fix to address specific feedback or /kiro all to address everything.
Learn about Kiro Web


Summary

Four related changes that move the app toward "configure once, run from anywhere":

1. Fix the repo-picker freeze (the bug)

pick_directory was a synchronous Tauri command, so it ran on the main UI thread and called blocking_pick_folder(). The native dialog needs that same thread's event loop to run, so the thread deadlocked waiting for a dialog that could never appear — the app froze on every repo selection.

Fix: make pick_directory async and use the non-blocking pick_folder + a oneshot channel (the dialog runs on the main thread; the handler awaits off it). check_git_repo and which_commands are made async too so their git/PATH probes never stall the UI. Adds the tokio sync feature.

2. Presets-only settings

Replaces the provider/model matrix with three first-class runner presets:

  • codex-clicodex exec --json --model <model> (edits files)
  • openai-responses — OpenAI Responses API for a model id like gpt-5.5
  • kiro-clikiro-cli chat --no-interactive --trust-tools=… with KIRO_API_KEY

The settings page picks a preset per role (writer = actor, reviewer = critic) with an editable model id (default gpt-5.5 where configurable), shows auth status (command installed / key stored), and keeps raw runner JSON behind the Advanced escape hatch. Adds a configurable Kiro trusted-tools field.

3. OpenAI Responses runner (new transport)

New ResponsesRunner (kind: "http-responses") targets POST /v1/responses (input + instructionsoutput_text), distinct from the Chat-Completions HttpRunner. Wired into the factory and profile schema.

4. Telegram notify + control relay (loopback-safe)

A private phone-updates layer: pushes final run status to one allowlisted chat and long-polls for replies, treating each as a new goal it submits as a run (reusing the last run's presets + repo). Outbound-only to api.telegram.org — the engine stays loopback-only with no inbound exposure. Configurable from the desktop settings (token + chat id stored as secrets).

Testing

  • npm run typecheck (engine) and tsc --noEmit (desktop): clean
  • npm test: 197 passing (+24 new — Responses runner and Telegram relay)
  • Engine boots and bundles (bun) with and without Telegram configured
  • repo.rs validated with rustfmt

Known limitations / notes for review

  • The Tauri Rust app could not be compiled in CI here (the sandbox lacks webkit2gtk-4.1), so please build/run the desktop app locally to confirm the picker now opens and the Telegram card works. The Rust change is the canonical Tauri v2 async-picker pattern and parses cleanly under rustfmt.
  • gpt-5.5 is set as the default model id but is fully editable in settings — change it without touching code if the id differs.
  • Telegram config is applied on engine restart (saving from the settings card triggers it); this aborts any in-flight runs, as noted in the UI.
  • Chat-initiated runs reuse the most recent run's env + repo, so start one run from the desktop first; replies from the phone then reuse that configuration.

Summary by CodeRabbit

  • New Features
    • Added Telegram relay to both receive run status updates and start new runs from approved chat messages.
    • Introduced runner presets-based model selection, including Kiro CLI “trusted tools”.
    • Added OpenAI Responses API support as a new runner option.
    • Added a desktop “Phone updates (Telegram)” card with secret storage and safe restart handling.
  • Updates
    • Reworked settings and Secrets UI to manage presets by environment key.
  • Bug Fixes / Reliability
    • Improved non-blocking native directory picking and updated git/tool checks to run asynchronously.
  • Tests
    • Added coverage for the Responses runner and Telegram relay behavior.

inhaq and others added 4 commits June 14, 2026 17:58
The folder picker was a synchronous Tauri command, which runs on the main
(UI) thread. blocking_pick_folder() then blocked that thread waiting for a
dialog that needs the same thread's event loop to run -> deadlock/freeze on
every repo selection.

Make pick_directory async and use the non-blocking pick_folder + a oneshot
channel so the dialog runs on the main thread while the handler awaits off it.
check_git_repo and which_commands are made async too so their git/PATH probes
never stall the UI thread. Adds tokio 'sync' feature for the oneshot channel.

Co-authored-by: Inzimam Ul Haq <27832433+inhaq@users.noreply.github.com>
A new ResponsesRunner (kind 'http-responses') targets POST /v1/responses
(input + instructions -> output_text), distinct from HttpRunner which speaks
the older Chat Completions shape. Wired into the runner factory and the
RunnerProfile schema/kind union. Mirrors HttpRunner's timeout, cancellation,
env-by-reference auth, quota detection, and non-throwing error handling.
Includes a 13-case test covering extraction, request shaping, and quota.

Co-authored-by: Inzimam Ul Haq <27832433+inhaq@users.noreply.github.com>
Replace the provider/model matrix with three first-class runner presets:
- codex-cli        codex exec --json --model <model> (edits files)
- openai-responses OpenAI Responses API for a model id like gpt-5.5
- kiro-cli         kiro-cli chat --no-interactive --trust-tools=... (KIRO_API_KEY)

The settings page now picks a preset per role (writer=actor, reviewer=critic)
with an editable model id (default gpt-5.5 where configurable), shows auth
status (command installed / key stored), and keeps raw runner JSON behind the
Advanced escape hatch. Adds a configurable Kiro trusted-tools field. Bumps the
settings storage key (v2) so legacy provider/model selections fall back to
preset defaults.

Co-authored-by: Inzimam Ul Haq <27832433+inhaq@users.noreply.github.com>
Add a private phone-updates layer that pushes final run status to a single
allowlisted Telegram chat and long-polls for replies, treating each as a new
goal it submits as a run (reusing the last run's presets + repo). It uses only
OUTBOUND HTTPS to api.telegram.org, so the engine stays loopback-only with no
inbound exposure.

- src/notify/telegram.ts: TelegramRelay (push + control loop, allowlist, backlog
  skip, backoff) and createTelegramRelayFromEnv (LOOPWRIGHT_TELEGRAM_* or plain
  TELEGRAM_* secret names).
- server: extract submitRun() core shared by the HTTP route and the relay,
  expose submitRun + lastRunConfig, and add an outbound ServerNotifier hook
  fired on terminal status (best-effort, never affects a run).
- index: build the relay from env, attach the server, start/stop with lifecycle.
- The desktop settings page gains a Telegram card (stores token + chat id as
  secrets, restarts the engine to apply).
Includes an 11-case relay test.

Co-authored-by: Inzimam Ul Haq <27832433+inhaq@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d4c434f7-61c6-4b69-a9c4-62ddfe868733

📥 Commits

Reviewing files that changed from the base of the PR and between efd2d25 and 90120fa.

📒 Files selected for processing (4)
  • desktop/src/main.ts
  • desktop/src/settings.ts
  • src/notify/telegram.ts
  • src/runners/responsesRunner.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • desktop/src/main.ts
  • src/runners/responsesRunner.ts
  • src/notify/telegram.ts
  • desktop/src/settings.ts

📝 Walkthrough

Walkthrough

This PR migrates the desktop settings from a provider/model catalog to runner presets (codex-cli, openai-responses, kiro-cli), converts Tauri repo commands to async, adds a new ResponsesRunner for the OpenAI Responses API, introduces a TelegramRelay for outbound notifications and inbound goal submission via long-polling, and refactors the server to expose programmatic submitRun/lastRunConfig interfaces.

Changes

Desktop: Async Tauri Commands and Preset-Based Settings

Layer / File(s) Summary
Tauri async commands and tokio sync feature
desktop/src-tauri/Cargo.toml, desktop/src-tauri/src/repo.rs
Adds the sync tokio feature and rewrites pick_directory, check_git_repo, and which_commands as async Tauri commands; pick_directory uses a tokio::sync::oneshot to bridge the non-blocking pick_folder callback.
Preset catalog, RunSettings migration, and profileFor
desktop/src/settings.ts
Introduces PresetKind, RunnerPreset, PRESETS, updates ModelChoice/RunSettings to use preset instead of provider, migrates defaults and loadSettings coercions to v2, and replaces provider-based profile construction with preset-based profileFor feeding buildRunEnv. Removes MODEL_CATALOG, getProvider, findModel.
Desktop UI: preset pickers, Kiro settings, and Telegram card
desktop/src/main.ts
Replaces provider/model selectors with preset-driven rolePicker dropdowns, adds a Kiro trusted-tools input, introduces a desktop-only Telegram bot-token/chat-id settings card with save/remove/restart actions, and reworks the Secrets catalog from MODEL_CATALOG to PRESETS.

ResponsesRunner: New http-responses Backend

Layer / File(s) Summary
RunnerKind extension and contract definitions
src/runners/agentRunner.ts
Extends RunnerKind union and RunnerProfileSchema validation enum to include "http-responses", and updates documentation to list the new ResponsesRunner backend.
ResponsesRunner implementation and helpers
src/runners/responsesRunner.ts
Adds expandEnvRefs for ${VAR} substitution in headers, extractResponsesText to parse Responses output, ResponsesRunner constructor with Zod option validation and fetch resolution, ResponsesRunner.run with POST/timeout/abort/quota-detection/truncation, request-building helpers (joinUrl, buildHeaders, buildBody), and safeText for defensive response reading.
Runner factory integration and tests
src/runners/runnerFactory.ts, test/responsesRunner.test.ts
Wires ResponsesRunner into createRunner factory dispatch for the "http-responses" kind; comprehensive Vitest suite covering option validation, request construction, env-based header/auth expansion, usage reporting, quota exhaustion detection, and transport-error handling.

Telegram Relay and Server Programmatic Submission

Layer / File(s) Summary
Server types and notifier interface
src/server/server.ts
Adds exported SubmitResult, RunFinishedInfo, ServerNotifier types; extends CreateServerOptions with optional notifier and LoopwrightServer interface with submitRun(body): Promise<SubmitResult> and lastRunConfig() methods.
Server programmatic submit and notifier integration
src/server/server.ts
Adds server-local notifier/lastRun state, refactors POST /api/runs handler to delegate to submitRun core logic, validates goal/repoDir/sessionId/maxActiveRuns, caches lastRun env/repoDir for chat reuse, and invokes notifier on terminal completion via notifySafe while preserving hub publishing.
TelegramRelay implementation and tests
src/notify/telegram.ts, test/telegram.test.ts
Adds formatFinalStatus for terminal-status messages, TelegramRelay class with lifecycle (attach/start/stop), inbound control (long-poll getUpdates with backlog skipping, handleUpdate with allowlist/command/submitRun dispatch), outbound notifications (runFinished/sendMessage), createTelegramRelayFromEnv factory, and utility helpers; full test coverage for formatting, control-channel handling, polling, and construction.
Server entrypoint Telegram wiring
src/server/index.ts
Constructs optional Telegram relay from environment variables, passes it as notifier to createServer, attaches and starts the relay post-creation, and stops it during graceful shutdown.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(173, 216, 230, 0.5)
    Note over TelegramUser,LoopwrightServer: Inbound goal submission via Telegram
  end
  participant TelegramUser as Telegram User
  participant TelegramAPI as Telegram API
  participant TelegramRelay
  participant LoopwrightServer

  TelegramRelay->>TelegramAPI: getUpdates(offset, long-poll timeout)
  TelegramAPI-->>TelegramRelay: message update from allowed chatId
  TelegramRelay->>TelegramRelay: handleUpdate — verify chatId, parse goal text
  TelegramRelay->>LoopwrightServer: submitRun(goal, lastRun.env, repoDir)
  LoopwrightServer-->>TelegramRelay: SubmitResult { ok, sessionId }
  TelegramRelay->>TelegramAPI: sendMessage("Run started")
  LoopwrightServer-->>TelegramRelay: runFinished(RunFinishedInfo)
  TelegramRelay->>TelegramAPI: sendMessage(formatFinalStatus)
  TelegramAPI-->>TelegramUser: status message
Loading
sequenceDiagram
  rect rgba(200, 230, 180, 0.5)
    Note over DesktopUI,ResponsesRunner: Preset-driven run execution via ResponsesRunner
  end
  participant DesktopUI as Desktop UI (main.ts)
  participant Settings as settings.ts
  participant ResponsesRunner
  participant OpenAI as OpenAI /v1/responses

  DesktopUI->>Settings: buildRunEnv(settings)
  Settings->>Settings: profileFor(openai-responses preset)
  Settings-->>DesktopUI: LOOPWRIGHT_RUNNERS env var
  DesktopUI->>ResponsesRunner: run(RunRequest)
  ResponsesRunner->>OpenAI: POST {baseUrl}/responses (model, input, apiKey)
  OpenAI-->>ResponsesRunner: 200 response body
  ResponsesRunner->>ResponsesRunner: extractResponsesText(body)
  ResponsesRunner-->>DesktopUI: RunResult { text, meta }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • inhaq/loopwright#15: Directly precedes this PR's desktop/src/settings.ts preset migration — the same buildRunEnv/provider catalog plumbing is being replaced here.
  • inhaq/loopwright#13: Overlaps with the desktop/src/main.ts model-settings UI rework — both modify the same preset/runner selection code paths.
  • inhaq/loopwright#14: Shares desktop/src-tauri/src/repo.rs — this PR converts the same pick_directory/check_git_repo/which_commands commands from sync to async.

Poem

🐇 Hop hop, the rabbit digs a relay deep,
Where Telegram whispers wake goals from sleep.
Presets now bloom where old providers lay,
Responses API joins the runners' fray.
Async oneshots bridge the folder quest —
A cottontail's refactor, simply the best! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title comprehensively covers all four major changes (repo-picker freeze fix, presets-only settings, Responses runner, Telegram relay) and accurately reflects the main objectives of the pull request.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 feat/picker-fix-presets-responses-telegram

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
desktop/src/main.ts (1)

352-366: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Only mark CLI tools missing after the probe actually succeeds.

If detectCommands() throws, installed stays {}, but commandMissing() and the detected-tools badges both treat that as "missing" for every CLI. That turns a probe failure into false onboarding/publishing warnings. Track probe success separately and only surface missing-command states when that probe completed.

Also applies to: 369-385, 728-734

🤖 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 `@desktop/src/main.ts` around lines 352 - 366, The issue is that when
`detectCommands()` throws an exception, the `installed` variable remains as an
empty object, but this is then incorrectly interpreted as "all CLI tools are
missing" by `commandMissing()` and the detected-tools badges, causing false
warnings. The fix is to track whether the probe succeeded separately (similar to
how `knowKeys` tracks success for `listSecretKeys()`). Create a new boolean
variable (like `probeSucceeded` or `cliToolsDetected`) that is only set to true
when `detectCommands()` completes without throwing. Then update all references
to `installed` in `commandMissing()` and the detected-tools badge logic to only
treat commands as missing if the probe actually succeeded. Apply this same
pattern to the other affected locations at lines 369-385 and 728-734 where
similar probe-and-track patterns exist with different data (listSecretKeys,
etc.).
🤖 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 `@desktop/src/main.ts`:
- Around line 764-817: The Telegram configuration flow requires both bot token
and chat ID to be properly set, but the current implementation only validates
the token on first save and uses stale hasToken/hasChat values captured once at
initialization. Modify the saveBtn click handler to require both
TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID to be present (either already stored or
newly provided) before allowing save. After successfully calling setSecret and
restartEngine, recompute the hasToken and hasChat flags from the current stored
state (or refetch them) and update tgStatus.textContent based on the actual
current values instead of hardcoding "stored · stored". Apply the same pattern
to the removeBtn handler (which begins around line 820) to ensure that after
removing either secret, the cached flags are refreshed and the status display
reflects the actual stored state.
- Around line 784-817: The saveBtn click event handler directly calls
restartEngine() without checking for active runs, which can silently abort
in-flight operations. Instead of calling restartEngine() directly after the
setSecret calls succeed, route the restart through the same active-run guard
used elsewhere in this file by checking activeRunCount() first and prompting the
user for confirmation if there are active runs before proceeding with the engine
restart. Apply this same guarded restart pattern to all locations in this
section where restartEngine() is invoked.

In `@desktop/src/settings.ts`:
- Line 160: The STORAGE_KEY constant at line 160 in desktop/src/settings.ts was
changed to v2 without providing a migration path for existing users, causing
them to lose their saved settings. In the loadSettings() function at lines
231-245, add a fallback mechanism that first attempts to read settings from the
legacy storage key (the previous version before v2), then if found, applies any
necessary coercion to that legacy payload, and finally saves the migrated
settings under the new STORAGE_KEY before returning. This ensures existing
users' settings are preserved and properly migrated when they upgrade.

In `@src/notify/telegram.ts`:
- Around line 168-171: The offset is being updated before the update is handled,
so if handleUpdate throws an exception, that update is still acknowledged and
won't be retried. Move the offset update statement (currently at the beginning
of the loop in the for loop iterating over updates) to after the handleUpdate
call completes successfully. Specifically, move the line that sets this.offset
to after the if statement that calls handleUpdate, ensuring the offset only
advances when the update has been successfully handled by the handleUpdate
method.

In `@src/runners/responsesRunner.ts`:
- Around line 262-268: The body object construction in responsesRunner.ts
spreads extraBody after defining the required model and input fields, allowing
extraBody to silently override these core request parameters. Reorder the object
properties so that model and input are defined after spreading extraBody,
ensuring the required fields cannot be overridden by configuration values and
the actual prompt and model used match req.prompt and this.profile.model
respectively.

---

Outside diff comments:
In `@desktop/src/main.ts`:
- Around line 352-366: The issue is that when `detectCommands()` throws an
exception, the `installed` variable remains as an empty object, but this is then
incorrectly interpreted as "all CLI tools are missing" by `commandMissing()` and
the detected-tools badges, causing false warnings. The fix is to track whether
the probe succeeded separately (similar to how `knowKeys` tracks success for
`listSecretKeys()`). Create a new boolean variable (like `probeSucceeded` or
`cliToolsDetected`) that is only set to true when `detectCommands()` completes
without throwing. Then update all references to `installed` in
`commandMissing()` and the detected-tools badge logic to only treat commands as
missing if the probe actually succeeded. Apply this same pattern to the other
affected locations at lines 369-385 and 728-734 where similar probe-and-track
patterns exist with different data (listSecretKeys, etc.).
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4a7f9faf-6609-426a-9c34-edc5ac8d9142

📥 Commits

Reviewing files that changed from the base of the PR and between e9052a7 and efd2d25.

📒 Files selected for processing (12)
  • desktop/src-tauri/Cargo.toml
  • desktop/src-tauri/src/repo.rs
  • desktop/src/main.ts
  • desktop/src/settings.ts
  • src/notify/telegram.ts
  • src/runners/agentRunner.ts
  • src/runners/responsesRunner.ts
  • src/runners/runnerFactory.ts
  • src/server/index.ts
  • src/server/server.ts
  • test/responsesRunner.test.ts
  • test/telegram.test.ts

Comment thread desktop/src/main.ts Outdated
Comment thread desktop/src/main.ts
Comment thread desktop/src/settings.ts
Comment thread src/notify/telegram.ts
Comment thread src/runners/responsesRunner.ts
- responsesRunner: spread extraBody before model/input so config can't
  override the core request fields.
- telegram: advance the getUpdates offset only after a successful handleUpdate,
  so a throwing handler leaves the update unacked for retry instead of dropping it.
- settings: migrate legacy v1 localStorage on upgrade (provider->preset coercion
  + carry-over of repo/run/publishing prefs) and resave under v2, instead of
  silently discarding saved settings.
- desktop Telegram card: require BOTH secrets before saving, refresh the cached
  stored-state from the keychain after save/remove (no optimistic 'stored'),
  and route the engine restart through a shared active-run guard (restartEngineGuarded)
  so it warns before aborting in-flight runs.
@inhaq inhaq merged commit 62fc4fb into main Jun 15, 2026
8 checks passed
@inhaq inhaq deleted the feat/picker-fix-presets-responses-telegram branch June 15, 2026 08:17
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