@wrongstack/telegram 0.284.0 → 0.285.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.
- package/dist/bot.d.ts +192 -0
- package/dist/bot.d.ts.map +1 -0
- package/dist/config.d.ts +108 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/format.d.ts +70 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/index.d.ts +5 -59
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -16
- package/dist/index.js.map +7 -1
- package/dist/poll-lock.d.ts +37 -0
- package/dist/poll-lock.d.ts.map +1 -0
- package/dist/redact.d.ts +7 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/slash-commands/index.d.ts +8 -0
- package/dist/slash-commands/index.d.ts.map +1 -0
- package/dist/tools/telegram-approve.d.ts +42 -0
- package/dist/tools/telegram-approve.d.ts.map +1 -0
- package/dist/tools/telegram-read.d.ts +18 -0
- package/dist/tools/telegram-read.d.ts.map +1 -0
- package/dist/tools/telegram-send.d.ts +18 -0
- package/dist/tools/telegram-send.d.ts.map +1 -0
- package/package.json +5 -6
package/dist/bot.d.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import type { Logger } from '@wrongstack/core';
|
|
2
|
+
import type { PollLock } from './poll-lock.js';
|
|
3
|
+
interface TgUser {
|
|
4
|
+
id: number;
|
|
5
|
+
is_bot: boolean;
|
|
6
|
+
first_name: string;
|
|
7
|
+
username?: string | undefined;
|
|
8
|
+
}
|
|
9
|
+
interface TgChat {
|
|
10
|
+
id: number;
|
|
11
|
+
type: 'private' | 'group' | 'supergroup' | 'channel';
|
|
12
|
+
title?: string | undefined;
|
|
13
|
+
username?: string | undefined;
|
|
14
|
+
}
|
|
15
|
+
interface TgMessage {
|
|
16
|
+
message_id: number;
|
|
17
|
+
from?: TgUser | undefined;
|
|
18
|
+
chat: TgChat;
|
|
19
|
+
date: number;
|
|
20
|
+
text?: string | undefined;
|
|
21
|
+
}
|
|
22
|
+
interface TgResponse<T> {
|
|
23
|
+
ok: boolean;
|
|
24
|
+
result?: T | undefined;
|
|
25
|
+
description?: string | undefined;
|
|
26
|
+
error_code?: number | undefined;
|
|
27
|
+
}
|
|
28
|
+
export interface TelegramIncomingMessage {
|
|
29
|
+
messageId: number;
|
|
30
|
+
chatId: number;
|
|
31
|
+
chatType: string;
|
|
32
|
+
userId?: number | undefined;
|
|
33
|
+
userName?: string | undefined;
|
|
34
|
+
text: string;
|
|
35
|
+
timestamp: number;
|
|
36
|
+
}
|
|
37
|
+
export interface TelegramBotOptions {
|
|
38
|
+
token: string;
|
|
39
|
+
pollIntervalSec: number;
|
|
40
|
+
allowedUsers: Set<string>;
|
|
41
|
+
allowedChats: Set<string>;
|
|
42
|
+
/** Max messages to buffer for the agent to read. Default: 50. */
|
|
43
|
+
bufferSize: number;
|
|
44
|
+
log: Logger;
|
|
45
|
+
/** Called for each incoming message that passes allowlist checks. */
|
|
46
|
+
onMessage(msg: TelegramIncomingMessage): void;
|
|
47
|
+
/**
|
|
48
|
+
* Optional path to a file that stores the polling offset. When provided,
|
|
49
|
+
* the offset is persisted on every successful poll and restored on startup,
|
|
50
|
+
* preventing message replay after crashes or restarts.
|
|
51
|
+
*/
|
|
52
|
+
offsetStoragePath?: string | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Optional cross-process single-poller lock. Telegram allows one
|
|
55
|
+
* `getUpdates` consumer per token; when another wstack instance holds the
|
|
56
|
+
* lock, this bot stands by (no polling) and takes over once the holder
|
|
57
|
+
* stops or its heartbeat goes stale.
|
|
58
|
+
*/
|
|
59
|
+
lock?: PollLock | undefined;
|
|
60
|
+
/** How often a standby instance retries acquiring the lock. Default: 15s. */
|
|
61
|
+
standbyRetryMs?: number | undefined;
|
|
62
|
+
}
|
|
63
|
+
export declare class TelegramBot {
|
|
64
|
+
private readonly baseUrl;
|
|
65
|
+
/** Base URL with token redacted, safe to use in log calls. */
|
|
66
|
+
private readonly safeBaseUrl;
|
|
67
|
+
private readonly pollIntervalMs;
|
|
68
|
+
private readonly allowedUsers;
|
|
69
|
+
private readonly allowedChats;
|
|
70
|
+
private readonly log;
|
|
71
|
+
private readonly onMessage;
|
|
72
|
+
private readonly controller;
|
|
73
|
+
private pollTimer;
|
|
74
|
+
private pollActive;
|
|
75
|
+
private offset;
|
|
76
|
+
/**
|
|
77
|
+
* Consecutive HTTP 409 ("another getUpdates in flight") responses. Two
|
|
78
|
+
* wstack instances polling the same bot token used to fight at full poll
|
|
79
|
+
* speed forever, erroring on every cycle. After CONFLICT_BACKOFF_AFTER
|
|
80
|
+
* consecutive conflicts this instance backs off to a slow poll and warns
|
|
81
|
+
* once; any successful poll resets to the normal cadence.
|
|
82
|
+
*/
|
|
83
|
+
private conflictStreak;
|
|
84
|
+
private static readonly CONFLICT_BACKOFF_AFTER;
|
|
85
|
+
private static readonly CONFLICT_POLL_MS;
|
|
86
|
+
private _startedAt;
|
|
87
|
+
/** If set, the offset is persisted here after each successful poll. */
|
|
88
|
+
private readonly offsetStoragePath?;
|
|
89
|
+
/** Single-poller election across wstack instances sharing this token. */
|
|
90
|
+
private readonly lock?;
|
|
91
|
+
private readonly standbyRetryMs;
|
|
92
|
+
private standbyTimer;
|
|
93
|
+
private standbyAnnounced;
|
|
94
|
+
private readonly bufferMax;
|
|
95
|
+
private readonly buffer;
|
|
96
|
+
private readonly callbackWaiters;
|
|
97
|
+
constructor(opts: TelegramBotOptions);
|
|
98
|
+
/** Start polling for updates. Idempotent. */
|
|
99
|
+
start(): void;
|
|
100
|
+
/** Stop polling and cancel all in-flight requests. */
|
|
101
|
+
stop(): void;
|
|
102
|
+
/** True when the bot is started but waiting for the poll lock. */
|
|
103
|
+
get standby(): boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Acquire the poll lock (when configured) and start the poll loop, or
|
|
106
|
+
* stand by and retry until the current holder releases it.
|
|
107
|
+
*/
|
|
108
|
+
private acquireAndPoll;
|
|
109
|
+
/** The lock was stolen while we held it — pause polling and stand by. */
|
|
110
|
+
private handleLockLost;
|
|
111
|
+
get startedAt(): number | null;
|
|
112
|
+
get running(): boolean;
|
|
113
|
+
/** Return buffered messages, newest first. Optionally filter by chat. */
|
|
114
|
+
getMessages(opts?: {
|
|
115
|
+
chatId?: string | number | undefined;
|
|
116
|
+
limit?: number | undefined;
|
|
117
|
+
}): TelegramIncomingMessage[];
|
|
118
|
+
/** Drop messages older than the given message ID from the buffer. */
|
|
119
|
+
acknowledge(lastMessageId: number): number;
|
|
120
|
+
get bufferCount(): number;
|
|
121
|
+
sendMessage(chatId: string | number, text: string): Promise<TgResponse<TgMessage>>;
|
|
122
|
+
/**
|
|
123
|
+
* Send a message that has up to one row of inline buttons (Telegram's
|
|
124
|
+
* `inline_keyboard`). Used by `telegram_approve` to present a
|
|
125
|
+
* yes/no prompt. The keyboard payload is opaque to the bot — callers
|
|
126
|
+
* pass already-encoded `callback_data` strings (≤ 64 bytes each).
|
|
127
|
+
*/
|
|
128
|
+
sendMessageWithKeyboard(chatId: string | number, text: string, buttons: Array<{
|
|
129
|
+
text: string;
|
|
130
|
+
callback_data: string;
|
|
131
|
+
}>): Promise<TgResponse<TgMessage>>;
|
|
132
|
+
health(): Promise<{
|
|
133
|
+
ok: boolean;
|
|
134
|
+
username?: string | undefined;
|
|
135
|
+
error?: string | undefined;
|
|
136
|
+
}>;
|
|
137
|
+
private schedulePoll;
|
|
138
|
+
private poll;
|
|
139
|
+
private processMessage;
|
|
140
|
+
/**
|
|
141
|
+
* Handle an inbound `callback_query` update: route it to a registered
|
|
142
|
+
* waiter (if any), and acknowledge it via `answerCallbackQuery` so the
|
|
143
|
+
* client stops spinning. Telegram requires the answer within 10 s.
|
|
144
|
+
*/
|
|
145
|
+
/**
|
|
146
|
+
* Resolve any pending waiter for `key` with a `{ approved: false, fromUser }`
|
|
147
|
+
* value, regardless of why the callback was rejected (allowlist, shutdown,
|
|
148
|
+
* etc.). Returns true if a waiter was found and resolved, false otherwise.
|
|
149
|
+
* This helper centralizes the race-safe `delete → resolve` pattern in one
|
|
150
|
+
* place so the deny and shutdown paths don't drift out of sync.
|
|
151
|
+
*/
|
|
152
|
+
private rejectWaiter;
|
|
153
|
+
private dispatchCallback;
|
|
154
|
+
/**
|
|
155
|
+
* POST /answerCallbackQuery for a callback. Best-effort: failures are
|
|
156
|
+
* logged at debug and swallowed — the caller's resolve() must not depend
|
|
157
|
+
* on the ack reaching Telegram (the user may get a "loading" spinner if
|
|
158
|
+
* it fails, but the agent's approval flow continues normally).
|
|
159
|
+
*/
|
|
160
|
+
private answerCallback;
|
|
161
|
+
/**
|
|
162
|
+
* Register a waiter for a callback_query whose `data` field equals `key`.
|
|
163
|
+
* Resolves with `{ approved, fromUser }` when a matching press arrives, or
|
|
164
|
+
* with `{ approved: false, fromUser: 'timeout' }` after `timeoutMs`.
|
|
165
|
+
*
|
|
166
|
+
* Callers are responsible for not registering the same key twice — a
|
|
167
|
+
* second `awaitCallback` for an in-flight key is undefined.
|
|
168
|
+
*/
|
|
169
|
+
awaitCallback(key: string, timeoutMs: number): Promise<{
|
|
170
|
+
approved: boolean;
|
|
171
|
+
fromUser: string;
|
|
172
|
+
}>;
|
|
173
|
+
private loadOffset;
|
|
174
|
+
private saveOffset;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Truncate text to fit Telegram's 4096-char message limit.
|
|
178
|
+
* Preserves semantic boundaries in this priority order:
|
|
179
|
+
* 1. Paragraph break (double newline)
|
|
180
|
+
* 2. Sentence break (. ! ? followed by space/newline)
|
|
181
|
+
* 3. Word break (space)
|
|
182
|
+
* 4. Hard cut with ellipsis
|
|
183
|
+
*
|
|
184
|
+
* When a clean boundary is found, appends "…" to signal intentional truncation.
|
|
185
|
+
*/
|
|
186
|
+
export declare function truncateForTelegram(text: string, maxLen?: number): string;
|
|
187
|
+
/**
|
|
188
|
+
* Escape HTML special chars for Telegram's HTML parse mode.
|
|
189
|
+
*/
|
|
190
|
+
export declare function escapeHtml(text: string): string;
|
|
191
|
+
export {};
|
|
192
|
+
//# sourceMappingURL=bot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../src/bot.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { PluginAPI } from '@wrongstack/core';
|
|
2
|
+
export declare const PLUGIN_NAME = "telegram";
|
|
3
|
+
export interface TelegramPluginConfig {
|
|
4
|
+
/** Telegram Bot API token (from @BotFather). */
|
|
5
|
+
botToken: string;
|
|
6
|
+
/**
|
|
7
|
+
* Default chat ID for outgoing notifications.
|
|
8
|
+
* The agent's `telegram_send` tool can override per-call.
|
|
9
|
+
*/
|
|
10
|
+
notifyChatId?: string | number | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* List of user/chat IDs allowed to interact with the bot.
|
|
13
|
+
* Empty = allow all. Recommended to set in production.
|
|
14
|
+
*/
|
|
15
|
+
allowedUsers?: Array<string | number> | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* List of group/chat IDs the bot is allowed to read from.
|
|
18
|
+
* Empty = allow all. Narrow this to prevent noise.
|
|
19
|
+
*/
|
|
20
|
+
allowedChats?: Array<string | number> | undefined;
|
|
21
|
+
/** Polling interval in seconds (default: 2). */
|
|
22
|
+
pollIntervalSec?: number | undefined;
|
|
23
|
+
/** Notify on Telegram when a session ends. */
|
|
24
|
+
notifyOnSessionEnd?: boolean | undefined;
|
|
25
|
+
/** Notify when a tool runs longer than this threshold (ms). Set 0 to disable. */
|
|
26
|
+
longToolThresholdMs?: number | undefined;
|
|
27
|
+
/** Notify (humanized) when a `delegate` subagent finishes. Default: true. */
|
|
28
|
+
notifyOnDelegate?: boolean | undefined;
|
|
29
|
+
/** Maximum message length for Telegram (Telegram caps at 4096). */
|
|
30
|
+
maxMessageLength?: number | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Path to a file that stores the Telegram polling offset. When set,
|
|
33
|
+
* the offset is persisted on every successful poll and restored on startup,
|
|
34
|
+
* preventing message replay after crashes or restarts.
|
|
35
|
+
* The directory must already exist and be writable.
|
|
36
|
+
*/
|
|
37
|
+
offsetStoragePath?: string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Elect a single poller per bot token across wstack instances (default:
|
|
40
|
+
* true). Telegram allows one `getUpdates` consumer per token; without this,
|
|
41
|
+
* two instances sharing a token fight and get HTTP 409 on every poll.
|
|
42
|
+
* Extra instances stand by and take over when the active poller stops.
|
|
43
|
+
* Set false only if this is guaranteed to be the sole consumer.
|
|
44
|
+
*/
|
|
45
|
+
singleInstanceLock?: boolean | undefined;
|
|
46
|
+
}
|
|
47
|
+
export declare const DEFAULT_CONFIG: Required<Omit<TelegramPluginConfig, 'botToken' | 'notifyChatId' | 'offsetStoragePath'>>;
|
|
48
|
+
export declare const telegramConfigSchema: {
|
|
49
|
+
type: string;
|
|
50
|
+
properties: {
|
|
51
|
+
botToken: {
|
|
52
|
+
type: string;
|
|
53
|
+
description: string;
|
|
54
|
+
};
|
|
55
|
+
notifyChatId: {
|
|
56
|
+
oneOf: {
|
|
57
|
+
type: string;
|
|
58
|
+
}[];
|
|
59
|
+
description: string;
|
|
60
|
+
};
|
|
61
|
+
allowedUsers: {
|
|
62
|
+
type: string;
|
|
63
|
+
items: {
|
|
64
|
+
oneOf: {
|
|
65
|
+
type: string;
|
|
66
|
+
}[];
|
|
67
|
+
};
|
|
68
|
+
description: string;
|
|
69
|
+
};
|
|
70
|
+
allowedChats: {
|
|
71
|
+
type: string;
|
|
72
|
+
items: {
|
|
73
|
+
oneOf: {
|
|
74
|
+
type: string;
|
|
75
|
+
}[];
|
|
76
|
+
};
|
|
77
|
+
description: string;
|
|
78
|
+
};
|
|
79
|
+
pollIntervalSec: {
|
|
80
|
+
type: string;
|
|
81
|
+
minimum: number;
|
|
82
|
+
maximum: number;
|
|
83
|
+
description: string;
|
|
84
|
+
};
|
|
85
|
+
notifyOnSessionEnd: {
|
|
86
|
+
type: string;
|
|
87
|
+
};
|
|
88
|
+
longToolThresholdMs: {
|
|
89
|
+
type: string;
|
|
90
|
+
minimum: number;
|
|
91
|
+
};
|
|
92
|
+
notifyOnDelegate: {
|
|
93
|
+
type: string;
|
|
94
|
+
};
|
|
95
|
+
maxMessageLength: {
|
|
96
|
+
type: string;
|
|
97
|
+
minimum: number;
|
|
98
|
+
maximum: number;
|
|
99
|
+
};
|
|
100
|
+
singleInstanceLock: {
|
|
101
|
+
type: string;
|
|
102
|
+
description: string;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
required: string[];
|
|
106
|
+
};
|
|
107
|
+
export declare function readTelegramConfig(api: Pick<PluginAPI, 'config'>): Required<Omit<TelegramPluginConfig, 'notifyChatId' | 'offsetStoragePath'>> & Pick<TelegramPluginConfig, 'notifyChatId' | 'offsetStoragePath'>;
|
|
108
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD,eAAO,MAAM,WAAW,aAAa,CAAC;AAEtC,MAAM,WAAW,oBAAoB;IACnC,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAC3C;;;OAGG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,SAAS,CAAC;IAClD;;;OAGG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,SAAS,CAAC;IAClD,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,8CAA8C;IAC9C,kBAAkB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACzC,iFAAiF;IACjF,mBAAmB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC1C;AAED,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,IAAI,CAAC,oBAAoB,EAAE,UAAU,GAAG,cAAc,GAAG,mBAAmB,CAAC,CASlH,CAAC;AAEF,eAAO,MAAM,oBAAoB;;;;YAGjB,IAAI;YAAY,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;YAgBrC,IAAI;YACJ,OAAO;YACP,OAAO;YACP,WAAW;;;YAES,IAAI;;;YACH,IAAI;YAAa,OAAO;;;YAC3B,IAAI;;;YACJ,IAAI;YAAa,OAAO;YAAO,OAAO;;;YAExD,IAAI;YACJ,WAAW;;;;CAIhB,CAAC;AAEF,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,GAC7B,QAAQ,CAAC,IAAI,CAAC,oBAAoB,EAAE,cAAc,GAAG,mBAAmB,CAAC,CAAC,GAC3E,IAAI,CAAC,oBAAoB,EAAE,cAAc,GAAG,mBAAmB,CAAC,CAgBjE"}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** Subset of the core `delegate.completed` event payload we render. */
|
|
2
|
+
export interface DelegateCompletedLike {
|
|
3
|
+
target: string;
|
|
4
|
+
task: string;
|
|
5
|
+
ok: boolean;
|
|
6
|
+
status?: string | undefined;
|
|
7
|
+
summary: string;
|
|
8
|
+
durationMs: number;
|
|
9
|
+
iterations: number;
|
|
10
|
+
toolCalls: number;
|
|
11
|
+
costUsd?: number | undefined;
|
|
12
|
+
subagentId?: string | undefined;
|
|
13
|
+
}
|
|
14
|
+
/** Subset of core `tool.executed` event payload. */
|
|
15
|
+
export interface ToolExecutedLike {
|
|
16
|
+
name: string;
|
|
17
|
+
ok: boolean;
|
|
18
|
+
durationMs: number;
|
|
19
|
+
/** Raw tool output — only the first 300 chars are rendered. */
|
|
20
|
+
output?: string | undefined;
|
|
21
|
+
}
|
|
22
|
+
/** Subset of core `session.ended` event payload (from Usage). */
|
|
23
|
+
export interface SessionEndedLike {
|
|
24
|
+
id: string;
|
|
25
|
+
inputTokens: number;
|
|
26
|
+
outputTokens: number;
|
|
27
|
+
cacheRead?: number | undefined;
|
|
28
|
+
cacheWrite?: number | undefined;
|
|
29
|
+
}
|
|
30
|
+
/** Compact human duration: `42s`, `3m`, `1.5h`. */
|
|
31
|
+
export declare function fmtDuration(ms: number): string;
|
|
32
|
+
/**
|
|
33
|
+
* Format a numeric count of tokens for human readability.
|
|
34
|
+
* Uses comma-separated thousands: 1,234, 56,789.
|
|
35
|
+
*/
|
|
36
|
+
export declare function fmtTokens(n: number): string;
|
|
37
|
+
/**
|
|
38
|
+
* Try to render a tool's output as a short human-readable snippet.
|
|
39
|
+
* Strips JSON braces/quoting, redacts secrets, limits to ~300 chars,
|
|
40
|
+
* preserves first/last lines.
|
|
41
|
+
*/
|
|
42
|
+
export declare function fmtToolOutput(raw: string | undefined): string;
|
|
43
|
+
/**
|
|
44
|
+
* Render a finished delegation as a readable Telegram message.
|
|
45
|
+
*
|
|
46
|
+
* Example:
|
|
47
|
+
* ✅ Delegate → bug-hunter · success
|
|
48
|
+
* Found 3 null-deref risks in auth.ts and patched the worst one…
|
|
49
|
+
* ⏱ 3m · 4 iter · 37 tools · 💲0.0820
|
|
50
|
+
*/
|
|
51
|
+
export declare function formatDelegateCompleted(e: DelegateCompletedLike): string;
|
|
52
|
+
/**
|
|
53
|
+
* Render a long-running tool execution notification.
|
|
54
|
+
*
|
|
55
|
+
* Example:
|
|
56
|
+
* ✅ bash completed in 45.2s
|
|
57
|
+
* pnpm test — 12 suites, 47 tests passed
|
|
58
|
+
* …
|
|
59
|
+
*/
|
|
60
|
+
export declare function formatToolExecuted(e: ToolExecutedLike): string;
|
|
61
|
+
/**
|
|
62
|
+
* Render a session-end notification.
|
|
63
|
+
*
|
|
64
|
+
* Example:
|
|
65
|
+
* 🏁 Session sess_abcd ended
|
|
66
|
+
* ⬇ 8,234 in · ⬆ 3,456 out · 11,690 total
|
|
67
|
+
* Cache: 1,200 read · 800 written
|
|
68
|
+
*/
|
|
69
|
+
export declare function formatSessionEnded(e: SessionEndedLike): string;
|
|
70
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAuBA,uEAAuE;AACvE,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAED,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,OAAO,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,iEAAiE;AACjE,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAMD,mDAAmD;AACnD,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAI9C;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAoB7D;AAMD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAqBxE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAS9D;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAkB9D"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,60 +1,6 @@
|
|
|
1
|
-
import { Plugin } from '@wrongstack/core';
|
|
2
|
-
|
|
3
|
-
interface TelegramIncomingMessage {
|
|
4
|
-
messageId: number;
|
|
5
|
-
chatId: number;
|
|
6
|
-
chatType: string;
|
|
7
|
-
userId?: number | undefined;
|
|
8
|
-
userName?: string | undefined;
|
|
9
|
-
text: string;
|
|
10
|
-
timestamp: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface TelegramPluginConfig {
|
|
14
|
-
/** Telegram Bot API token (from @BotFather). */
|
|
15
|
-
botToken: string;
|
|
16
|
-
/**
|
|
17
|
-
* Default chat ID for outgoing notifications.
|
|
18
|
-
* The agent's `telegram_send` tool can override per-call.
|
|
19
|
-
*/
|
|
20
|
-
notifyChatId?: string | number | undefined;
|
|
21
|
-
/**
|
|
22
|
-
* List of user/chat IDs allowed to interact with the bot.
|
|
23
|
-
* Empty = allow all. Recommended to set in production.
|
|
24
|
-
*/
|
|
25
|
-
allowedUsers?: Array<string | number> | undefined;
|
|
26
|
-
/**
|
|
27
|
-
* List of group/chat IDs the bot is allowed to read from.
|
|
28
|
-
* Empty = allow all. Narrow this to prevent noise.
|
|
29
|
-
*/
|
|
30
|
-
allowedChats?: Array<string | number> | undefined;
|
|
31
|
-
/** Polling interval in seconds (default: 2). */
|
|
32
|
-
pollIntervalSec?: number | undefined;
|
|
33
|
-
/** Notify on Telegram when a session ends. */
|
|
34
|
-
notifyOnSessionEnd?: boolean | undefined;
|
|
35
|
-
/** Notify when a tool runs longer than this threshold (ms). Set 0 to disable. */
|
|
36
|
-
longToolThresholdMs?: number | undefined;
|
|
37
|
-
/** Notify (humanized) when a `delegate` subagent finishes. Default: true. */
|
|
38
|
-
notifyOnDelegate?: boolean | undefined;
|
|
39
|
-
/** Maximum message length for Telegram (Telegram caps at 4096). */
|
|
40
|
-
maxMessageLength?: number | undefined;
|
|
41
|
-
/**
|
|
42
|
-
* Path to a file that stores the Telegram polling offset. When set,
|
|
43
|
-
* the offset is persisted on every successful poll and restored on startup,
|
|
44
|
-
* preventing message replay after crashes or restarts.
|
|
45
|
-
* The directory must already exist and be writable.
|
|
46
|
-
*/
|
|
47
|
-
offsetStoragePath?: string | undefined;
|
|
48
|
-
/**
|
|
49
|
-
* Elect a single poller per bot token across wstack instances (default:
|
|
50
|
-
* true). Telegram allows one `getUpdates` consumer per token; without this,
|
|
51
|
-
* two instances sharing a token fight and get HTTP 409 on every poll.
|
|
52
|
-
* Extra instances stand by and take over when the active poller stops.
|
|
53
|
-
* Set false only if this is guaranteed to be the sole consumer.
|
|
54
|
-
*/
|
|
55
|
-
singleInstanceLock?: boolean | undefined;
|
|
56
|
-
}
|
|
57
|
-
|
|
1
|
+
import type { Plugin } from '@wrongstack/core';
|
|
58
2
|
declare const plugin: Plugin;
|
|
59
|
-
|
|
60
|
-
export {
|
|
3
|
+
export default plugin;
|
|
4
|
+
export type { TelegramIncomingMessage } from './bot.js';
|
|
5
|
+
export type { TelegramPluginConfig } from './config.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA2DvD,QAAA,MAAM,MAAM,EAAE,MAkPb,CAAC;eAEa,MAAM;AAGrB,YAAY,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { expectDefined } from '@wrongstack/core';
|
|
2
|
-
import { wstackGlobalRoot, sleep } from '@wrongstack/core/utils';
|
|
3
|
-
import { randomUUID, createHash } from 'crypto';
|
|
4
|
-
import { mkdirSync, unlinkSync, writeFileSync, renameSync, readFileSync } from 'fs';
|
|
5
|
-
import { dirname, join } from 'path';
|
|
6
|
-
|
|
7
1
|
// src/index.ts
|
|
2
|
+
import { expectDefined as expectDefined2 } from "@wrongstack/core";
|
|
3
|
+
|
|
4
|
+
// src/bot.ts
|
|
5
|
+
import { sleep } from "@wrongstack/core/utils";
|
|
8
6
|
function redactToken(url, token) {
|
|
9
7
|
return url.replace(token, "[REDACTED]");
|
|
10
8
|
}
|
|
@@ -436,7 +434,7 @@ var TelegramBot = class _TelegramBot {
|
|
|
436
434
|
async loadOffset() {
|
|
437
435
|
if (!this.offsetStoragePath) return;
|
|
438
436
|
try {
|
|
439
|
-
const { readFileSync: readFileSync2 } = await import(
|
|
437
|
+
const { readFileSync: readFileSync2 } = await import("node:fs");
|
|
440
438
|
const raw = readFileSync2(this.offsetStoragePath, "utf8").trim();
|
|
441
439
|
const n = Number.parseInt(raw, 10);
|
|
442
440
|
if (Number.isFinite(n) && n >= 0) {
|
|
@@ -449,7 +447,7 @@ var TelegramBot = class _TelegramBot {
|
|
|
449
447
|
async saveOffset() {
|
|
450
448
|
if (!this.offsetStoragePath) return;
|
|
451
449
|
try {
|
|
452
|
-
const { writeFileSync: writeFileSync2 } = await import(
|
|
450
|
+
const { writeFileSync: writeFileSync2 } = await import("node:fs");
|
|
453
451
|
writeFileSync2(this.offsetStoragePath, String(this.offset), "utf8");
|
|
454
452
|
} catch (err) {
|
|
455
453
|
this.log.debug(`Failed to persist Telegram offset: ${err}`);
|
|
@@ -665,6 +663,12 @@ function formatSessionEnded(e) {
|
|
|
665
663
|
}
|
|
666
664
|
return lines.join("\n");
|
|
667
665
|
}
|
|
666
|
+
|
|
667
|
+
// src/poll-lock.ts
|
|
668
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
669
|
+
import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
670
|
+
import { dirname, join } from "node:path";
|
|
671
|
+
import { wstackGlobalRoot } from "@wrongstack/core/utils";
|
|
668
672
|
function lockPathForToken(token, globalRoot = wstackGlobalRoot()) {
|
|
669
673
|
const hash = createHash("sha256").update(token).digest("hex").slice(0, 12);
|
|
670
674
|
return join(globalRoot, "telegram", `poll-${hash}.lock`);
|
|
@@ -783,6 +787,9 @@ var PollLock = class {
|
|
|
783
787
|
}
|
|
784
788
|
}
|
|
785
789
|
};
|
|
790
|
+
|
|
791
|
+
// src/slash-commands/index.ts
|
|
792
|
+
import { expectDefined } from "@wrongstack/core";
|
|
786
793
|
function tgHealthCommand(bot, cfg) {
|
|
787
794
|
return {
|
|
788
795
|
name: "telegram-health",
|
|
@@ -980,6 +987,9 @@ function makeTelegramSendTool(opts) {
|
|
|
980
987
|
}
|
|
981
988
|
};
|
|
982
989
|
}
|
|
990
|
+
|
|
991
|
+
// src/tools/telegram-approve.ts
|
|
992
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
983
993
|
function makeTelegramApproveTool(opts) {
|
|
984
994
|
return {
|
|
985
995
|
name: "telegram_approve",
|
|
@@ -1023,7 +1033,7 @@ function makeTelegramApproveTool(opts) {
|
|
|
1023
1033
|
);
|
|
1024
1034
|
}
|
|
1025
1035
|
const timeoutMs = Math.min(Math.max(input.timeout_ms ?? 6e4, 1e3), 6e5);
|
|
1026
|
-
const token =
|
|
1036
|
+
const token = randomUUID2().slice(0, 16);
|
|
1027
1037
|
const yesKey = `approve:${token}:yes`;
|
|
1028
1038
|
const noKey = `approve:${token}:no`;
|
|
1029
1039
|
const heading = `\u26A0\uFE0F ${input.prompt}`;
|
|
@@ -1170,7 +1180,7 @@ var plugin = {
|
|
|
1170
1180
|
formatSessionEnded(payload),
|
|
1171
1181
|
runtimeCfg.maxMessageLength
|
|
1172
1182
|
);
|
|
1173
|
-
void bot.sendMessage(
|
|
1183
|
+
void bot.sendMessage(expectDefined2(runtimeCfg.notifyChatId), msg).catch((err) => {
|
|
1174
1184
|
log.debug(`Failed to send session end notification: ${err.message}`);
|
|
1175
1185
|
});
|
|
1176
1186
|
})
|
|
@@ -1188,7 +1198,7 @@ var plugin = {
|
|
|
1188
1198
|
formatToolExecuted(payload),
|
|
1189
1199
|
runtimeCfg.maxMessageLength
|
|
1190
1200
|
);
|
|
1191
|
-
void bot.sendMessage(
|
|
1201
|
+
void bot.sendMessage(expectDefined2(runtimeCfg.notifyChatId), msg).catch((err) => {
|
|
1192
1202
|
log.debug(`Failed to send tool notification: ${err.message}`);
|
|
1193
1203
|
});
|
|
1194
1204
|
})
|
|
@@ -1200,7 +1210,7 @@ var plugin = {
|
|
|
1200
1210
|
formatDelegateCompleted(event),
|
|
1201
1211
|
runtimeCfg.maxMessageLength
|
|
1202
1212
|
);
|
|
1203
|
-
void bot.sendMessage(
|
|
1213
|
+
void bot.sendMessage(expectDefined2(runtimeCfg.notifyChatId), msg).catch((err) => {
|
|
1204
1214
|
log.debug(`Failed to send delegate notification: ${err.message}`);
|
|
1205
1215
|
});
|
|
1206
1216
|
})
|
|
@@ -1256,8 +1266,8 @@ var plugin = {
|
|
|
1256
1266
|
return h;
|
|
1257
1267
|
}
|
|
1258
1268
|
};
|
|
1259
|
-
var
|
|
1260
|
-
|
|
1261
|
-
|
|
1269
|
+
var src_default = plugin;
|
|
1270
|
+
export {
|
|
1271
|
+
src_default as default
|
|
1272
|
+
};
|
|
1262
1273
|
//# sourceMappingURL=index.js.map
|
|
1263
|
-
//# sourceMappingURL=index.js.map
|