pilotlynx 0.1.2 → 0.2.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/README.md +40 -40
- package/dist/agents/improve.agent.d.ts +42 -1
- package/dist/agents/improve.agent.js +102 -7
- package/dist/agents/improve.agent.js.map +1 -1
- package/dist/agents/run.agent.d.ts +0 -1
- package/dist/agents/run.agent.js +10 -7
- package/dist/agents/run.agent.js.map +1 -1
- package/dist/cli.js +8 -3
- package/dist/cli.js.map +1 -1
- package/dist/commands/audit.d.ts +2 -0
- package/dist/commands/audit.js +51 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/eval.d.ts +2 -0
- package/dist/commands/eval.js +47 -0
- package/dist/commands/eval.js.map +1 -0
- package/dist/commands/improve.js +83 -5
- package/dist/commands/improve.js.map +1 -1
- package/dist/commands/init.js +17 -27
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/insights.js +63 -0
- package/dist/commands/insights.js.map +1 -1
- package/dist/commands/logs.d.ts +1 -0
- package/dist/commands/logs.js +23 -1
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/relay.js +250 -124
- package/dist/commands/relay.js.map +1 -1
- package/dist/commands/run.js +21 -2
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/schedule.js +7 -1
- package/dist/commands/schedule.js.map +1 -1
- package/dist/commands/status.js +9 -2
- package/dist/commands/status.js.map +1 -1
- package/dist/lib/agent-runner.d.ts +5 -1
- package/dist/lib/agent-runner.js +41 -1
- package/dist/lib/agent-runner.js.map +1 -1
- package/dist/lib/audit.d.ts +7 -0
- package/dist/lib/audit.js +63 -0
- package/dist/lib/audit.js.map +1 -0
- package/dist/lib/callbacks.d.ts +12 -1
- package/dist/lib/callbacks.js +147 -19
- package/dist/lib/callbacks.js.map +1 -1
- package/dist/lib/command-ops/doctor-ops.js +41 -6
- package/dist/lib/command-ops/doctor-ops.js.map +1 -1
- package/dist/lib/command-ops/eval-ops.d.ts +7 -0
- package/dist/lib/command-ops/eval-ops.js +111 -0
- package/dist/lib/command-ops/eval-ops.js.map +1 -0
- package/dist/lib/command-ops/improve-ops.d.ts +14 -1
- package/dist/lib/command-ops/improve-ops.js +369 -17
- package/dist/lib/command-ops/improve-ops.js.map +1 -1
- package/dist/lib/command-ops/run-ops.d.ts +8 -1
- package/dist/lib/command-ops/run-ops.js +25 -4
- package/dist/lib/command-ops/run-ops.js.map +1 -1
- package/dist/lib/command-ops/secrets-migration-ops.js +1 -1
- package/dist/lib/command-ops/secrets-migration-ops.js.map +1 -1
- package/dist/lib/command-ops/status-ops.d.ts +1 -0
- package/dist/lib/command-ops/status-ops.js +19 -7
- package/dist/lib/command-ops/status-ops.js.map +1 -1
- package/dist/lib/config.js +3 -3
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/cron.d.ts +1 -1
- package/dist/lib/cron.js +2 -2
- package/dist/lib/cron.js.map +1 -1
- package/dist/lib/global-config.js +1 -1
- package/dist/lib/global-config.js.map +1 -1
- package/dist/lib/observation.d.ts +60 -2
- package/dist/lib/observation.js +261 -13
- package/dist/lib/observation.js.map +1 -1
- package/dist/lib/registry.d.ts +0 -1
- package/dist/lib/registry.js +1 -1
- package/dist/lib/registry.js.map +1 -1
- package/dist/lib/relay/admin.d.ts +29 -0
- package/dist/lib/relay/admin.js +176 -0
- package/dist/lib/relay/admin.js.map +1 -0
- package/dist/lib/relay/bindings.d.ts +8 -0
- package/dist/lib/relay/bindings.js +50 -0
- package/dist/lib/relay/bindings.js.map +1 -0
- package/dist/lib/relay/config.d.ts +3 -3
- package/dist/lib/relay/config.js +18 -10
- package/dist/lib/relay/config.js.map +1 -1
- package/dist/lib/relay/context.d.ts +31 -0
- package/dist/lib/relay/context.js +118 -0
- package/dist/lib/relay/context.js.map +1 -0
- package/dist/lib/relay/db.d.ts +38 -0
- package/dist/lib/relay/db.js +252 -0
- package/dist/lib/relay/db.js.map +1 -0
- package/dist/lib/relay/executor.d.ts +2 -0
- package/dist/lib/relay/executor.js +108 -0
- package/dist/lib/relay/executor.js.map +1 -0
- package/dist/lib/relay/feedback.d.ts +29 -0
- package/dist/lib/relay/feedback.js +142 -0
- package/dist/lib/relay/feedback.js.map +1 -0
- package/dist/lib/relay/notifier.d.ts +30 -0
- package/dist/lib/relay/notifier.js +78 -0
- package/dist/lib/relay/notifier.js.map +1 -0
- package/dist/lib/relay/notify.d.ts +3 -4
- package/dist/lib/relay/notify.js +43 -159
- package/dist/lib/relay/notify.js.map +1 -1
- package/dist/lib/relay/platform.d.ts +52 -0
- package/dist/lib/relay/platform.js +5 -0
- package/dist/lib/relay/platform.js.map +1 -0
- package/dist/lib/relay/platforms/slack.d.ts +37 -0
- package/dist/lib/relay/platforms/slack.js +240 -0
- package/dist/lib/relay/platforms/slack.js.map +1 -0
- package/dist/lib/relay/platforms/telegram.d.ts +29 -0
- package/dist/lib/relay/platforms/telegram.js +193 -0
- package/dist/lib/relay/platforms/telegram.js.map +1 -0
- package/dist/lib/relay/poster.d.ts +24 -0
- package/dist/lib/relay/poster.js +136 -0
- package/dist/lib/relay/poster.js.map +1 -0
- package/dist/lib/relay/queue.d.ts +18 -0
- package/dist/lib/relay/queue.js +85 -0
- package/dist/lib/relay/queue.js.map +1 -0
- package/dist/lib/relay/router.d.ts +25 -2
- package/dist/lib/relay/router.js +259 -168
- package/dist/lib/relay/router.js.map +1 -1
- package/dist/lib/relay/service.d.ts +31 -7
- package/dist/lib/relay/service.js +281 -200
- package/dist/lib/relay/service.js.map +1 -1
- package/dist/lib/relay/types.d.ts +189 -34
- package/dist/lib/relay/types.js +68 -28
- package/dist/lib/relay/types.js.map +1 -1
- package/dist/lib/sandbox.d.ts +9 -1
- package/dist/lib/sandbox.js +17 -2
- package/dist/lib/sandbox.js.map +1 -1
- package/dist/lib/schedule.d.ts +4 -5
- package/dist/lib/schedule.js +7 -8
- package/dist/lib/schedule.js.map +1 -1
- package/dist/lib/secrets.js +11 -1
- package/dist/lib/secrets.js.map +1 -1
- package/dist/lib/types.d.ts +80 -0
- package/dist/lib/types.js +9 -1
- package/dist/lib/types.js.map +1 -1
- package/package.json +18 -18
- package/prompts/improve.yaml +114 -6
- package/prompts/relay.yaml +29 -0
- package/prompts/run.yaml +36 -2
- package/template/CLAUDE.md +34 -5
- package/template/RUNBOOK.md +5 -5
- package/template/evals/sample.json +16 -0
- package/template/memory/MEMORY.md +6 -0
- package/template/memory/procedures/.gitkeep +0 -0
- package/template/schedule.yaml +1 -1
- package/template/workflows/daily_feedback.ts +78 -2
- package/template/workflows/project_review.ts +51 -2
- package/prompts/relay-chat.yaml +0 -24
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// ── Slack Platform Adapter ──
|
|
2
|
+
// Implements ChatPlatform via @slack/bolt (Socket Mode or HTTP).
|
|
3
|
+
export class SlackAdapter {
|
|
4
|
+
config;
|
|
5
|
+
name = 'slack';
|
|
6
|
+
capabilities = {
|
|
7
|
+
nativeStreaming: true,
|
|
8
|
+
maxStreamUpdateHz: 10,
|
|
9
|
+
supportsReactions: true,
|
|
10
|
+
supportsSlashCommands: true,
|
|
11
|
+
supportsThreads: true,
|
|
12
|
+
maxMessageLength: 4000,
|
|
13
|
+
};
|
|
14
|
+
onMessage = async () => { };
|
|
15
|
+
onReaction = async () => { };
|
|
16
|
+
onCommand = async () => '';
|
|
17
|
+
app; // Bolt App – dynamically imported
|
|
18
|
+
botUserId;
|
|
19
|
+
userCache = new Map(); // userId → displayName
|
|
20
|
+
reconnectBackoff = 1000;
|
|
21
|
+
lastEventTime = Date.now();
|
|
22
|
+
healthTimer;
|
|
23
|
+
processedRetries = new Set(); // dedup x-slack-retry-num
|
|
24
|
+
constructor(config) {
|
|
25
|
+
this.config = config;
|
|
26
|
+
}
|
|
27
|
+
async start() {
|
|
28
|
+
const { App } = await import('@slack/bolt');
|
|
29
|
+
this.app = new App({
|
|
30
|
+
token: this.config.botToken,
|
|
31
|
+
appToken: this.config.appToken,
|
|
32
|
+
signingSecret: this.config.signingSecret,
|
|
33
|
+
socketMode: this.config.mode === 'socket',
|
|
34
|
+
port: this.config.port,
|
|
35
|
+
});
|
|
36
|
+
// ── Event: app_mention ──
|
|
37
|
+
this.app.event('app_mention', async ({ event, context }) => {
|
|
38
|
+
if (this.shouldSkipRetry(context))
|
|
39
|
+
return;
|
|
40
|
+
this.lastEventTime = Date.now();
|
|
41
|
+
const msg = await this.slackEventToChatMessage(event);
|
|
42
|
+
if (!msg.isBot)
|
|
43
|
+
await this.onMessage(msg);
|
|
44
|
+
});
|
|
45
|
+
// ── Event: message ──
|
|
46
|
+
this.app.event('message', async ({ event, context }) => {
|
|
47
|
+
if (this.shouldSkipRetry(context))
|
|
48
|
+
return;
|
|
49
|
+
if (event.subtype && event.subtype !== 'file_share')
|
|
50
|
+
return;
|
|
51
|
+
this.lastEventTime = Date.now();
|
|
52
|
+
const msg = await this.slackEventToChatMessage(event);
|
|
53
|
+
if (!msg.isBot)
|
|
54
|
+
await this.onMessage(msg);
|
|
55
|
+
});
|
|
56
|
+
// ── Event: reaction_added ──
|
|
57
|
+
this.app.event('reaction_added', async ({ event, context }) => {
|
|
58
|
+
if (this.shouldSkipRetry(context))
|
|
59
|
+
return;
|
|
60
|
+
this.lastEventTime = Date.now();
|
|
61
|
+
await this.onReaction(event.item.channel, event.item.ts, event.user, event.reaction);
|
|
62
|
+
});
|
|
63
|
+
// ── Slash command: /pilotlynx-bind ──
|
|
64
|
+
this.app.command('/pilotlynx-bind', async ({ command, ack }) => {
|
|
65
|
+
await ack();
|
|
66
|
+
this.lastEventTime = Date.now();
|
|
67
|
+
const response = await this.onCommand(command.channel_id, command.user_id, 'bind', command.text);
|
|
68
|
+
return { text: response };
|
|
69
|
+
});
|
|
70
|
+
await this.app.start();
|
|
71
|
+
// Resolve bot user ID for self-filtering
|
|
72
|
+
const authResult = await this.app.client.auth.test();
|
|
73
|
+
this.botUserId = authResult.user_id;
|
|
74
|
+
// Health check timer – force reconnect if no events for 90s
|
|
75
|
+
this.healthTimer = setInterval(() => {
|
|
76
|
+
if (Date.now() - this.lastEventTime > 90_000) {
|
|
77
|
+
this.attemptReconnect();
|
|
78
|
+
}
|
|
79
|
+
}, 30_000);
|
|
80
|
+
}
|
|
81
|
+
async stop() {
|
|
82
|
+
if (this.healthTimer) {
|
|
83
|
+
clearInterval(this.healthTimer);
|
|
84
|
+
this.healthTimer = undefined;
|
|
85
|
+
}
|
|
86
|
+
if (this.app) {
|
|
87
|
+
await this.app.stop();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async sendMessage(channelId, text, threadId) {
|
|
91
|
+
const result = await this.app.client.chat.postMessage({
|
|
92
|
+
channel: channelId,
|
|
93
|
+
text,
|
|
94
|
+
thread_ts: threadId,
|
|
95
|
+
});
|
|
96
|
+
return result.ts;
|
|
97
|
+
}
|
|
98
|
+
async updateMessage(channelId, messageId, text) {
|
|
99
|
+
await this.app.client.chat.update({
|
|
100
|
+
channel: channelId,
|
|
101
|
+
ts: messageId,
|
|
102
|
+
text,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async startStream(channelId, threadId) {
|
|
106
|
+
const initialTs = await this.sendMessage(channelId, 'Working on it\u2026', threadId);
|
|
107
|
+
let accumulated = '';
|
|
108
|
+
let pending;
|
|
109
|
+
const debounceMs = 300;
|
|
110
|
+
const flush = async (text) => {
|
|
111
|
+
await this.updateMessage(channelId, initialTs, text);
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
append: async (text) => {
|
|
115
|
+
accumulated += text;
|
|
116
|
+
if (pending)
|
|
117
|
+
clearTimeout(pending);
|
|
118
|
+
pending = setTimeout(() => {
|
|
119
|
+
void flush(accumulated);
|
|
120
|
+
}, debounceMs);
|
|
121
|
+
},
|
|
122
|
+
stop: async (finalText) => {
|
|
123
|
+
if (pending)
|
|
124
|
+
clearTimeout(pending);
|
|
125
|
+
await flush(finalText ?? accumulated);
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
async uploadFile(channelId, content, filename, threadId) {
|
|
130
|
+
await this.app.client.files.uploadV2({
|
|
131
|
+
channel_id: channelId,
|
|
132
|
+
content,
|
|
133
|
+
filename,
|
|
134
|
+
thread_ts: threadId,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
async getThreadHistory(channelId, threadId, afterTs) {
|
|
138
|
+
const result = await this.app.client.conversations.replies({
|
|
139
|
+
channel: channelId,
|
|
140
|
+
ts: threadId,
|
|
141
|
+
oldest: afterTs,
|
|
142
|
+
});
|
|
143
|
+
const messages = [];
|
|
144
|
+
for (const msg of result.messages ?? []) {
|
|
145
|
+
messages.push(await this.slackMsgToChatMessage(channelId, msg));
|
|
146
|
+
}
|
|
147
|
+
return messages;
|
|
148
|
+
}
|
|
149
|
+
// ── Helpers ──
|
|
150
|
+
shouldSkipRetry(context) {
|
|
151
|
+
const retryNum = context?.retryNum;
|
|
152
|
+
if (retryNum == null)
|
|
153
|
+
return false;
|
|
154
|
+
const key = `${retryNum}`;
|
|
155
|
+
if (this.processedRetries.has(key))
|
|
156
|
+
return true;
|
|
157
|
+
this.processedRetries.add(key);
|
|
158
|
+
// Prevent memory leak – prune old entries periodically
|
|
159
|
+
if (this.processedRetries.size > 1000) {
|
|
160
|
+
const entries = Array.from(this.processedRetries);
|
|
161
|
+
this.processedRetries = new Set(entries.slice(-500));
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
async resolveUserName(userId) {
|
|
166
|
+
const cached = this.userCache.get(userId);
|
|
167
|
+
if (cached)
|
|
168
|
+
return cached;
|
|
169
|
+
try {
|
|
170
|
+
const result = await this.app.client.users.info({ user: userId });
|
|
171
|
+
const name = result.user?.profile?.display_name ||
|
|
172
|
+
result.user?.real_name ||
|
|
173
|
+
result.user?.name ||
|
|
174
|
+
userId;
|
|
175
|
+
this.userCache.set(userId, name);
|
|
176
|
+
return name;
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return userId;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async slackEventToChatMessage(event) {
|
|
183
|
+
const isBot = !!(event.bot_id || event.user === this.botUserId);
|
|
184
|
+
const userName = isBot
|
|
185
|
+
? 'bot'
|
|
186
|
+
: await this.resolveUserName(event.user ?? '');
|
|
187
|
+
return {
|
|
188
|
+
platform: 'slack',
|
|
189
|
+
channelId: event.channel,
|
|
190
|
+
conversationId: event.thread_ts ?? event.ts,
|
|
191
|
+
messageId: event.ts,
|
|
192
|
+
userId: event.user ?? event.bot_id ?? '',
|
|
193
|
+
userName,
|
|
194
|
+
text: slackMrkdwnToMarkdown(event.text ?? ''),
|
|
195
|
+
timestamp: event.ts,
|
|
196
|
+
isBot,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
async slackMsgToChatMessage(channelId, msg) {
|
|
200
|
+
const isBot = !!(msg.bot_id || msg.user === this.botUserId);
|
|
201
|
+
const userName = isBot
|
|
202
|
+
? 'bot'
|
|
203
|
+
: await this.resolveUserName(msg.user ?? '');
|
|
204
|
+
return {
|
|
205
|
+
platform: 'slack',
|
|
206
|
+
channelId,
|
|
207
|
+
conversationId: msg.thread_ts ?? msg.ts,
|
|
208
|
+
messageId: msg.ts,
|
|
209
|
+
userId: msg.user ?? msg.bot_id ?? '',
|
|
210
|
+
userName,
|
|
211
|
+
text: slackMrkdwnToMarkdown(msg.text ?? ''),
|
|
212
|
+
timestamp: msg.ts,
|
|
213
|
+
isBot,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
attemptReconnect() {
|
|
217
|
+
this.reconnectBackoff = Math.min(this.reconnectBackoff * 2, 30_000);
|
|
218
|
+
setTimeout(async () => {
|
|
219
|
+
try {
|
|
220
|
+
await this.app.stop();
|
|
221
|
+
await this.app.start();
|
|
222
|
+
this.reconnectBackoff = 1000;
|
|
223
|
+
this.lastEventTime = Date.now();
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
// Will retry on next health check interval
|
|
227
|
+
}
|
|
228
|
+
}, this.reconnectBackoff);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// ── Slack mrkdwn → Markdown conversion ──
|
|
232
|
+
function slackMrkdwnToMarkdown(text) {
|
|
233
|
+
return text
|
|
234
|
+
.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, '**$1**') // *bold* → **bold**
|
|
235
|
+
.replace(/(?<!_)_([^_]+)_(?!_)/g, '*$1*') // _italic_ → *italic*
|
|
236
|
+
.replace(/~([^~]+)~/g, '~~$1~~') // ~strike~ → ~~strike~~
|
|
237
|
+
.replace(/<(https?:\/\/[^|>]+)\|([^>]+)>/g, '[$2]($1)') // <url|text> → [text](url)
|
|
238
|
+
.replace(/<(https?:\/\/[^>]+)>/g, '$1'); // <url> → url
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=slack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack.js","sourceRoot":"","sources":["../../../../src/lib/relay/platforms/slack.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,iEAAiE;AAkBjE,MAAM,OAAO,YAAY;IAiCH;IAhCX,IAAI,GAAG,OAAO,CAAC;IACf,YAAY,GAAyB;QAC5C,eAAe,EAAE,IAAI;QACrB,iBAAiB,EAAE,EAAE;QACrB,iBAAiB,EAAE,IAAI;QACvB,qBAAqB,EAAE,IAAI;QAC3B,eAAe,EAAE,IAAI;QACrB,gBAAgB,EAAE,IAAI;KACvB,CAAC;IAEF,SAAS,GAAwC,KAAK,IAAI,EAAE,GAAE,CAAC,CAAC;IAChE,UAAU,GAKW,KAAK,IAAI,EAAE,GAAE,CAAC,CAAC;IACpC,SAAS,GAKc,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;IAE9B,GAAG,CAAM,CAAC,kCAAkC;IAC5C,SAAS,CAAqB;IAC9B,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC,CAAC,uBAAuB;IAC9D,gBAAgB,GAAG,IAAI,CAAC;IACxB,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,WAAW,CAA6B;IACxC,gBAAgB,GAAG,IAAI,GAAG,EAAU,CAAC,CAAC,0BAA0B;IAExE,YAAoB,MAA0B;QAA1B,WAAM,GAAN,MAAM,CAAoB;IAAG,CAAC;IAElD,KAAK,CAAC,KAAK;QACT,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QAE5C,IAAI,CAAC,GAAG,GAAG,IAAI,GAAG,CAAC;YACjB,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;YAC3B,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;YAC9B,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa;YACxC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,QAAQ;YACzC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI;SACvB,CAAC,CAAC;QAEH,2BAA2B;QAC3B,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAO,EAAE,EAAE;YAC9D,IAAI,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC;gBAAE,OAAO;YAC1C,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAChC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC;YACtD,IAAI,CAAC,GAAG,CAAC,KAAK;gBAAE,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;QAEH,uBAAuB;QACvB,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAO,EAAE,EAAE;YAC1D,IAAI,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC;gBAAE,OAAO;YAC1C,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,KAAK,YAAY;gBAAE,OAAO;YAC5D,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAChC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC;YACtD,IAAI,CAAC,GAAG,CAAC,KAAK;gBAAE,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;QAEH,8BAA8B;QAC9B,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,gBAAgB,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAO,EAAE,EAAE;YACjE,IAAI,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC;gBAAE,OAAO;YAC1C,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAChC,MAAM,IAAI,CAAC,UAAU,CACnB,KAAK,CAAC,IAAI,CAAC,OAAO,EAClB,KAAK,CAAC,IAAI,CAAC,EAAE,EACb,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,QAAQ,CACf,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,uCAAuC;QACvC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,iBAAiB,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,GAAG,EAAO,EAAE,EAAE;YAClE,MAAM,GAAG,EAAE,CAAC;YACZ,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAChC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CACnC,OAAO,CAAC,UAAU,EAClB,OAAO,CAAC,OAAO,EACf,MAAM,EACN,OAAO,CAAC,IAAI,CACb,CAAC;YACF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;QAC5B,CAAC,CAAC,CAAC;QAEH,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QAEvB,yCAAyC;QACzC,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACrD,IAAI,CAAC,SAAS,GAAG,UAAU,CAAC,OAAO,CAAC;QAEpC,4DAA4D;QAC5D,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE;YAClC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,aAAa,GAAG,MAAM,EAAE,CAAC;gBAC7C,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1B,CAAC;QACH,CAAC,EAAE,MAAM,CAAC,CAAC;IACb,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAChC,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;QAC/B,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACxB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,WAAW,CACf,SAAiB,EACjB,IAAY,EACZ,QAAiB;QAEjB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC;YACpD,OAAO,EAAE,SAAS;YAClB,IAAI;YACJ,SAAS,EAAE,QAAQ;SACpB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,EAAY,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,aAAa,CACjB,SAAiB,EACjB,SAAiB,EACjB,IAAY;QAEZ,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;YAChC,OAAO,EAAE,SAAS;YAClB,EAAE,EAAE,SAAS;YACb,IAAI;SACL,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,WAAW,CACf,SAAiB,EACjB,QAAiB;QAEjB,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,WAAW,CACtC,SAAS,EACT,qBAAqB,EACrB,QAAQ,CACT,CAAC;QACF,IAAI,WAAW,GAAG,EAAE,CAAC;QACrB,IAAI,OAAmC,CAAC;QACxC,MAAM,UAAU,GAAG,GAAG,CAAC;QAEvB,MAAM,KAAK,GAAG,KAAK,EAAE,IAAY,EAAiB,EAAE;YAClD,MAAM,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;QACvD,CAAC,CAAC;QAEF,OAAO;YACL,MAAM,EAAE,KAAK,EAAE,IAAY,EAAiB,EAAE;gBAC5C,WAAW,IAAI,IAAI,CAAC;gBACpB,IAAI,OAAO;oBAAE,YAAY,CAAC,OAAO,CAAC,CAAC;gBACnC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;oBACxB,KAAK,KAAK,CAAC,WAAW,CAAC,CAAC;gBAC1B,CAAC,EAAE,UAAU,CAAC,CAAC;YACjB,CAAC;YACD,IAAI,EAAE,KAAK,EAAE,SAAkB,EAAiB,EAAE;gBAChD,IAAI,OAAO;oBAAE,YAAY,CAAC,OAAO,CAAC,CAAC;gBACnC,MAAM,KAAK,CAAC,SAAS,IAAI,WAAW,CAAC,CAAC;YACxC,CAAC;SACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,UAAU,CACd,SAAiB,EACjB,OAAe,EACf,QAAgB,EAChB,QAAiB;QAEjB,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC;YACnC,UAAU,EAAE,SAAS;YACrB,OAAO;YACP,QAAQ;YACR,SAAS,EAAE,QAAQ;SACpB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,gBAAgB,CACpB,SAAiB,EACjB,QAAgB,EAChB,OAAgB;QAEhB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC;YACzD,OAAO,EAAE,SAAS;YAClB,EAAE,EAAE,QAAQ;YACZ,MAAM,EAAE,OAAO;SAChB,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAkB,EAAE,CAAC;QACnC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;YACxC,QAAQ,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,qBAAqB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC;QAClE,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,gBAAgB;IAER,eAAe,CAAC,OAAY;QAClC,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,CAAC;QACnC,IAAI,QAAQ,IAAI,IAAI;YAAE,OAAO,KAAK,CAAC;QACnC,MAAM,GAAG,GAAG,GAAG,QAAQ,EAAE,CAAC;QAC1B,IAAI,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAChD,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,uDAAuD;QACvD,IAAI,IAAI,CAAC,gBAAgB,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC;YACtC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAClD,IAAI,CAAC,gBAAgB,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACvD,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,KAAK,CAAC,eAAe,CAAC,MAAc;QAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAE1B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YAClE,MAAM,IAAI,GACR,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,YAAY;gBAClC,MAAM,CAAC,IAAI,EAAE,SAAS;gBACtB,MAAM,CAAC,IAAI,EAAE,IAAI;gBACjB,MAAM,CAAC;YACT,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACjC,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,uBAAuB,CAAC,KAAU;QAC9C,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC;QAChE,MAAM,QAAQ,GAAG,KAAK;YACpB,CAAC,CAAC,KAAK;YACP,CAAC,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QAEjD,OAAO;YACL,QAAQ,EAAE,OAAO;YACjB,SAAS,EAAE,KAAK,CAAC,OAAO;YACxB,cAAc,EAAE,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,EAAE;YAC3C,SAAS,EAAE,KAAK,CAAC,EAAE;YACnB,MAAM,EAAE,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,MAAM,IAAI,EAAE;YACxC,QAAQ;YACR,IAAI,EAAE,qBAAqB,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;YAC7C,SAAS,EAAE,KAAK,CAAC,EAAE;YACnB,KAAK;SACN,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,qBAAqB,CACjC,SAAiB,EACjB,GAAQ;QAER,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5D,MAAM,QAAQ,GAAG,KAAK;YACpB,CAAC,CAAC,KAAK;YACP,CAAC,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QAE/C,OAAO;YACL,QAAQ,EAAE,OAAO;YACjB,SAAS;YACT,cAAc,EAAE,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,EAAE;YACvC,SAAS,EAAE,GAAG,CAAC,EAAE;YACjB,MAAM,EAAE,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,MAAM,IAAI,EAAE;YACpC,QAAQ;YACR,IAAI,EAAE,qBAAqB,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;YAC3C,SAAS,EAAE,GAAG,CAAC,EAAE;YACjB,KAAK;SACN,CAAC;IACJ,CAAC;IAEO,gBAAgB;QACtB,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,gBAAgB,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;QACpE,UAAU,CAAC,KAAK,IAAI,EAAE;YACpB,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;gBACtB,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;gBACvB,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;gBAC7B,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAClC,CAAC;YAAC,MAAM,CAAC;gBACP,2CAA2C;YAC7C,CAAC;QACH,CAAC,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAC5B,CAAC;CACF;AAED,2CAA2C;AAE3C,SAAS,qBAAqB,CAAC,IAAY;IACzC,OAAO,IAAI;SACR,OAAO,CAAC,2BAA2B,EAAE,QAAQ,CAAC,CAAC,oBAAoB;SACnE,OAAO,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAC,sBAAsB;SAC/D,OAAO,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,wBAAwB;SACxD,OAAO,CAAC,iCAAiC,EAAE,UAAU,CAAC,CAAC,2BAA2B;SAClF,OAAO,CAAC,uBAAuB,EAAE,IAAI,CAAC,CAAC,CAAC,cAAc;AAC3D,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ChatPlatform, ChatMessage, PlatformCapabilities, StreamHandle } from '../platform.js';
|
|
2
|
+
export interface TelegramAdapterConfig {
|
|
3
|
+
botToken: string;
|
|
4
|
+
streamMode: 'edit' | 'chunked' | 'final-only';
|
|
5
|
+
editIntervalMs: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class TelegramAdapter implements ChatPlatform {
|
|
8
|
+
private config;
|
|
9
|
+
readonly name = "telegram";
|
|
10
|
+
readonly capabilities: PlatformCapabilities;
|
|
11
|
+
onMessage: (msg: ChatMessage) => Promise<void>;
|
|
12
|
+
onReaction: (channelId: string, messageId: string, userId: string, emoji: string) => Promise<void>;
|
|
13
|
+
onCommand: (channelId: string, userId: string, command: string, args: string) => Promise<string>;
|
|
14
|
+
private bot;
|
|
15
|
+
private InputFile;
|
|
16
|
+
constructor(config: TelegramAdapterConfig);
|
|
17
|
+
start(): Promise<void>;
|
|
18
|
+
stop(): Promise<void>;
|
|
19
|
+
sendMessage(channelId: string, text: string, threadId?: string): Promise<string>;
|
|
20
|
+
updateMessage(channelId: string, messageId: string, text: string): Promise<void>;
|
|
21
|
+
startStream(channelId: string, threadId?: string): Promise<StreamHandle>;
|
|
22
|
+
uploadFile(channelId: string, content: string, filename: string, threadId?: string): Promise<void>;
|
|
23
|
+
getThreadHistory(_channelId: string, _threadId: string, _afterTs?: string): Promise<ChatMessage[]>;
|
|
24
|
+
private streamEdit;
|
|
25
|
+
private streamChunked;
|
|
26
|
+
private streamFinalOnly;
|
|
27
|
+
private telegramToChatMessage;
|
|
28
|
+
private resolveConversationId;
|
|
29
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// ── Telegram Platform Adapter ──
|
|
2
|
+
// Implements ChatPlatform via grammy Bot framework.
|
|
3
|
+
export class TelegramAdapter {
|
|
4
|
+
config;
|
|
5
|
+
name = 'telegram';
|
|
6
|
+
capabilities = {
|
|
7
|
+
nativeStreaming: false,
|
|
8
|
+
maxStreamUpdateHz: 0.08,
|
|
9
|
+
supportsReactions: true,
|
|
10
|
+
supportsSlashCommands: false,
|
|
11
|
+
supportsThreads: false,
|
|
12
|
+
maxMessageLength: 4096,
|
|
13
|
+
};
|
|
14
|
+
onMessage = async () => { };
|
|
15
|
+
onReaction = async () => { };
|
|
16
|
+
onCommand = async () => '';
|
|
17
|
+
bot; // grammy Bot – dynamically imported
|
|
18
|
+
InputFile; // grammy InputFile constructor
|
|
19
|
+
constructor(config) {
|
|
20
|
+
this.config = config;
|
|
21
|
+
}
|
|
22
|
+
async start() {
|
|
23
|
+
const grammy = await import('grammy');
|
|
24
|
+
const { Bot } = grammy;
|
|
25
|
+
this.InputFile = grammy.InputFile;
|
|
26
|
+
this.bot = new Bot(this.config.botToken);
|
|
27
|
+
// ── Text messages ──
|
|
28
|
+
this.bot.on('message:text', async (ctx) => {
|
|
29
|
+
const msg = ctx.message;
|
|
30
|
+
// Handle /command messages
|
|
31
|
+
if (msg.text.startsWith('/')) {
|
|
32
|
+
const [cmdRaw, ...rest] = msg.text.split(' ');
|
|
33
|
+
const command = cmdRaw.slice(1).replace(/@.*$/, ''); // strip /prefix and @botname
|
|
34
|
+
const args = rest.join(' ');
|
|
35
|
+
const response = await this.onCommand(String(msg.chat.id), String(msg.from.id), command, args);
|
|
36
|
+
if (response) {
|
|
37
|
+
await ctx.reply(response);
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const chatMessage = this.telegramToChatMessage(msg);
|
|
42
|
+
await this.onMessage(chatMessage);
|
|
43
|
+
});
|
|
44
|
+
// ── Callback queries (inline keyboard feedback) ──
|
|
45
|
+
this.bot.on('callback_query:data', async (ctx) => {
|
|
46
|
+
const query = ctx.callbackQuery;
|
|
47
|
+
await ctx.answerCallbackQuery();
|
|
48
|
+
await this.onReaction(String(query.message?.chat?.id ?? ''), String(query.message?.message_id ?? ''), String(query.from.id), query.data);
|
|
49
|
+
});
|
|
50
|
+
await this.bot.start();
|
|
51
|
+
}
|
|
52
|
+
async stop() {
|
|
53
|
+
if (this.bot) {
|
|
54
|
+
this.bot.stop();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async sendMessage(channelId, text, threadId) {
|
|
58
|
+
const opts = {};
|
|
59
|
+
if (threadId) {
|
|
60
|
+
opts.reply_to_message_id = parseInt(threadId, 10);
|
|
61
|
+
}
|
|
62
|
+
const escaped = escapeMarkdownV2(text);
|
|
63
|
+
const result = await this.bot.api.sendMessage(channelId, escaped, { ...opts, parse_mode: 'MarkdownV2' });
|
|
64
|
+
return String(result.message_id);
|
|
65
|
+
}
|
|
66
|
+
async updateMessage(channelId, messageId, text) {
|
|
67
|
+
const escaped = escapeMarkdownV2(text);
|
|
68
|
+
await this.bot.api.editMessageText(channelId, parseInt(messageId, 10), escaped, { parse_mode: 'MarkdownV2' });
|
|
69
|
+
}
|
|
70
|
+
async startStream(channelId, threadId) {
|
|
71
|
+
const mode = this.config.streamMode;
|
|
72
|
+
const intervalMs = this.config.editIntervalMs;
|
|
73
|
+
if (mode === 'final-only') {
|
|
74
|
+
return this.streamFinalOnly(channelId, threadId);
|
|
75
|
+
}
|
|
76
|
+
if (mode === 'chunked') {
|
|
77
|
+
return this.streamChunked(channelId, threadId);
|
|
78
|
+
}
|
|
79
|
+
// Default: 'edit' mode
|
|
80
|
+
return this.streamEdit(channelId, threadId, intervalMs);
|
|
81
|
+
}
|
|
82
|
+
async uploadFile(channelId, content, filename, threadId) {
|
|
83
|
+
const opts = {};
|
|
84
|
+
if (threadId) {
|
|
85
|
+
opts.reply_to_message_id = parseInt(threadId, 10);
|
|
86
|
+
}
|
|
87
|
+
const inputFile = new this.InputFile(Buffer.from(content), filename);
|
|
88
|
+
await this.bot.api.sendDocument(channelId, inputFile, opts);
|
|
89
|
+
}
|
|
90
|
+
async getThreadHistory(_channelId, _threadId, _afterTs) {
|
|
91
|
+
// Telegram has no thread history API – rely on SQLite cache
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
// ── Stream mode implementations ──
|
|
95
|
+
async streamEdit(channelId, threadId, intervalMs) {
|
|
96
|
+
const msgId = await this.sendMessage(channelId, 'Working on it\u2026', threadId);
|
|
97
|
+
let accumulated = '';
|
|
98
|
+
let lastUpdate = 0;
|
|
99
|
+
let pending;
|
|
100
|
+
const flush = async (text) => {
|
|
101
|
+
const now = Date.now();
|
|
102
|
+
if (now - lastUpdate < intervalMs)
|
|
103
|
+
return;
|
|
104
|
+
lastUpdate = now;
|
|
105
|
+
try {
|
|
106
|
+
await this.updateMessage(channelId, msgId, text);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Telegram rate limit or identical content – ignore
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
append: async (text) => {
|
|
114
|
+
accumulated += text;
|
|
115
|
+
if (pending)
|
|
116
|
+
clearTimeout(pending);
|
|
117
|
+
pending = setTimeout(() => {
|
|
118
|
+
void flush(accumulated);
|
|
119
|
+
}, intervalMs);
|
|
120
|
+
},
|
|
121
|
+
stop: async (finalText) => {
|
|
122
|
+
if (pending)
|
|
123
|
+
clearTimeout(pending);
|
|
124
|
+
await this.updateMessage(channelId, msgId, finalText ?? accumulated);
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
async streamChunked(channelId, threadId) {
|
|
129
|
+
const chunkSize = 2000;
|
|
130
|
+
let accumulated = '';
|
|
131
|
+
let sentLength = 0;
|
|
132
|
+
return {
|
|
133
|
+
append: async (text) => {
|
|
134
|
+
accumulated += text;
|
|
135
|
+
while (accumulated.length - sentLength >= chunkSize) {
|
|
136
|
+
const chunk = accumulated.slice(sentLength, sentLength + chunkSize);
|
|
137
|
+
await this.sendMessage(channelId, chunk, threadId);
|
|
138
|
+
sentLength += chunkSize;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
stop: async (finalText) => {
|
|
142
|
+
const remaining = finalText ?? accumulated.slice(sentLength);
|
|
143
|
+
if (remaining.length > 0) {
|
|
144
|
+
await this.sendMessage(channelId, remaining, threadId);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
streamFinalOnly(channelId, threadId) {
|
|
150
|
+
let accumulated = '';
|
|
151
|
+
return {
|
|
152
|
+
append: async (text) => {
|
|
153
|
+
accumulated += text;
|
|
154
|
+
},
|
|
155
|
+
stop: async (finalText) => {
|
|
156
|
+
const content = finalText ?? accumulated;
|
|
157
|
+
if (content.length > 0) {
|
|
158
|
+
await this.sendMessage(channelId, content, threadId);
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// ── Helpers ──
|
|
164
|
+
telegramToChatMessage(msg) {
|
|
165
|
+
const conversationId = this.resolveConversationId(msg);
|
|
166
|
+
return {
|
|
167
|
+
platform: 'telegram',
|
|
168
|
+
channelId: String(msg.chat.id),
|
|
169
|
+
conversationId,
|
|
170
|
+
messageId: String(msg.message_id),
|
|
171
|
+
userId: String(msg.from.id),
|
|
172
|
+
userName: msg.from.username ||
|
|
173
|
+
[msg.from.first_name, msg.from.last_name].filter(Boolean).join(' ') ||
|
|
174
|
+
String(msg.from.id),
|
|
175
|
+
text: msg.text ?? '',
|
|
176
|
+
timestamp: String(msg.date),
|
|
177
|
+
isBot: msg.from.is_bot ?? false,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
resolveConversationId(msg) {
|
|
181
|
+
// Walk reply chain to find root message ID
|
|
182
|
+
let current = msg;
|
|
183
|
+
while (current.reply_to_message) {
|
|
184
|
+
current = current.reply_to_message;
|
|
185
|
+
}
|
|
186
|
+
return String(current.message_id);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// ── Telegram MarkdownV2 escaping ──
|
|
190
|
+
function escapeMarkdownV2(text) {
|
|
191
|
+
return text.replace(/([_*\[\]()~`>#+\-=|{}.!\\])/g, '\\$1');
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=telegram.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telegram.js","sourceRoot":"","sources":["../../../../src/lib/relay/platforms/telegram.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RelayRunResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Split a long response into parts that fit within maxLen.
|
|
4
|
+
* Splits at paragraph boundaries first, then line boundaries, then hard-splits.
|
|
5
|
+
* Numbers parts "[1/N]", "[2/N]" when there are multiple.
|
|
6
|
+
*/
|
|
7
|
+
export declare function formatResponse(text: string, maxLen: number): string[];
|
|
8
|
+
/**
|
|
9
|
+
* Build a cost/usage footer for a relay run result.
|
|
10
|
+
*/
|
|
11
|
+
export declare function addCostFooter(result: RelayRunResult): string;
|
|
12
|
+
/**
|
|
13
|
+
* Run `git diff --stat` in a project directory.
|
|
14
|
+
* Returns the output or null if not a git repo / no changes.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getGitDiffSummary(projectDir: string): string | null;
|
|
17
|
+
/**
|
|
18
|
+
* Multi-stage sanitization of agent output before posting to chat.
|
|
19
|
+
*
|
|
20
|
+
* Stage 1: Pattern-based secret detection (redact matched patterns)
|
|
21
|
+
* Stage 2: Literal env value replacement
|
|
22
|
+
* Stage 3: Length cap
|
|
23
|
+
*/
|
|
24
|
+
export declare function sanitizeAgentOutput(text: string, projectEnv: Record<string, string>): string;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
const MAX_OUTPUT_CHARS = 40_000;
|
|
3
|
+
const TRUNCATION_THRESHOLD = 12_000;
|
|
4
|
+
/**
|
|
5
|
+
* Split a long response into parts that fit within maxLen.
|
|
6
|
+
* Splits at paragraph boundaries first, then line boundaries, then hard-splits.
|
|
7
|
+
* Numbers parts "[1/N]", "[2/N]" when there are multiple.
|
|
8
|
+
*/
|
|
9
|
+
export function formatResponse(text, maxLen) {
|
|
10
|
+
if (text.length > TRUNCATION_THRESHOLD) {
|
|
11
|
+
text = text.slice(0, TRUNCATION_THRESHOLD) +
|
|
12
|
+
'\n\n_Response truncated. Full output available as file._';
|
|
13
|
+
}
|
|
14
|
+
if (text.length <= maxLen) {
|
|
15
|
+
return [text];
|
|
16
|
+
}
|
|
17
|
+
const chunks = [];
|
|
18
|
+
// Split at paragraph boundaries (double newline)
|
|
19
|
+
const paragraphs = text.split(/\n\n+/);
|
|
20
|
+
let current = '';
|
|
21
|
+
for (const para of paragraphs) {
|
|
22
|
+
if (para.length > maxLen) {
|
|
23
|
+
// Flush current buffer
|
|
24
|
+
if (current.length > 0) {
|
|
25
|
+
chunks.push(current.trimEnd());
|
|
26
|
+
current = '';
|
|
27
|
+
}
|
|
28
|
+
// Split long paragraph at line boundaries
|
|
29
|
+
const lines = para.split('\n');
|
|
30
|
+
for (const line of lines) {
|
|
31
|
+
if (line.length > maxLen) {
|
|
32
|
+
// Flush current
|
|
33
|
+
if (current.length > 0) {
|
|
34
|
+
chunks.push(current.trimEnd());
|
|
35
|
+
current = '';
|
|
36
|
+
}
|
|
37
|
+
// Hard-split
|
|
38
|
+
for (let i = 0; i < line.length; i += maxLen) {
|
|
39
|
+
chunks.push(line.slice(i, i + maxLen));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (current.length + line.length + 1 > maxLen) {
|
|
43
|
+
chunks.push(current.trimEnd());
|
|
44
|
+
current = line;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
current += (current.length > 0 ? '\n' : '') + line;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else if (current.length + para.length + 2 > maxLen) {
|
|
52
|
+
chunks.push(current.trimEnd());
|
|
53
|
+
current = para;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
current += (current.length > 0 ? '\n\n' : '') + para;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (current.length > 0) {
|
|
60
|
+
chunks.push(current.trimEnd());
|
|
61
|
+
}
|
|
62
|
+
// Number parts if multiple
|
|
63
|
+
if (chunks.length > 1) {
|
|
64
|
+
const total = chunks.length;
|
|
65
|
+
return chunks.map((c, i) => `[${i + 1}/${total}]\n${c}`);
|
|
66
|
+
}
|
|
67
|
+
return chunks;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Build a cost/usage footer for a relay run result.
|
|
71
|
+
*/
|
|
72
|
+
export function addCostFooter(result) {
|
|
73
|
+
const dur = Math.round(result.durationMs / 1000);
|
|
74
|
+
const cost = result.costUsd.toFixed(4);
|
|
75
|
+
const model = result.model ?? 'unknown';
|
|
76
|
+
return `_Model: ${model} | Cost: $${cost} | Tokens: ${result.inputTokens} in / ${result.outputTokens} out | Duration: ${dur}s | Turns: ${result.numTurns}_`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Run `git diff --stat` in a project directory.
|
|
80
|
+
* Returns the output or null if not a git repo / no changes.
|
|
81
|
+
*/
|
|
82
|
+
export function getGitDiffSummary(projectDir) {
|
|
83
|
+
try {
|
|
84
|
+
const diff = execFileSync('git', ['diff', '--stat'], {
|
|
85
|
+
cwd: projectDir,
|
|
86
|
+
timeout: 5000,
|
|
87
|
+
encoding: 'utf8',
|
|
88
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
89
|
+
}).trim();
|
|
90
|
+
return diff.length > 0 ? diff : null;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Multi-stage sanitization of agent output before posting to chat.
|
|
98
|
+
*
|
|
99
|
+
* Stage 1: Pattern-based secret detection (redact matched patterns)
|
|
100
|
+
* Stage 2: Literal env value replacement
|
|
101
|
+
* Stage 3: Length cap
|
|
102
|
+
*/
|
|
103
|
+
export function sanitizeAgentOutput(text, projectEnv) {
|
|
104
|
+
let sanitized = text;
|
|
105
|
+
// Stage 1: Pattern-based secret detection — always run all patterns
|
|
106
|
+
const patterns = [
|
|
107
|
+
/(?:sk|pk|api|key|token|secret|password|auth)[-_]?[a-zA-Z0-9]{20,}/gi,
|
|
108
|
+
/AIza[0-9A-Za-z_-]{35}/g,
|
|
109
|
+
/ghp_[0-9a-zA-Z]{36}/g,
|
|
110
|
+
/(?:AKIA|ASIA)[0-9A-Z]{16}/g,
|
|
111
|
+
/xox[bporas]-[0-9a-zA-Z-]{10,}/g,
|
|
112
|
+
/-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/g,
|
|
113
|
+
/sk-ant-[a-zA-Z0-9_-]{20,}/g,
|
|
114
|
+
/eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+/g,
|
|
115
|
+
/[a-z]+:\/\/[^:]+:[^@]+@[^\s]+/g,
|
|
116
|
+
/xapp-[0-9a-zA-Z-]{10,}/g,
|
|
117
|
+
];
|
|
118
|
+
for (const pattern of patterns) {
|
|
119
|
+
sanitized = sanitized.replace(pattern, '[REDACTED]');
|
|
120
|
+
}
|
|
121
|
+
// Stage 2: Literal env value replacement
|
|
122
|
+
for (const [key, value] of Object.entries(projectEnv)) {
|
|
123
|
+
if (value.length > 3) {
|
|
124
|
+
// Use split/join for literal replacement (no regex special chars issues)
|
|
125
|
+
while (sanitized.includes(value)) {
|
|
126
|
+
sanitized = sanitized.split(value).join(`[ENV:${key}]`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Stage 3: Length cap
|
|
131
|
+
if (sanitized.length > MAX_OUTPUT_CHARS) {
|
|
132
|
+
sanitized = sanitized.slice(0, MAX_OUTPUT_CHARS) + '\n[output truncated]';
|
|
133
|
+
}
|
|
134
|
+
return sanitized;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=poster.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poster.js","sourceRoot":"","sources":["../../../src/lib/relay/poster.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIlD,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAChC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAEpC;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,MAAc;IACzD,IAAI,IAAI,CAAC,MAAM,GAAG,oBAAoB,EAAE,CAAC;QACvC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,oBAAoB,CAAC;YACxC,0DAA0D,CAAC;IAC/D,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,IAAI,MAAM,EAAE,CAAC;QAC1B,OAAO,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC;IAED,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,iDAAiD;IACjD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACvC,IAAI,OAAO,GAAG,EAAE,CAAC;IAEjB,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,IAAI,IAAI,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC;YACzB,uBAAuB;YACvB,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;gBAC/B,OAAO,GAAG,EAAE,CAAC;YACf,CAAC;YACD,0CAA0C;YAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,IAAI,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC;oBACzB,gBAAgB;oBAChB,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBACvB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;wBAC/B,OAAO,GAAG,EAAE,CAAC;oBACf,CAAC;oBACD,aAAa;oBACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC;wBAC7C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;oBACzC,CAAC;gBACH,CAAC;qBAAM,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,MAAM,EAAE,CAAC;oBACrD,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;oBAC/B,OAAO,GAAG,IAAI,CAAC;gBACjB,CAAC;qBAAM,CAAC;oBACN,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;gBACrD,CAAC;YACH,CAAC;QACH,CAAC;aAAM,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,MAAM,EAAE,CAAC;YACrD,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;YAC/B,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;QACvD,CAAC;IACH,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACjC,CAAC;IAED,2BAA2B;IAC3B,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC;QAC5B,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,MAAsB;IAClD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC;IACxC,OAAO,WAAW,KAAK,aAAa,IAAI,cAAc,MAAM,CAAC,WAAW,SAAS,MAAM,CAAC,YAAY,oBAAoB,GAAG,cAAc,MAAM,CAAC,QAAQ,GAAG,CAAC;AAC9J,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAkB;IAClD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE;YACnD,GAAG,EAAE,UAAU;YACf,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;SAChC,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAY,EACZ,UAAkC;IAElC,IAAI,SAAS,GAAG,IAAI,CAAC;IAErB,oEAAoE;IACpE,MAAM,QAAQ,GAAG;QACf,qEAAqE;QACrE,wBAAwB;QACxB,sBAAsB;QACtB,4BAA4B;QAC5B,gCAAgC;QAChC,2CAA2C;QAC3C,4BAA4B;QAC5B,uCAAuC;QACvC,gCAAgC;QAChC,yBAAyB;KAC1B,CAAC;IACF,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IACvD,CAAC;IAED,yCAAyC;IACzC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACtD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,yEAAyE;YACzE,OAAO,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACjC,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC,CAAC;YAC1D,CAAC;QACH,CAAC;IACH,CAAC;IAED,sBAAsB;IACtB,IAAI,SAAS,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;QACxC,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,sBAAsB,CAAC;IAC5E,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
|