opencode-plugin-teleprompt 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,11 +4,14 @@ TUI-scoped OpenCode plugin that binds a Telegram channel to one active OpenCode
4
4
 
5
5
  ## What It Does
6
6
 
7
- - Polls a strict Telegram channel for `/tp` commands
8
- - Injects `/tp <prompt>` into the currently bound OpenCode session
9
- - Sends summary-only response messages back to the same channel
10
- - Relays OpenCode permission prompts and accepts Telegram `approve`, `approve-always`, `deny`
11
- - Keeps single-owner bridge semantics across multiple OpenCode consoles
7
+ - **Frictionless Chat Interface**: Type direct prompts (like `write a python function`) in your Telegram channel without any prefixes!
8
+ - **Direct Slash Commands**: Run administrative commands like `/status`, `/queue`, `/dc` or `/approve <id>` directly in the Telegram channel.
9
+ - **Backward Compatibility**: Fully supports old `/tp <prompt>` and `/tp:<command>` syntax out of the box.
10
+ - **Relays Permission Prompts**: Directly relays OpenCode permission requests and accepts direct `/approve`, `/approve-always`, and `/deny` replies.
11
+ - **Inline Keyboards For Approvals**: Permission prompts arrive as Telegram messages with `Approve once` / `Approve always` / `Deny` buttons — no typing required.
12
+ - **Relays OpenCode Questions**: When the agent asks the user a question (via the OpenCode `question` tool), teleprompt posts it to Telegram with inline option buttons (single-choice direct, multi-choice with toggle+confirm). Tap to reply, or fall back to `/qreply`/`/qreject` text commands.
13
+ - **Lease-based Owner Semantics**: Keeps single-owner bridge semantics across multiple OpenCode consoles.
14
+ - **Instant Ctrl+C Exit**: Stops event streams and cleans up instantly, ensuring no exit lags when shutting down OpenCode.
12
15
 
13
16
  ## V1 Limits
14
17
 
@@ -67,33 +70,35 @@ OpenCode installs npm plugins automatically at startup.
67
70
  5. While teleprompt is active, local prompt input is locked for that session.
68
71
  6. Disconnect options:
69
72
  - Press `Esc` twice in a row in OpenCode
70
- - Send `/tp:dc` in Telegram channel
73
+ - Send `/dc` in Telegram channel
71
74
  - Run `/tp:stop` in OpenCode
72
75
  7. Use Telegram channel commands:
73
- - `/tp <prompt>`
74
- - `/tp:interrupt`
75
- - `/tp:queue`
76
- - `/tp:cancel <job_id|last>`
77
- - `/tp:retry`
78
- - `/tp:context`
79
- - `/tp:compact`
80
- - `/tp:newsession`
81
- - `/tp:reset-context`
82
- - `/tp:who`
83
- - `/tp:health`
84
- - `/tp:reclaim`
85
- - `/tp:history`
86
- - `/tp:last-error`
87
- - `/tp:model`
88
- - `/tp:model fast`
89
- - `/tp:model smart`
90
- - `/tp:model max`
91
- - `/tp:model <provider>/<model>`
92
- - `/tp approve <request_id>`
93
- - `/tp approve-always <request_id>`
94
- - `/tp deny <request_id>`
95
- - `/tp status`
96
- - `/tp:dc`
76
+ - `<prompt>` (any message NOT starting with `/` is treated directly as a prompt!)
77
+ - `/interrupt` (or `/tp:interrupt`)
78
+ - `/queue` (or `/tp:queue`)
79
+ - `/cancel <job_id|last>` (or `/tp:cancel <job_id|last>`)
80
+ - `/retry` (or `/tp:retry`)
81
+ - `/context` (or `/tp:context`)
82
+ - `/compact` (or `/tp:compact`)
83
+ - `/newsession` (or `/tp:newsession`)
84
+ - `/reset-context` (or `/tp:reset-context`)
85
+ - `/who` (or `/tp:who`)
86
+ - `/health` (or `/tp:health`)
87
+ - `/reclaim` (or `/tp:reclaim`)
88
+ - `/history` (or `/tp:history`)
89
+ - `/last-error` (or `/tp:last-error`)
90
+ - `/model` (or `/tp:model`)
91
+ - `/model fast` (or `/tp:model fast`)
92
+ - `/model smart` (or `/tp:model smart`)
93
+ - `/model max` (or `/tp:model max`)
94
+ - `/model <provider>/<model>`
95
+ - `/approve <request_id>`
96
+ - `/approve-always <request_id>`
97
+ - `/deny <request_id>`
98
+ - `/qreply <request_id>` (then `<index>:<label1>|<label2>` lines for each question)
99
+ - `/qreject <request_id>`
100
+ - `/status`
101
+ - `/dc` (or `/tp:dc`)
97
102
 
