instar 0.8.6 → 0.8.7
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/cli.js
CHANGED
|
File without changes
|
|
@@ -135,6 +135,11 @@ When user input starts with \`[telegram:N]\` (e.g., \`[telegram:26] hello\`), th
|
|
|
135
135
|
|
|
136
136
|
**IMMEDIATE ACKNOWLEDGMENT (MANDATORY):** When you receive a Telegram message, your FIRST action — before reading files, searching code, or doing any work — must be sending a brief acknowledgment back. This confirms the message was received and you haven't stalled. Examples: "Got it, looking into this now." / "On it — checking the scheduler." / "Received, working on the sync." Then do the work, then send the full response.
|
|
137
137
|
|
|
138
|
+
**Message types:**
|
|
139
|
+
- **Text**: \`[telegram:26] hello there\` — standard text message
|
|
140
|
+
- **Voice**: \`[telegram:26] [voice] transcribed text here\` — voice message, already transcribed
|
|
141
|
+
- **Photo**: \`[telegram:26] [image:/path/to/file.jpg]\` or \`[telegram:26] [image:/path/to/file.jpg] caption text\` — use the Read tool to view the image at the given path
|
|
142
|
+
|
|
138
143
|
**Response relay:** After completing your work, relay your response back:
|
|
139
144
|
|
|
140
145
|
\`\`\`bash
|
|
@@ -163,6 +168,17 @@ Strip the \`[telegram:N]\` prefix before interpreting the message. Respond natur
|
|
|
163
168
|
}
|
|
164
169
|
}
|
|
165
170
|
}
|
|
171
|
+
// Upgrade existing Telegram Relay sections to document image message format
|
|
172
|
+
if (this.config.hasTelegram && content.includes('Telegram Relay') && !content.includes('[image:')) {
|
|
173
|
+
const imageBlock = `\n**Message types:**\n- **Text**: \`[telegram:N] hello there\` — standard text message\n- **Voice**: \`[telegram:N] [voice] transcribed text here\` — voice message, already transcribed\n- **Photo**: \`[telegram:N] [image:/path/to/file.jpg]\` or with caption — use the Read tool to view the image at the given path\n`;
|
|
174
|
+
// Insert before the Response relay section
|
|
175
|
+
const relayIdx = content.indexOf('**Response relay:**');
|
|
176
|
+
if (relayIdx >= 0) {
|
|
177
|
+
content = content.slice(0, relayIdx) + imageBlock + '\n' + content.slice(relayIdx);
|
|
178
|
+
patched = true;
|
|
179
|
+
result.upgraded.push('CLAUDE.md: added image/photo message format to Telegram Relay');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
166
182
|
// Private Viewer + Tunnel section
|
|
167
183
|
if (!content.includes('Private Viewing') && !content.includes('POST /view')) {
|
|
168
184
|
const section = `
|
|
@@ -38,6 +38,14 @@ export declare class TelegramLifeline {
|
|
|
38
38
|
start(): Promise<void>;
|
|
39
39
|
private poll;
|
|
40
40
|
private processUpdate;
|
|
41
|
+
/**
|
|
42
|
+
* Handle an incoming photo message: download it and forward/queue with [image:path] content.
|
|
43
|
+
*/
|
|
44
|
+
private handlePhotoMessage;
|
|
45
|
+
/**
|
|
46
|
+
* Download a photo from Telegram and save it to the state directory.
|
|
47
|
+
*/
|
|
48
|
+
private downloadPhoto;
|
|
41
49
|
/**
|
|
42
50
|
* Forward a message to the Instar server's Telegram webhook.
|
|
43
51
|
*/
|
|
@@ -233,7 +233,14 @@ export class TelegramLifeline {
|
|
|
233
233
|
}
|
|
234
234
|
async processUpdate(update) {
|
|
235
235
|
const msg = update.message;
|
|
236
|
-
if (!msg
|
|
236
|
+
if (!msg)
|
|
237
|
+
return;
|
|
238
|
+
// Handle photo messages
|
|
239
|
+
if (msg.photo && msg.photo.length > 0 && !msg.text) {
|
|
240
|
+
await this.handlePhotoMessage(msg);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (!msg.text)
|
|
237
244
|
return;
|
|
238
245
|
const topicId = msg.message_thread_id ?? 1;
|
|
239
246
|
const text = msg.text;
|
|
@@ -276,6 +283,73 @@ export class TelegramLifeline {
|
|
|
276
283
|
// Notify user that message is queued
|
|
277
284
|
await this.sendToTopic(topicId, `Server is temporarily down. Your message has been queued (${this.queue.length} in queue). It will be delivered when the server recovers.`);
|
|
278
285
|
}
|
|
286
|
+
/**
|
|
287
|
+
* Handle an incoming photo message: download it and forward/queue with [image:path] content.
|
|
288
|
+
*/
|
|
289
|
+
async handlePhotoMessage(msg) {
|
|
290
|
+
const topicId = msg.message_thread_id ?? 1;
|
|
291
|
+
const photos = msg.photo;
|
|
292
|
+
const photo = photos[photos.length - 1]; // highest resolution
|
|
293
|
+
const caption = msg.caption ?? '';
|
|
294
|
+
let content;
|
|
295
|
+
let photoPath;
|
|
296
|
+
try {
|
|
297
|
+
photoPath = await this.downloadPhoto(photo.file_id, msg.message_id);
|
|
298
|
+
content = caption ? `[image:${photoPath}] ${caption}` : `[image:${photoPath}]`;
|
|
299
|
+
}
|
|
300
|
+
catch (err) {
|
|
301
|
+
// Download failed — forward caption or placeholder so message isn't silently dropped
|
|
302
|
+
content = caption ? `[image:download-failed] ${caption}` : '[image:download-failed]';
|
|
303
|
+
console.error(`[lifeline] Failed to download photo: ${err}`);
|
|
304
|
+
}
|
|
305
|
+
if (this.supervisor.healthy) {
|
|
306
|
+
const forwarded = await this.forwardToServer(topicId, content, msg);
|
|
307
|
+
if (forwarded) {
|
|
308
|
+
await this.sendToTopic(topicId, '✓ Delivered');
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// Queue the photo message (server down or forward failed)
|
|
313
|
+
this.queue.enqueue({
|
|
314
|
+
id: `tg-${msg.message_id}`,
|
|
315
|
+
topicId,
|
|
316
|
+
text: content,
|
|
317
|
+
fromUserId: msg.from.id,
|
|
318
|
+
fromUsername: msg.from.username,
|
|
319
|
+
fromFirstName: msg.from.first_name,
|
|
320
|
+
timestamp: new Date(msg.date * 1000).toISOString(),
|
|
321
|
+
photoPath,
|
|
322
|
+
});
|
|
323
|
+
if (this.supervisor.healthy) {
|
|
324
|
+
await this.sendToTopic(topicId, `Server is restarting. Your photo has been queued (${this.queue.length} in queue). It will be delivered when the server recovers.`);
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
await this.sendToTopic(topicId, `Server is temporarily down. Your photo has been queued (${this.queue.length} in queue). It will be delivered when the server recovers.`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Download a photo from Telegram and save it to the state directory.
|
|
332
|
+
*/
|
|
333
|
+
async downloadPhoto(fileId, messageId) {
|
|
334
|
+
// Get file path from Telegram
|
|
335
|
+
const infoRes = await fetch(`https://api.telegram.org/bot${this.config.token}/getFile?file_id=${encodeURIComponent(fileId)}`);
|
|
336
|
+
if (!infoRes.ok)
|
|
337
|
+
throw new Error(`getFile failed: ${infoRes.status}`);
|
|
338
|
+
const infoData = await infoRes.json();
|
|
339
|
+
if (!infoData.ok || !infoData.result?.file_path)
|
|
340
|
+
throw new Error('getFile returned no path');
|
|
341
|
+
const filePath = infoData.result.file_path;
|
|
342
|
+
const photoDir = path.join(this.projectConfig.stateDir, 'telegram-images');
|
|
343
|
+
fs.mkdirSync(photoDir, { recursive: true });
|
|
344
|
+
const filename = `photo-${Date.now()}-${messageId}.jpg`;
|
|
345
|
+
const localPath = path.join(photoDir, filename);
|
|
346
|
+
const fileRes = await fetch(`https://api.telegram.org/file/bot${this.config.token}/${filePath}`);
|
|
347
|
+
if (!fileRes.ok)
|
|
348
|
+
throw new Error(`File download failed: ${fileRes.status}`);
|
|
349
|
+
const buf = Buffer.from(await fileRes.arrayBuffer());
|
|
350
|
+
fs.writeFileSync(localPath, buf);
|
|
351
|
+
return localPath;
|
|
352
|
+
}
|
|
279
353
|
/**
|
|
280
354
|
* Forward a message to the Instar server's Telegram webhook.
|
|
281
355
|
*/
|