opencode-plugin-teleprompt 0.1.0

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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +180 -0
  3. package/dist/config.d.ts +2 -0
  4. package/dist/config.js +40 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/index.d.ts +7 -0
  7. package/dist/index.js +8 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/opencode/binding.d.ts +2 -0
  10. package/dist/opencode/binding.js +12 -0
  11. package/dist/opencode/binding.js.map +1 -0
  12. package/dist/opencode/events.d.ts +31 -0
  13. package/dist/opencode/events.js +74 -0
  14. package/dist/opencode/events.js.map +1 -0
  15. package/dist/opencode/permissions.d.ts +4 -0
  16. package/dist/opencode/permissions.js +66 -0
  17. package/dist/opencode/permissions.js.map +1 -0
  18. package/dist/opencode/submit.d.ts +7 -0
  19. package/dist/opencode/submit.js +14 -0
  20. package/dist/opencode/submit.js.map +1 -0
  21. package/dist/runtime/controller.d.ts +78 -0
  22. package/dist/runtime/controller.js +1180 -0
  23. package/dist/runtime/controller.js.map +1 -0
  24. package/dist/runtime/shutdown.d.ts +1 -0
  25. package/dist/runtime/shutdown.js +10 -0
  26. package/dist/runtime/shutdown.js.map +1 -0
  27. package/dist/state/lease.d.ts +12 -0
  28. package/dist/state/lease.js +56 -0
  29. package/dist/state/lease.js.map +1 -0
  30. package/dist/state/store.d.ts +9 -0
  31. package/dist/state/store.js +59 -0
  32. package/dist/state/store.js.map +1 -0
  33. package/dist/summary/format.d.ts +2 -0
  34. package/dist/summary/format.js +23 -0
  35. package/dist/summary/format.js.map +1 -0
  36. package/dist/telegram/api.d.ts +16 -0
  37. package/dist/telegram/api.js +78 -0
  38. package/dist/telegram/api.js.map +1 -0
  39. package/dist/telegram/parser.d.ts +3 -0
  40. package/dist/telegram/parser.js +267 -0
  41. package/dist/telegram/parser.js.map +1 -0
  42. package/dist/telegram/poller.d.ts +18 -0
  43. package/dist/telegram/poller.js +50 -0
  44. package/dist/telegram/poller.js.map +1 -0
  45. package/dist/tui-types.d.ts +55 -0
  46. package/dist/tui-types.js +2 -0
  47. package/dist/tui-types.js.map +1 -0
  48. package/dist/tui.d.ts +2 -0
  49. package/dist/tui.js +98 -0
  50. package/dist/tui.js.map +1 -0
  51. package/dist/types.d.ts +167 -0
  52. package/dist/types.js +2 -0
  53. package/dist/types.js.map +1 -0
  54. package/package.json +55 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Berkecan Ozgur
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,180 @@
1
+ # opencode-plugin-teleprompt
2
+
3
+ TUI-scoped OpenCode plugin that binds a Telegram channel to one active OpenCode session.
4
+
5
+ ## What It Does
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
12
+
13
+ ## V1 Limits
14
+
15
+ - Single strict channel
16
+ - Single active bound session
17
+ - Summary-only output (no full transcript)
18
+ - No auto-bind on startup
19
+
20
+ ## Requirements
21
+
22
+ - Telegram bot token
23
+ - Bot must be an admin in the target channel
24
+ - Telegram credentials can be provided either:
25
+ - via env vars (`OPENCODE_TELEGRAM_BOT_TOKEN`, `OPENCODE_TELEGRAM_CHANNEL_ID`)
26
+ - or at runtime with `/tp:start <bot_token> <channel_id>` or `/tp:credentials <bot_token> <channel_id>`
27
+
28
+ Optional:
29
+
30
+ - `OPENCODE_TELEGRAM_POLL_TIMEOUT_SEC` (default: `30`)
31
+ - `OPENCODE_TELEGRAM_HEARTBEAT_MS` (default: `10000`)
32
+ - `OPENCODE_TELEGRAM_LEASE_TTL_MS` (default: `30000`)
33
+ - `OPENCODE_TELEGRAM_SUMMARY_MAX_CHARS` (default: `1200`)
34
+ - `OPENCODE_TELEGRAM_ONLINE_NOTICE` (`true`/`false`, default: `true`)
35
+ - `OPENCODE_TELEGRAM_OFFLINE_NOTICE` (`true`/`false`, default: `true`)
36
+
37
+ ## Install And Build
38
+
39
+ ```bash
40
+ npm install
41
+ npm run build
42
+ ```
43
+
44
+ ## Install In OpenCode (npm package)
45
+
46
+ Add this plugin package name into your OpenCode config:
47
+
48
+ `opencode.json`
49
+
50
+ ```json
51
+ {
52
+ "$schema": "https://opencode.ai/config.json",
53
+ "plugin": ["opencode-plugin-teleprompt"]
54
+ }
55
+ ```
56
+
57
+ OpenCode installs npm plugins automatically at startup.
58
+
59
+ ## Activation And Use
60
+
61
+ 1. Publish this package to npm.
62
+ 2. Add package name to `opencode.json` as shown above.
63
+ 3. Open a session in OpenCode TUI.
64
+ 4. Run `/tp:start` in OpenCode to activate teleprompt for the current session.
65
+ - If env vars are missing, run `/tp:start <bot_token> <channel_id>` once (or `/tp:credentials <bot_token> <channel_id>` then `/tp:start`).
66
+ - Runtime credentials are session-only and are cleared on plugin/session shutdown.
67
+ 5. While teleprompt is active, local prompt input is locked for that session.
68
+ 6. Disconnect options:
69
+ - Press `Esc` twice in a row in OpenCode
70
+ - Send `/tp:dc` in Telegram channel
71
+ - Run `/tp:stop` in OpenCode
72
+ 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`
97
+
98
+ 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
+
100
+ ## Publish And Install Verification
101
+
102
+ ### npm Publish Preparation
103
+
104
+ 1. Run release validation locally:
105
+ - `npm run verify:release`
106
+ 2. Validate npm auth/account:
107
+ - `npm whoami`
108
+ 3. Publish:
109
+ - `npm publish`
110
+
111
+ `prepublishOnly` is enabled and will run release verification automatically before publishing.
112
+
113
+ ### Clean-Machine Install Checklist
114
+
115
+ 1. On a clean machine, create `opencode.json` with:
116
+ - `"plugin": ["opencode-plugin-teleprompt"]`
117
+ 2. Set required env vars:
118
+ - `OPENCODE_TELEGRAM_BOT_TOKEN`
119
+ - `OPENCODE_TELEGRAM_CHANNEL_ID`
120
+ 3. Start OpenCode and ensure plugin loads without startup errors.
121
+ 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.
125
+
126
+ ## Telegram Live E2E Quick Checklist
127
+
128
+ 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:
132
+ - `accepted` -> `running` -> `completed`
133
+ - summary arrives as reply to the same Telegram message
134
+ 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.
139
+
140
+ ## Command Reference
141
+
142
+ ### OpenCode Local Commands
143
+
144
+ - `/tp:start`: Bind teleprompt to the current OpenCode session and start Telegram polling.
145
+ - `/tp:start <bot_token> <channel_id>`: Bind with session-only credentials when env vars are not set.
146
+ - `/tp:credentials <bot_token> <channel_id>`: Store session-only credentials for the current runtime.
147
+ - `/tp:stop`: Unbind teleprompt, stop polling, and unlock local session input.
148
+ - `/tp:status`: Show current bridge status in OpenCode (session, owner, model, queue, permissions).
149
+
150
+ ### Telegram Commands
151
+
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.
176
+
177
+ ## Shutdown Behavior
178
+
179
+ - On normal TUI disposal (`Ctrl+C`, clean exit), poller and heartbeat stop and lease is released.
180
+ - On unclean termination, lease expires by TTL and next owner instance can reclaim.
@@ -0,0 +1,2 @@
1
+ import type { BridgeConfig } from "./types.js";
2
+ export declare function loadConfig(): BridgeConfig;
package/dist/config.js ADDED
@@ -0,0 +1,40 @@
1
+ function readNumber(name, fallback) {
2
+ const raw = process.env[name];
3
+ if (!raw)
4
+ return fallback;
5
+ const parsed = Number(raw);
6
+ if (!Number.isFinite(parsed) || parsed <= 0) {
7
+ throw new Error(`${name} must be a positive number`);
8
+ }
9
+ return parsed;
10
+ }
11
+ function readBoolean(name, fallback) {
12
+ const raw = process.env[name];
13
+ if (!raw)
14
+ return fallback;
15
+ const normalized = raw.trim().toLowerCase();
16
+ if (normalized === "true" || normalized === "1" || normalized === "yes") {
17
+ return true;
18
+ }
19
+ if (normalized === "false" || normalized === "0" || normalized === "no") {
20
+ return false;
21
+ }
22
+ throw new Error(`${name} must be one of: true, false, 1, 0, yes, no`);
23
+ }
24
+ export function loadConfig() {
25
+ const botToken = process.env.OPENCODE_TELEGRAM_BOT_TOKEN?.trim();
26
+ const channelID = process.env.OPENCODE_TELEGRAM_CHANNEL_ID?.trim();
27
+ const prefix = "/tp";
28
+ return {
29
+ botToken: botToken || "",
30
+ channelID: channelID || "",
31
+ prefix,
32
+ pollTimeoutSec: readNumber("OPENCODE_TELEGRAM_POLL_TIMEOUT_SEC", 30),
33
+ heartbeatMs: readNumber("OPENCODE_TELEGRAM_HEARTBEAT_MS", 10_000),
34
+ leaseTtlMs: readNumber("OPENCODE_TELEGRAM_LEASE_TTL_MS", 30_000),
35
+ summaryMaxChars: readNumber("OPENCODE_TELEGRAM_SUMMARY_MAX_CHARS", 1_200),
36
+ onlineNotice: readBoolean("OPENCODE_TELEGRAM_ONLINE_NOTICE", true),
37
+ offlineNotice: readBoolean("OPENCODE_TELEGRAM_OFFLINE_NOTICE", true),
38
+ };
39
+ }
40
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,SAAS,UAAU,CAAC,IAAY,EAAE,QAAgB;IAChD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,CAAC,GAAG;QAAE,OAAO,QAAQ,CAAC;IAC1B,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,4BAA4B,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,WAAW,CAAC,IAAY,EAAE,QAAiB;IAClD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,CAAC,GAAG;QAAE,OAAO,QAAQ,CAAC;IAC1B,MAAM,UAAU,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,UAAU,KAAK,MAAM,IAAI,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,KAAK,EAAE,CAAC;QACxE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,UAAU,KAAK,OAAO,IAAI,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QACxE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,6CAA6C,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,2BAA2B,EAAE,IAAI,EAAE,CAAC;IACjE,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,4BAA4B,EAAE,IAAI,EAAE,CAAC;IACnE,MAAM,MAAM,GAAG,KAAK,CAAC;IACrB,OAAO;QACL,QAAQ,EAAE,QAAQ,IAAI,EAAE;QACxB,SAAS,EAAE,SAAS,IAAI,EAAE;QAC1B,MAAM;QACN,cAAc,EAAE,UAAU,CAAC,oCAAoC,EAAE,EAAE,CAAC;QACpE,WAAW,EAAE,UAAU,CAAC,gCAAgC,EAAE,MAAM,CAAC;QACjE,UAAU,EAAE,UAAU,CAAC,gCAAgC,EAAE,MAAM,CAAC;QAChE,eAAe,EAAE,UAAU,CAAC,qCAAqC,EAAE,KAAK,CAAC;QACzE,YAAY,EAAE,WAAW,CAAC,iCAAiC,EAAE,IAAI,CAAC;QAClE,aAAa,EAAE,WAAW,CAAC,kCAAkC,EAAE,IAAI,CAAC;KACrE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { tui } from "./tui.js";
2
+ declare const plugin: {
3
+ id: string;
4
+ tui: import("./tui-types.js").TuiPlugin;
5
+ };
6
+ export default plugin;
7
+ export { tui };
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ import { tui } from "./tui.js";
2
+ const plugin = {
3
+ id: "teleprompt",
4
+ tui,
5
+ };
6
+ export default plugin;
7
+ export { tui };
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,MAAM,MAAM,GAAG;IACb,EAAE,EAAE,YAAY;IAChB,GAAG;CACJ,CAAC;AAEF,eAAe,MAAM,CAAC;AACtB,OAAO,EAAE,GAAG,EAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { TuiPluginApi } from "../tui-types.js";
2
+ export declare function getCurrentSessionID(api: TuiPluginApi): string;
@@ -0,0 +1,12 @@
1
+ export function getCurrentSessionID(api) {
2
+ const route = api.route.current;
3
+ if (route.name !== "session") {
4
+ throw new Error("Open a session first, then run telegram.bind.");
5
+ }
6
+ const sessionID = route.params?.sessionID;
7
+ if (!sessionID || typeof sessionID !== "string") {
8
+ throw new Error("Current route does not contain a valid session ID.");
9
+ }
10
+ return sessionID;
11
+ }
12
+ //# sourceMappingURL=binding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binding.js","sourceRoot":"","sources":["../../src/opencode/binding.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,mBAAmB,CAAC,GAAiB;IACnD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC;IAChC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IACD,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC;IAC1C,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,31 @@
1
+ type EventHandlers = {
2
+ onAssistantCompleted: (sessionID: string, assistantMessageID: string, parentUserMessageID: string) => Promise<void>;
3
+ onPermissionAsked: (event: {
4
+ id: string;
5
+ sessionID: string;
6
+ permission: string;
7
+ patterns: string[];
8
+ metadata: Record<string, unknown>;
9
+ }) => Promise<void>;
10
+ onSessionError: (event: {
11
+ sessionID?: string;
12
+ error?: {
13
+ name?: string;
14
+ };
15
+ }) => Promise<void>;
16
+ onUserMessage: (sessionID: string, userMessageID: string) => Promise<void>;
17
+ onStreamError?: (error: unknown) => Promise<void>;
18
+ };
19
+ export declare class SessionEventStream {
20
+ private readonly client;
21
+ private readonly sessionID;
22
+ private readonly handlers;
23
+ private abort;
24
+ private running?;
25
+ constructor(client: any, sessionID: string, handlers: EventHandlers);
26
+ start(): void;
27
+ stop(): Promise<void>;
28
+ private loop;
29
+ private handleEvent;
30
+ }
31
+ export {};
@@ -0,0 +1,74 @@
1
+ export class SessionEventStream {
2
+ client;
3
+ sessionID;
4
+ handlers;
5
+ abort = new AbortController();
6
+ running;
7
+ constructor(client, sessionID, handlers) {
8
+ this.client = client;
9
+ this.sessionID = sessionID;
10
+ this.handlers = handlers;
11
+ }
12
+ start() {
13
+ if (this.running)
14
+ return;
15
+ this.running = this.loop().catch(async (error) => {
16
+ if (this.abort.signal.aborted)
17
+ return;
18
+ await this.handlers.onStreamError?.(error);
19
+ });
20
+ }
21
+ async stop() {
22
+ this.abort.abort();
23
+ await this.running;
24
+ this.running = undefined;
25
+ }
26
+ async loop() {
27
+ const streamResult = await this.client.event.subscribe();
28
+ try {
29
+ for await (const event of streamResult.stream) {
30
+ if (this.abort.signal.aborted)
31
+ break;
32
+ if (!event || typeof event !== "object")
33
+ continue;
34
+ await this.handleEvent(event);
35
+ }
36
+ }
37
+ catch {
38
+ if (!this.abort.signal.aborted) {
39
+ throw new Error("Event stream terminated unexpectedly.");
40
+ }
41
+ }
42
+ }
43
+ async handleEvent(event) {
44
+ if (event.type === "permission.asked") {
45
+ const data = event.properties;
46
+ if (data.sessionID !== this.sessionID)
47
+ return;
48
+ await this.handlers.onPermissionAsked(data);
49
+ return;
50
+ }
51
+ if (event.type === "session.error") {
52
+ const data = event.properties;
53
+ if (data.sessionID && data.sessionID !== this.sessionID)
54
+ return;
55
+ await this.handlers.onSessionError(data);
56
+ return;
57
+ }
58
+ if (event.type !== "message.updated")
59
+ return;
60
+ const data = event.properties;
61
+ if (data.sessionID !== this.sessionID)
62
+ return;
63
+ if (data.info.role === "assistant") {
64
+ if (!data.info.time?.completed || !data.info.parentID)
65
+ return;
66
+ await this.handlers.onAssistantCompleted(data.sessionID, data.info.id, data.info.parentID);
67
+ return;
68
+ }
69
+ if (data.info.role !== "user")
70
+ return;
71
+ await this.handlers.onUserMessage(data.sessionID, data.info.id);
72
+ }
73
+ }
74
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/opencode/events.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { PendingPermission, PermissionAskInput } from "../types.js";
2
+ export declare function toPendingPermission(input: PermissionAskInput): PendingPermission;
3
+ export declare function formatPermissionRequestMessage(prefix: string, permission: PendingPermission): string;
4
+ export declare function replyPermission(client: any, requestID: string, action: "once" | "always" | "reject"): Promise<void>;
@@ -0,0 +1,66 @@
1
+ export function toPendingPermission(input) {
2
+ return {
3
+ requestID: input.id,
4
+ sessionID: input.sessionID,
5
+ permission: input.permission,
6
+ patterns: input.patterns,
7
+ metadata: input.metadata,
8
+ announcedAt: Date.now(),
9
+ };
10
+ }
11
+ export function formatPermissionRequestMessage(prefix, permission) {
12
+ const toolType = typeof permission.metadata?.tool === "string"
13
+ ? permission.metadata.tool
14
+ : typeof permission.metadata?.toolName === "string"
15
+ ? permission.metadata.toolName
16
+ : "unknown";
17
+ const risk = classifyRisk(permission.permission, permission.metadata);
18
+ const target = permission.patterns[0] || "(none)";
19
+ const patterns = permission.patterns.length > 0
20
+ ? permission.patterns.map((v) => `- ${v}`).join("\n")
21
+ : "- (none)";
22
+ return [
23
+ "OpenCode permission request:",
24
+ `request_id: ${permission.requestID}`,
25
+ `session_id: ${permission.sessionID}`,
26
+ `tool: ${toolType}`,
27
+ `permission: ${permission.permission}`,
28
+ `risk: ${risk}`,
29
+ `target: ${target}`,
30
+ "patterns:",
31
+ patterns,
32
+ "",
33
+ "Reply examples:",
34
+ `- Approve once: ${prefix} approve ${permission.requestID}`,
35
+ `- Approve always: ${prefix} approve-always ${permission.requestID}`,
36
+ `- Deny: ${prefix} deny ${permission.requestID}`,
37
+ ].join("\n");
38
+ }
39
+ function classifyRisk(permission, metadata) {
40
+ const merged = `${permission} ${JSON.stringify(metadata)}`.toLowerCase();
41
+ if (merged.includes("exec") ||
42
+ merged.includes("shell") ||
43
+ merged.includes("command")) {
44
+ return "exec";
45
+ }
46
+ if (merged.includes("network") ||
47
+ merged.includes("http") ||
48
+ merged.includes("fetch") ||
49
+ merged.includes("url")) {
50
+ return "network";
51
+ }
52
+ if (merged.includes("write") ||
53
+ merged.includes("delete") ||
54
+ merged.includes("modify") ||
55
+ merged.includes("create")) {
56
+ return "write";
57
+ }
58
+ return "read";
59
+ }
60
+ export async function replyPermission(client, requestID, action) {
61
+ await client.permission.reply({
62
+ requestID,
63
+ reply: action,
64
+ });
65
+ }
66
+ //# sourceMappingURL=permissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/opencode/permissions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export declare function createTelegramUserMessageID(updateID: number): string;
2
+ export declare function submitPrompt(client: any, sessionID: string, prompt: string, updateID: number, model?: {
3
+ providerID: string;
4
+ modelID: string;
5
+ }): Promise<{
6
+ userMessageID: string;
7
+ }>;
@@ -0,0 +1,14 @@
1
+ export function createTelegramUserMessageID(updateID) {
2
+ return `tg-${updateID}`;
3
+ }
4
+ export async function submitPrompt(client, sessionID, prompt, updateID, model) {
5
+ const userMessageID = createTelegramUserMessageID(updateID);
6
+ await client.session.promptAsync({
7
+ sessionID,
8
+ messageID: userMessageID,
9
+ ...(model ? { model } : {}),
10
+ parts: [{ type: "text", text: prompt }],
11
+ });
12
+ return { userMessageID };
13
+ }
14
+ //# sourceMappingURL=submit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submit.js","sourceRoot":"","sources":["../../src/opencode/submit.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,2BAA2B,CAAC,QAAgB;IAC1D,OAAO,MAAM,QAAQ,EAAE,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAW,EACX,SAAiB,EACjB,MAAc,EACd,QAAgB,EAChB,KAGC;IAED,MAAM,aAAa,GAAG,2BAA2B,CAAC,QAAQ,CAAC,CAAC;IAC5D,MAAM,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC;QAC/B,SAAS;QACT,SAAS,EAAE,aAAa;QACxB,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;KACxC,CAAC,CAAC;IACH,OAAO,EAAE,aAAa,EAAE,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,78 @@
1
+ import type { BridgeConfig, ParsedTelegramCommand, RuntimeDeps } from "../types.js";
2
+ import type { TuiPluginApi } from "../tui-types.js";
3
+ type SessionCredentials = {
4
+ botToken: string;
5
+ channelID: string;
6
+ };
7
+ export declare class BridgeController {
8
+ private readonly api;
9
+ private readonly config;
10
+ private readonly deps;
11
+ private readonly instanceID;
12
+ private readonly client;
13
+ private telegram;
14
+ private readonly store;
15
+ private readonly lease;
16
+ private data?;
17
+ private heartbeatTimer?;
18
+ private pollAbort?;
19
+ private pollTask?;
20
+ private eventStream?;
21
+ private eventRestartTimer?;
22
+ private readonly shutdownOnce;
23
+ private processingQueue;
24
+ private lastEscAt;
25
+ private sessionCredentials?;
26
+ constructor(api: TuiPluginApi, config: BridgeConfig, storePath: string, deps?: Partial<RuntimeDeps>);
27
+ init(): Promise<void>;
28
+ bindCurrent(credentials?: SessionCredentials): Promise<string>;
29
+ unbind(): Promise<void>;
30
+ statusLine(): Promise<string>;
31
+ handleTelegramCommand(command: ParsedTelegramCommand): Promise<void>;
32
+ shutdown(): Promise<void>;
33
+ handleLocalTuiCommand(command: string): Promise<void>;
34
+ private processPromptQueue;
35
+ private onAssistantCompleted;
36
+ private onUserMessage;
37
+ private onPermissionAsked;
38
+ private handlePermissionReply;
39
+ private handleModelCommand;
40
+ private fetchAvailableModels;
41
+ private buildSummary;
42
+ private handleQueue;
43
+ private handleCancel;
44
+ private handleRetry;
45
+ private handleContext;
46
+ private handleCompact;
47
+ private handleNewSession;
48
+ private handleResetContext;
49
+ private handleWho;
50
+ private handleHealth;
51
+ private handleReclaim;
52
+ private handleHistory;
53
+ private handleLastError;
54
+ private handleInterrupt;
55
+ private onSessionError;
56
+ private getGitBranch;
57
+ private appendPromptHistory;
58
+ private getSessionTitle;
59
+ private switchBoundSession;
60
+ private startHeartbeat;
61
+ private stopHeartbeat;
62
+ private startPolling;
63
+ private startEventStream;
64
+ private onEventStreamError;
65
+ private normalizeLocalCommand;
66
+ private splitCommandArgs;
67
+ private parseCredentialArgs;
68
+ private parseStartWithCredentials;
69
+ private parseCredentialCommand;
70
+ private resolveCredentials;
71
+ private getTelegramApi;
72
+ private requireChannelID;
73
+ private stopRuntime;
74
+ private requireState;
75
+ private persist;
76
+ private syncState;
77
+ }
78
+ export {};