Skip to content

feat(steer): desktop and Telegram Steer through shared run-input admission - #298

Open
sambitcreate wants to merge 1 commit into
mainfrom
feature/desktop-telegram-steer
Open

sambitcreate wants to merge 1 commit into
mainfrom
feature/desktop-telegram-steer

Conversation

@sambitcreate

Copy link
Copy Markdown
Owner

Summary

This PR replaces the desktop and Telegram parts of #220, built on top of #251. Everything goes through #251's admission service, receipt and IPC. There is no new endpoint, receipt type or IPC channel, and the Aiden Remote wire contract and contractRevision (18) are unchanged.

Desktop

  • Composer Steer now calls chat:admitRunInput. Main saves the guidance to the transcript before Pi reads it.
    • The open chat shows the committed message as soon as the receipt arrives.
    • Toasts distinguish admitted from saved after the response ended.
    • Only an uncommitted rejection or an unknown outcome keeps the draft.
  • Each text-only queued message gets a Steer action, using the shared squircle Button with the CornerDownRight icon and an accessible label.
    • committed: the row is removed and never resent.
    • uncommitted rejection: the row stays queued.
    • unknown outcome: the row stays and the queue pauses, with no auto-resend.
  • The legacy in-memory chat:steer IPC, llmClient.steer and steerGeneration are removed, so desktop Steer has a single path.

Telegram

  • /steer <guidance> admits text into the route's own running turn, using the stream captured from sendTelegramTurn.
    • It never aborts, queues or retries.
    • It replies with copy specific to each receipt.
    • It broadcasts chats:changed when the guidance is committed.
  • Without a running turn on that route, it never reaches admission.
  • Edited /steer messages are never sent.
  • /steer is added to the bot command menu and help text, and the onboarding Telegram tile mentions steering.

