Skip to content

Commit a903b05

Browse files
authored
feat: add telegram remote control (#1383)
* feat(remote): add telegram control * fix(remote): harden polling and status * chore: update system prompt * feat(remote-control): add telegram remote control * feat(remote): refine telegram control * fix(remote): harden telegram runtime * fix(remote): seal ipc teardown
1 parent ec02a20 commit a903b05

File tree

79 files changed

+8162
-237
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+8162
-237
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Implementation Plan
2+
3+
## Architecture
4+
5+
- Add `src/main/presenter/remoteControlPresenter/` as a main-process presenter that exposes a small shared contract to the renderer through the existing `presenter:call` IPC path.
6+
- Keep Telegram transport in Electron main using native `fetch` and Bot API long polling.
7+
- Reuse `newAgentPresenter.sendMessage()` and `DeepChatAgentPresenter` for message persistence, stream state, title generation, and stop behavior.
8+
- Add detached session creation to `newAgentPresenter` so remote conversations do not require a renderer-bound window.
9+
10+
## Main-Process Modules
11+
12+
- `remoteBindingStore`
13+
- Stores `remoteControl.telegram` config in Electron Store.
14+
- Persists poll offset, allowlist, default agent id, pair code, internal stream mode, and endpoint bindings.
15+
- Keeps active event IDs, `/sessions` snapshots, and `/model` inline-menu state in memory.
16+
- `remoteAuthGuard`
17+
- Enforces private-chat-only usage.
18+
- Authenticates strictly by numeric `from.id`.
19+
- Supports one-time `/pair <code>` flow.
20+
- `remoteConversationRunner`
21+
- Creates detached sessions when needed.
22+
- Resolves a valid enabled DeepChat default agent before creating unbound Telegram sessions.
23+
- Lists recent sessions by the currently bound session's agent when a valid binding exists; otherwise falls back to the default DeepChat agent.
24+
- Exposes current-session lookup and bound-session model switching through `newAgentPresenter.setSessionModel()`.
25+
- Reuses `newAgentPresenter.sendMessage()` for plain-text Telegram input.
26+
- Tracks the active assistant message/event for `/stop`.
27+
- `remoteCommandRouter`
28+
- Handles `/start`, `/help`, `/pair`, `/new`, `/sessions`, `/use`, `/stop`, `/status`, `/model`, plain text, and `/model` callback actions.
29+
- `telegramClient`
30+
- Calls `getMe`, `getUpdates`, `sendMessageDraft`, `sendMessage`, `sendChatAction`, `setMyCommands`, `setMessageReaction`, `editMessageText`, `editMessageReplyMarkup`, and `answerCallbackQuery`.
31+
- `telegramParser`
32+
- Parses private text updates, bot commands, and callback queries into one internal event shape.
33+
- `telegramOutbound`
34+
- Builds plain-text assistant output, detects “desktop confirmation required” states, and chunks output to 4096 characters.
35+
- `telegramPoller`
36+
- Runs a single sequential long-poll loop.
37+
- Advances the stored offset only after a specific update is handled successfully.
38+
- Uses exponential backoff on failures.
39+
- Only adds reactions for plain-text conversation messages and clears them after the conversation completes or fails.
40+
41+
## Shared / IPC Contract
42+
43+
- Add `src/shared/types/presenters/remote-control.presenter.d.ts`.
44+
- Expose methods for reading/saving Telegram settings, reading runtime status, listing/removing bindings, reading pairing snapshot, generating/clearing pair codes, clearing bindings, and testing Telegram hooks.
45+
46+
## Renderer Plan
47+
48+
- Add a new `Remote` settings route and `RemoteSettings.vue`.
49+
- Move Telegram configuration out of `NotificationsHooksSettings.vue`.
50+
- Keep `Hooks` for Discord, Confirmo, and command hooks only.
51+
- Simplify the first-layer Telegram remote UI to allowed user IDs, default agent selection, pairing, and binding management.
52+
- Show pairing and binding management inside dialogs; hide remote/hook detail forms when their toggle is off.
53+
- Reuse existing i18n flow for all renderer-visible strings.
54+
55+
## Data Model
56+
57+
- SQLite
58+
- No schema change.
59+
- Sessions/messages continue to use existing new-agent tables.
60+
- Electron Store
61+
- `hooksNotifications.telegram`
62+
- Shared Telegram bot token and hook notification target settings.
63+
- `remoteControl.telegram`
64+
- `enabled`
65+
- `allowlist`
66+
- `defaultAgentId`
67+
- `streamMode`
68+
- `pairing`
69+
- `pollOffset`
70+
- `bindings`
71+
72+
## Event / Request Flow
73+
74+
1. Renderer saves Remote settings through `remoteControlPresenter`.
75+
2. Main presenter updates `hooksNotifications.telegram` and `remoteControl.telegram`, then rebuilds the Telegram runtime if required.
76+
3. Telegram poller receives private updates through `getUpdates`.
77+
4. Parser normalizes message and callback payloads.
78+
5. Router applies auth, command handling, and `/model` inline-menu transitions.
79+
6. Plain text enters `newAgentPresenter.sendMessage()` using the bound or newly created detached session.
80+
7. `/model` callback actions edit a single bot menu message in place and answer the callback query.
81+
8. Poller watches assistant message state and sends draft/final Telegram output.
82+
9. If the assistant pauses on a permission/question action, Telegram returns a desktop-confirmation notice instead of bypassing approval.
83+
84+
## Testing Strategy
85+
86+
- Unit tests for `remoteAuthGuard`.
87+
- Unit tests for `remoteBindingStore`.
88+
- Unit tests for `remoteCommandRouter`.
89+
- Unit tests for `remoteConversationRunner`.
90+
- Unit tests for `telegramParser`.
91+
- Unit tests for `telegramClient` request payloads.
92+
- Unit tests for `telegramOutbound` chunking/final-text behavior.
93+
- Unit tests for Telegram command registration, callback handling, and message reaction lifecycle behavior.
94+
- Presenter-level tests for detached session creation.
95+
- Presenter-level tests for stop-by-event behavior.
96+
97+
## Migration Note
98+
99+
- No SQLite migration is required.
100+
- Existing Telegram hook settings remain compatible.
101+
- New remote state is additive and can be removed cleanly by disabling remote control or clearing the config blob.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Telegram Remote Control
2+
3+
## Summary
4+
5+
Add Telegram private-chat remote control to the `dev` branch without changing DeepChat's main architecture. The bot runtime lives in Electron main, remote messages reuse the existing DeepChat session/message/stream pipeline, and the settings UI moves Telegram-related controls into a new `Remote` page.
6+
7+
This increment simplifies the Remote settings UX, removes the user-facing stream mode switch, adds a selectable default DeepChat agent for new remote sessions, and keeps remote session/model control intentionally lightweight for v1.
8+
9+
## User Stories
10+
11+
- As a DeepChat desktop user, I can pair my Telegram account and send a DM to my bot to continue a DeepChat conversation remotely.
12+
- As a paired user, my first Telegram DM can create a detached DeepChat session even when no chat window is focused.
13+
- As a paired user, I can stop an active generation with `/stop`, list recent sessions with `/sessions`, and rebind to one with `/use`.
14+
- As a user configuring integrations, I can manage Telegram remote control and Telegram hook notifications from a single `Remote` settings page.
15+
- As a user configuring Telegram pairing, I only see a simple “Pair” entry point in the first layer and complete the flow from a small modal.
16+
- As a user using multiple DeepChat agents, I can choose which enabled DeepChat agent new Telegram sessions should use by default.
17+
- As a paired user, I can change the current bound session's provider/model through a two-step Telegram inline keyboard opened from `/model`.
18+
19+
## Acceptance Criteria
20+
21+
- An authorized Telegram private-chat user can DM the bot and receive a DeepChat assistant reply.
22+
- The first DM can create a detached DeepChat session without a focused window or existing `webContents` binding.
23+
- Subsequent DMs continue the currently bound DeepChat session for that Telegram endpoint.
24+
- `/stop` cancels the active generation for that remote endpoint through the existing stop path.
25+
- `/sessions` lists recent sessions for the currently bound session's agent; if no valid binding exists, it falls back to the configured default DeepChat agent.
26+
- `/use <index>` binds the endpoint to the corresponding session from the latest `/sessions` list.
27+
- `/model` opens a Telegram inline keyboard, first for enabled providers and then for enabled models, and only changes the current bound session.
28+
- Remote-triggered conversations do not bypass the existing permission flow for tools, files, or settings.
29+
- Telegram settings appear under a new `Remote` settings page, and the old Telegram section is removed from `Hooks`.
30+
- The Remote settings page hides the remote-control detail area when remote control is disabled, and hides the Telegram hook detail area when hooks are disabled.
31+
- The first-layer Telegram remote UI shows allowed user IDs, a default DeepChat agent selector, a pairing button, and a binding-management button; pair code display moves into a modal.
32+
- Telegram remote no longer exposes a stream mode selector; draft streaming remains the internal default.
33+
- Telegram runtime registers its default command list when it starts.
34+
- Only plain-text conversation messages get a temporary bot reaction; slash commands and inline-button callbacks do not, and the reaction is cleared after the reply finishes or fails.
35+
- New Telegram sessions use the selected default DeepChat agent, inheriting that agent's default model/project/permission defaults; existing bound sessions remain bound even if the default agent later changes.
36+
- Existing local desktop chat behavior remains unchanged.
37+
38+
## Constraints
39+
40+
- Telegram only for v1.
41+
- No generic channel registry or plugin system.
42+
- Bot runtime lives in Electron main, not renderer or preload.
43+
- SQLite remains the source of truth for sessions and messages.
44+
- Config/state uses the existing Electron Store path; no new SQLite migration is introduced.
45+
- v1 is private-chat only. No group support, no media upload, no remote permission approvals.
46+
47+
## Non-Goals
48+
49+
- Group chats, forum moderation, or multi-platform messaging channels.
50+
- Telegram media uploads, arbitrary bot button workflows outside `/model`, or Markdown/HTML rich formatting.
51+
- Remote approval of tool/file/settings permission requests.
52+
- A standalone helper daemon or public remote-control SDK.
53+
54+
## Compatibility
55+
56+
- Existing Telegram hook settings remain valid and are reused by the new `Remote` page.
57+
- New remote-specific state lives under `remoteControl.telegram` in Electron Store.
58+
- `remoteControl.telegram.defaultAgentId` stores the default DeepChat agent for new Telegram sessions.
59+
- Disabling remote control or clearing the bot token cleanly stops polling without affecting local chats or persisted SQLite data.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Tasks
2+
3+
1. Main presenter
4+
- Add `remoteControlPresenter` contract and register it in main `Presenter`.
5+
- Rebuild runtime on settings changes and app init.
6+
7+
2. Detached session support
8+
- Add `createDetachedSession()` to `newAgentPresenter`.
9+
- Ensure first remote message still triggers title generation through the shared send path.
10+
11+
3. Remote runtime services
12+
- Implement auth guard, binding store, command router, and conversation runner.
13+
- Route new Telegram sessions through a validated default DeepChat agent.
14+
- Make `/sessions` prefer the currently bound agent and add bound-session `/model` switching.
15+
16+
4. Telegram transport
17+
- Implement native-fetch Telegram client.
18+
- Implement long polling with offset persistence and backoff.
19+
- Implement plain-text outbound chunking and draft/final delivery.
20+
- Register default Telegram bot commands, support inline-keyboard callback queries, and keep reactions scoped to plain-text conversations.
21+
22+
5. Renderer
23+
- Add `RemoteSettings.vue`.
24+
- Add `settings-remote` route.
25+
- Remove Telegram UI from `NotificationsHooksSettings.vue`.
26+
- Simplify the Telegram remote first layer and move pairing / binding management into dialogs.
27+
- Hide remote and hook detail sections when their toggle is off.
28+
- Add i18n keys for `Remote`.
29+
30+
6. Tests
31+
- Add main tests for auth guard, bindings, command routing, and chunking.
32+
- Add parser/client tests for callback query and inline-keyboard payloads.
33+
- Extend existing presenter tests for detached session creation, session model switching, and stop-by-event behavior.
34+
35+
7. Validation
36+
- Run formatting, i18n check, lint, and targeted tests when dependencies are available in the worktree.

src/main/presenter/configPresenter/systemPromptHelper.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ type SetSetting = <T>(key: string, value: T) => void
77

88
export const DEFAULT_SYSTEM_PROMPT = `You are DeepChat, a highly capable AI assistant. Your goal is to fully complete the user’s requested task before handing the conversation back to them. Keep working autonomously until the task is fully resolved.
99
Be thorough in gathering information. Before replying, make sure you have all the details necessary to provide a complete solution. Use additional tools or ask clarifying questions when needed, but if you can find the answer on your own, avoid asking the user for help.
10-
When using tools, briefly describe your intended steps first—for example, which tool you’ll use and for what purpose.
11-
Adhere to this in all languages.Respond in the same language as the user's query.`
10+
When using tools, briefly describe your intended steps first—for example, which tool you’ll use and for what purpose.`
1211

1312
type GetSetting = <T>(key: string) => T | undefined
1413

src/main/presenter/deepchatAgentPresenter/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,28 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
888888
this.setSessionStatus(sessionId, 'idle')
889889
}
890890

891+
getActiveGeneration(sessionId: string): { eventId: string; runId: string } | null {
892+
const activeGeneration = this.activeGenerations.get(sessionId)
893+
if (!activeGeneration) {
894+
return null
895+
}
896+
897+
return {
898+
eventId: activeGeneration.messageId,
899+
runId: activeGeneration.runId
900+
}
901+
}
902+
903+
async cancelGenerationByEventId(sessionId: string, eventId: string): Promise<boolean> {
904+
const activeGeneration = this.activeGenerations.get(sessionId)
905+
if (!activeGeneration || activeGeneration.messageId !== eventId) {
906+
return false
907+
}
908+
909+
await this.cancelGeneration(sessionId)
910+
return true
911+
}
912+
891913
private dispatchTerminalHooks(
892914
sessionId: string,
893915
state: DeepChatSessionState | undefined,

0 commit comments

Comments
 (0)