98
103
  During remote runs, teleprompt posts lifecycle updates (`accepted`, `queued`, `running`, `waiting-permission`, `completed`, `failed`) and result summaries are sent as replies to the originating Telegram message for clear correlation.
99
104
 
@@ -119,23 +124,34 @@ During remote runs, teleprompt posts lifecycle updates (`accepted`, `queued`, `r
119
124
  - `OPENCODE_TELEGRAM_CHANNEL_ID`
120
125
  3. Start OpenCode and ensure plugin loads without startup errors.
121
126
  4. Open one session, run `/tp:start`, verify bridge binds.
122
- 5. Send `/tp status` from Telegram and confirm status response.
123
- 6. Send `/tp test ping` and confirm lifecycle + summary reply.
124
- 7. Send `/tp:dc` and confirm local input is unlocked in OpenCode.
127
+ 5. Send `/status` from Telegram and confirm status response.
128
+ 6. Send `test ping` and confirm lifecycle + summary reply.
129
+ 7. Send `/dc` and confirm local input is unlocked in OpenCode.
125
130
 
126
131
  ## Telegram Live E2E Quick Checklist
127
132
 
128
133
  1. Start OpenCode session and run `/tp:start`.
129
- 2. From Telegram channel, run `/tp status` and verify owner/session info.
130
- 3. Run `/tp:model` and switch once with `/tp:model fast` (or explicit provider/model).
131
- 4. Send `/tp write a 1-line summary of this session` and verify:
134
+ 2. From Telegram channel, run `/status` and verify owner/session info.
135
+ 3. Run `/model` and switch once with `/model fast` (or explicit provider/model).
136
+ 4. Send `write a 1-line summary of this session` and verify:
132
137
  - `accepted` -> `running` -> `completed`
133
138
  - summary arrives as reply to the same Telegram message
134
139
  5. Trigger a permissioned action prompt and verify:
135
- - plugin posts permission request with `request_id`
136
- - `/tp approve <request_id>` (or `/tp deny <request_id>`) is applied immediately
137
- 6. Send `/tp:interrupt` during a long run and confirm graceful stop.
138
- 7. Send `/tp:dc` and confirm disconnect/unlock behavior.
140
+ - plugin posts a permission request message with `Approve once`,
141
+ `Approve always`, and `Deny` inline buttons
142
+ - tapping a button (or typing `/approve <request_id>` / `/deny <request_id>`)
143
+ applies the reply via the OpenCode SDK and removes the buttons
144
+ 6. Trigger an OpenCode question (e.g. "what's the goal?") and verify:
145
+ - plugin posts a `waiting-question` notice and the question with
146
+ inline option buttons
147
+ - single-choice: tapping an option button replies via
148
+ `client.question.reply` and removes the buttons
149
+ - multi-choice: toggling options marks them with ✅ and a `Confirm`
150
+ button finalizes the reply with all selected labels
151
+ - text fallback: `/qreply <request_id>` with one
152
+ `<index>:<label1>|<label2>` line per question
153
+ 7. Send `/interrupt` during a long run and confirm graceful stop.
154
+ 8. Send `/dc` and confirm disconnect/unlock behavior.
139
155
 
140
156
  ## Command Reference
141
157
 
@@ -149,32 +165,37 @@ During remote runs, teleprompt posts lifecycle updates (`accepted`, `queued`, `r
149
165
 
150
166
  ### Telegram Commands
151
167
 
152
- - `/tp <prompt>`: Queue a new prompt for the currently bound session.
153
- - `/tp:interrupt`: Abort the currently running remote prompt without disconnecting the bridge.
154
- - `/tp:queue`: Show active prompt and queued prompts.
155
- - `/tp:cancel <job_id|last>`: Remove a queued prompt by job ID, or remove the newest queued prompt with `last`.
156
- - `/tp:retry`: Re-queue the most recent prompt from prompt history.
157
- - `/tp:context`: Show compact session context (recent prompts, summaries, and changed files).
158
- - `/tp:compact`: Trigger session summarization/compaction for long-running sessions.
159
- - `/tp:newsession`: Create a new OpenCode session and switch teleprompt binding to it.
160
- - `/tp:reset-context`: Alias behavior for creating/switching to a fresh session context.
161
- - `/tp:who`: Show lease ownership details (current instance, lease owner, ownership state).
162
- - `/tp:health`: Show bridge health (lease age/staleness, poller/event stream status, queue stats).
163
- - `/tp:reclaim`: Try to reclaim bridge ownership for the current instance.
164
- - `/tp:history`: Show recent run history with status and short summaries.
165
- - `/tp:last-error`: Show the latest failed or interrupted run summary.
166
- - `/tp:model`: List available models by provider and show current model selection.
167
- - `/tp:model fast`: Select a model using the `fast` preset resolver.
168
- - `/tp:model smart`: Select a model using the `smart` preset resolver.
169
- - `/tp:model max`: Select a model using the `max` preset resolver.
170
- - `/tp:model <provider>/<model>`: Select an explicit provider/model for the bound session.
171
- - `/tp approve <request_id>`: Approve a pending permission request once.
172
- - `/tp approve-always <request_id>`: Approve a pending permission request and persist approval behavior when supported.
173
- - `/tp deny <request_id>`: Reject a pending permission request.
174
- - `/tp status`: Show bridge status from Telegram.
175
- - `/tp:dc`: Disconnect teleprompt from Telegram and unbind the current session.
168
+ - `<prompt>`: Any message not starting with `/` is queued directly as a prompt for the session.
169
+ - `/interrupt`: Abort the currently running remote prompt without disconnecting the bridge.
170
+ - `/queue`: Show active prompt and queued prompts.
171
+ - `/cancel <job_id|last>`: Remove a queued prompt by job ID, or remove the newest queued prompt with `last`.
172
+ - `/retry`: Re-queue the most recent prompt from prompt history.
173
+ - `/context`: Show compact session context (recent prompts, summaries, and changed files).
174
+ - `/compact`: Trigger session summarization/compaction for long-running sessions.
175
+ - `/newsession`: Create a new OpenCode session and switch teleprompt binding to it.
176
+ - `/reset-context`: Alias behavior for creating/switching to a fresh session context.
177
+ - `/who`: Show lease ownership details (current instance, lease owner, ownership state).
178
+ - `/health`: Show bridge health (lease age/staleness, poller/event stream status, queue stats).
179
+ - `/reclaim`: Try to reclaim bridge ownership for the current instance.
180
+ - `/history`: Show recent run history with status and short summaries.
181
+ - `/last-error`: Show the latest failed or interrupted run summary.
182
+ - `/model`: List available models by provider and show current model selection.
183
+ - `/model fast`: Select a model using the `fast` preset resolver.
184
+ - `/model smart`: Select a model using the `smart` preset resolver.
185
+ - `/model max`: Select a model using the `max` preset resolver.
186
+ - `/model <provider>/<model>`: Select an explicit provider/model for the bound session.
187
+ - `/approve <request_id>`: Approve a pending permission request once.
188
+ - `/approve-always <request_id>`: Approve a pending permission request and persist approval behavior when supported.
189
+ - `/deny <request_id>`: Reject a pending permission request.
190
+ - `/qreply <request_id>`: Answer an OpenCode question. Follow with one line per
191
+ question in the form `<index>:<label1>|<label2>`. Only known option labels
192
+ are accepted; unknown labels are dropped. As a fallback to the inline
193
+ keyboard buttons, you can also use this for single-choice questions.
194
+ - `/qreject <request_id>`: Reject an OpenCode question without answering it.
195
+ - `/status`: Show bridge status from Telegram.
196
+ - `/dc`: Disconnect teleprompt from Telegram and unbind the current session.
176
197
 
177
198
  ## Shutdown Behavior
178
199
 
179
- - On normal TUI disposal (`Ctrl+C`, clean exit), poller and heartbeat stop and lease is released.
200
+ - On normal TUI disposal (`Ctrl+C`, clean exit), poller and heartbeat stop and lease is released **instantly** without hangs.
180
201
  - On unclean termination, lease expires by TTL and next owner instance can reclaim.
@@ -1,3 +1,4 @@
1
+ import type { QuestionInfo } from "../types.js";
1
2
  type EventHandlers = {
2
3
  onAssistantCompleted: (sessionID: string, assistantMessageID: string, parentUserMessageID: string) => Promise<void>;
3
4
  onPermissionAsked: (event: {
@@ -7,6 +8,24 @@ type EventHandlers = {
7
8
  patterns: string[];
8
9
  metadata: Record<string, unknown>;
9
10
  }) => Promise<void>;
11
+ onPermissionReplied?: (requestID: string) => Promise<void>;
12
+ onQuestionAsked?: (event: {
13
+ id: string;
14
+ sessionID: string;
15
+ questions: QuestionInfo[];
16
+ tool?: {
17
+ messageID: string;
18
+ callID: string;
19
+ };
20
+ }) => Promise<void>;
21
+ onQuestionReplied?: (event: {
22
+ id: string;
23
+ sessionID: string;
24
+ }) => Promise<void>;
25
+ onQuestionRejected?: (event: {
26
+ id: string;
27
+ sessionID: string;
28
+ }) => Promise<void>;
10
29
  onSessionError: (event: {
11
30
  sessionID?: string;
12
31
  error?: {
@@ -14,19 +33,42 @@ type EventHandlers = {
14
33
  };
15
34
  }) => Promise<void>;
16
35
  onUserMessage: (sessionID: string, userMessageID: string) => Promise<void>;
36
+ onMessagePartUpdated?: (input: {
37
+ sessionID: string;
38
+ messageID: string;
39
+ part: {
40
+ type: string;
41
+ text?: string;
42
+ [key: string]: unknown;
43
+ };
44
+ delta?: string;
45
+ }) => Promise<void>;
17
46
  onStreamError?: (error: unknown) => Promise<void>;
18
47
  };
19
48
  export declare class SessionEventStream {
20
49
  private readonly client;
21
50
  private readonly sessionID;
51
+ private readonly directory;
22
52
  private readonly handlers;
53
+ private readonly debugLog?;
23
54
  private abort;
24
55
  private running?;
25
56
  private stream?;
26
- constructor(client: any, sessionID: string, handlers: EventHandlers);
57
+ private pollTimer?;
58
+ constructor(client: any, sessionID: string, directory: string, handlers: EventHandlers, debugLog?: ((message: string) => void) | undefined);
59
+ /** Exposed for testing */
60
+ __test?: {
61
+ pollQuestionListOnce: () => Promise<void>;
62
+ seenForwardedQuestionCalls: Set<string>;
63
+ };
27
64
  start(): void;
28
65
  stop(): Promise<void>;
66
+ private startQuestionPoller;
67
+ private pollQuestionListOnce;
29
68
  private loop;
30
69
  private handleEvent;
70
+ private readonly seenForwardedQuestionCalls;
71
+ private handleQuestionToolPart;
72
+ private pollQuestionList;
31
73
  }
32
74
  export {};