Harness fix (needed for #251's mobile path as well)

  • Admission commits the visible message, then Pi emits it, and the harness projected a second copy. With PiRuntimeQueueOptions.visibleChatMessageId, emission now reuses the committed id.
  • Host-committed input is excluded from takeUndeliveredQueuedMessages, so after Stop it stays as history instead of returning to the draft.

What was kept or dropped from #220

#220 piece Decision
Desktop composer/queued-row Steer UI, Telegram /steer Ported and rebuilt on #251's admission
Harness reuse of the committed visible id Kept, in a smaller form
Request-id idempotency ledger, replay, own receipt and IPC Dropped. They would add a second contract. A crash ends the run, so a redelivered /steer is rejected as run_not_active and cannot duplicate.
Persisted-input requeue / pending-input persistence Dropped. Admission is already durable before Pi accepts the input.
Compaction-store queuedUserMessageId marker change Dropped. flushDurableMessages already writes it.
Telegram afterPersist offset barrier Dropped, for the same crash semantics as the ledger

Tests

  • renderer/lib/chat-message-queue.test.ts: 25 pass. New tests cover:
    • queued-row steer for each receipt kind
    • paused queues
    • unsteerable or editing rows
    • the committed-message display helper
  • main/services/pi-agent-runtime-harness.test.ts: admitted steering appears once, and Stop keeps it as history. Both fail on main.
  • Telegram suite: 245 pass. New tests cover:
    • the /steer admission payload
    • the receipt replies
    • no admission without a running turn on the route
    • edited /steer
  • Onboarding suite: 64 pass.
  • Playwright tests/e2e/chat-message-queue.spec.ts: 15/15 pass. This includes the new queued-row Steer test and updated Stop, rejected and unknown receipt tests.
  • npm run type-check, type-check:e2e and lint are clean.
  • pi-compaction-core.test.ts has 4 failures that also fail identically on origin/main and are unrelated to this PR.

Follow-up: desktop Steer no longer feeds chat:guidance-returned. That plumbing is still generic, but may now be dead code.

🤖 Generated with Claude Code

…ssion

Desktop composer Steer, a new Steer action on queued text rows, and
Telegram /steer now all go through #251's main-owned admission
(llmClient.admitChatRunInput / chat:admitRunInput), so guidance is in the
transcript before Pi reads it. The legacy in-memory chat:steer IPC,
llmClient.steer and steerGeneration are removed.

The harness reuses the admission's committed message id when Pi emits the
queued input, instead of projecting a second visible copy, and no longer
returns host-committed input to the draft on Stop.

No Remote contract, contractRevision, endpoint, receipt or IPC changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@very-hermes-bot

Copy link
Copy Markdown
Collaborator

Hermes Review Bot

Confidence: 5

Engine: agy/gemini-3.8-flash-high
Review mode: full
Head: 080c5a968ad4341556b5d6dd5fc88bc6b5c7f8a8
Generated: 2026-10-01T07:17:43+00:00
Reviews: 1

Summary

This change routes desktop composer Steer, a new per-row Steer action on queued messages, and Telegram /steer through the shared chat:admitRunInput admission boundary. By committing user guidance to the transcript before Pi enqueues it, admitted inputs become crash-durable conversation history and avoid duplicate projection via PiRuntimeQueueOptions.visibleChatMessageId. Obsolete in-memory steering paths (chat:steer, llmClient.steer, and steerGeneration) are eliminated across main and renderer processes. A maintainer should double-check that the optimistic withCommittedRunInput cache update in chat-pane.tsx cleanly reconciles with full chat reloads across multi-window and mobile scenarios without key collisions.

Confidence Score: 5/5

Full end-to-end verification completed across desktop IPC, Telegram control routing, harness emission/projection lifecycle, queue mutation states, and Playwright e2e specs.

📁 Important Files Changed
  • main/services/chat-run-input-admission.ts: Passes visibleChatMessageId to queueSteer and queueFollowUp so the runtime harness associates host-committed IDs with queued items.
  • main/services/pi-agent-runtime-harness.ts: Tracks visibleChatMessageId on accepted queued messages, bypasses redundant projectQueuedUser calls during emission, and filters committed messages out of takeUndeliveredQueuedMessages.
  • main/services/telegram/telegram-service-core.ts: Adds /steer command handling bound to activeStream, blocks replay of edited steer commands, and handles admission receipts with tailored replies.
  • main/services/telegram/telegram-service.ts: Wires admitRunInput through llmClient.admitChatRunInput and broadcasts chats:changed upon committed guidance.
  • main/services/telegram/telegram-turn.ts: Exposes streamId and background owner via options.onStream once turn acquisition succeeds.
  • renderer/components/queued-messages.tsx: Adds accessible squircle Steer button for text-only queued rows with focus return to the composer upon row removal.
  • renderer/lib/chat-message-queue.ts: Implements claimById, steerQueuedMessage, canSteerQueuedMessage, and optimistic cache merger withCommittedRunInput.
  • renderer/main/chat-pane.tsx: Replaces legacy steer IPC with admitChatRunInput, integrates withCommittedRunInput on React Query cache, and handles composer and queued steer receipts.
  • main/handlers/chat.ts & main/services/llm-client.ts: Deletes deprecated chat:steer IPC registration and llmClient.steer implementation.

Findings

No findings.

Sequence Diagram

sequenceDiagram
  autonumber
  actor User as User (Desktop / Telegram)
  participant UI as Desktop UI / Telegram Bot
  participant Admission as Run-Input Admission
  participant Store as Chat Transcript Store
  participant Harness as Pi Agent Runtime

  User->>UI: Submit Steer (Composer / Queued Row / /steer)
  UI->>Admission: admit({ streamId, mode: "steer", text })
  Admission->>Harness: queueAdmissionBlocked() preflight probe
  Admission->>Store: appendChatMessageWithReconciliation(messageId, text)
  Admission->>Harness: queueSteer(message, { visibleChatMessageId })
  Harness-->>Admission: receipt ({ accepted: true, queue: "steer" })
  Admission-->>UI: { admitted: true, committed: true, messageId }
  UI->>UI: Optimistic transcript update & receipt toast / reply
  Harness->>Harness: On emission: reuse visibleChatMessageId & skip duplicate projection
Loading
[]

Last reviewed commit: 080c5a968ad4
Reviews (1) · Comment /hermes review to trigger a new review · /hermes review full for full re-review

@pullfrog pullfrog Bot left a comment

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.

✅ No new issues found.

Reviewed changes This review covers the initial PR head, adding desktop and Telegram Steer through shared run-input admission and updating Pi's visible-message projection.

  • Desktop Steer Replaces the legacy IPC with shared admission for composer and queued text rows, with committed transcript display and receipt-aware draft and queue handling.
  • Telegram /steer Captures the current run's stream and owner, admits only on the matching active route, and adds receipt replies and edited-command handling.
  • Harness projection Reuses host-committed message IDs when Pi emits queued input and no longer returns those messages as undelivered after Stop.
  • Coverage and guidance Adds queue, harness, Telegram, and Playwright behavior tests and updates run-control docs and the Telegram onboarding description.

Pullfrog  | View workflow run | Using GPT Luna | 𝕏

This branch has not been deployed

No deployments
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.

2 participants