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 || !msg.text)
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
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "0.8.6",
3
+ "version": "0.8.7",
4
4
  "description": "Persistent autonomy infrastructure for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",