instar 0.7.14 → 0.7.15

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.
@@ -656,8 +656,16 @@ Adapt the tone and examples to the agent's personality and role. Keep it warm an
656
656
  After the server is running and the greeting is sent:
657
657
 
658
658
  > "All done! [Agent name] just messaged you in the Lifeline topic on Telegram. From here on, that's your primary channel — just talk to your agent there."
659
+
660
+ Then explain the connectivity requirement clearly:
661
+
662
+ > "One important thing to know: your agent runs on this computer. As long as it's on and awake, your agent is reachable via Telegram."
659
663
  >
660
- > "As long as your computer is running the Instar server, your agent is available."
664
+ > "If your computer goes to sleep or shuts down, Telegram messages will queue up. Your agent will pick them up when it wakes back up."
665
+ >
666
+ > "For always-on access, some users keep a dedicated machine running — but it's not required to get started."
667
+
668
+ Keep it matter-of-fact, not alarming. The user should understand the tradeoff without feeling like they need a server farm to use this.
661
669
 
662
670
  **Do NOT present a list of CLI commands or next steps.** The setup wizard's job is done. The user's next action is opening Telegram and replying to their agent.
663
671
 
@@ -0,0 +1,11 @@
1
+ > Why do I have a folder named ".vercel" in my project?
2
+ The ".vercel" folder is created when you link a directory to a Vercel project.
3
+
4
+ > What does the "project.json" file contain?
5
+ The "project.json" file contains:
6
+ - The ID of the Vercel project that you linked ("projectId")
7
+ - The ID of the user or team your Vercel project is owned by ("orgId")
8
+
9
+ > Should I commit the ".vercel" folder?
10
+ No, you should not share the ".vercel" folder with anyone.
11
+ Upon creation, it will be automatically added to your ".gitignore" file.
@@ -0,0 +1 @@
1
+ {"projectId":"prj_evM5LcItYL3IAmw8zNvEPGrHeaya","orgId":"team_dHctwIDcV3X9ydapQlCPHFGI","projectName":"claude-agent-kit"}
package/dist/cli.js CHANGED
File without changes
@@ -462,6 +462,19 @@ async function runClassicSetup() {
462
462
  const parsed = JSON.parse(topicResult);
463
463
  if (parsed.ok && parsed.result?.message_thread_id) {
464
464
  lifelineThreadId = parsed.result.message_thread_id;
465
+ // Persist lifelineTopicId back to config.json
466
+ try {
467
+ const configPath = path.join(stateDir, 'config.json');
468
+ const rawConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
469
+ const tgEntry = rawConfig.messaging?.find((m) => m.type === 'telegram');
470
+ if (tgEntry?.config) {
471
+ tgEntry.config.lifelineTopicId = lifelineThreadId;
472
+ const tmpPath = `${configPath}.${process.pid}.tmp`;
473
+ fs.writeFileSync(tmpPath, JSON.stringify(rawConfig, null, 2));
474
+ fs.renameSync(tmpPath, configPath);
475
+ }
476
+ }
477
+ catch { /* non-fatal */ }
465
478
  }
466
479
  }
467
480
  catch {
@@ -480,6 +493,8 @@ async function runClassicSetup() {
480
493
  '- I can proactively create topics when something needs attention',
481
494
  '- Lifeline is always here for anything that doesn\'t fit elsewhere',
482
495
  '',
496
+ '_I run on your computer, so I\'m available as long as it\'s on and awake. If it sleeps, I\'ll pick up messages when it wakes back up._',
497
+ '',
483
498
  'What should we work on first?',
484
499
  ].join('\n');
485
500
  const payload = {
@@ -504,7 +519,10 @@ async function runClassicSetup() {
504
519
  const topicNote = lifelineThreadId ? ' in the Lifeline topic' : '';
505
520
  console.log(pc.bold(` All done! ${projectName} just messaged you${topicNote} on Telegram.`));
506
521
  console.log(pc.dim(' That\'s your primary channel from here on — no terminal needed.'));
507
- console.log(pc.dim(' As long as your computer is running the Instar server, your agent is available.'));
522
+ console.log();
523
+ console.log(pc.dim(' Your agent runs on this computer. As long as it\'s on and awake,'));
524
+ console.log(pc.dim(' your agent is reachable via Telegram. If your computer sleeps or'));
525
+ console.log(pc.dim(' shuts down, messages will queue and be picked up when it wakes.'));
508
526
  }
509
527
  else {
510
528
  console.log();
@@ -29,6 +29,7 @@ export declare class TelegramLifeline {
29
29
  private offsetPath;
30
30
  private stopHeartbeat;
31
31
  private replayInterval;
32
+ private lifelineTopicId;
32
33
  constructor(projectDir?: string);
33
34
  /**
34
35
  * Start the lifeline — begins Telegram polling and server supervision.
@@ -43,6 +44,14 @@ export declare class TelegramLifeline {
43
44
  private handleLifelineCommand;
44
45
  private replayQueue;
45
46
  private notifyServerDown;
47
+ /**
48
+ * Ensure the Lifeline topic exists. Recreates if deleted.
49
+ */
50
+ private ensureLifelineTopic;
51
+ /**
52
+ * Persist the Lifeline topic ID to config.json.
53
+ */
54
+ private persistLifelineTopicId;
46
55
  private sendToTopic;
47
56
  private getUpdates;
48
57
  private apiCall;
@@ -36,6 +36,7 @@ export class TelegramLifeline {
36
36
  offsetPath;
37
37
  stopHeartbeat = null;
38
38
  replayInterval = null;
39
+ lifelineTopicId = null;
39
40
  constructor(projectDir) {
40
41
  this.projectConfig = loadConfig(projectDir);
41
42
  ensureStateDir(this.projectConfig.stateDir);
@@ -81,6 +82,11 @@ export class TelegramLifeline {
81
82
  }
82
83
  catch { /* non-critical */ }
83
84
  this.stopHeartbeat = startHeartbeat(`${this.projectConfig.projectName}-lifeline`);
85
+ // Ensure Lifeline topic exists (auto-recreate if deleted)
86
+ this.lifelineTopicId = await this.ensureLifelineTopic();
87
+ if (this.lifelineTopicId) {
88
+ console.log(pc.green(` Lifeline topic: ${this.lifelineTopicId}`));
89
+ }
84
90
  // Start server supervisor
85
91
  const serverStarted = await this.supervisor.start();
86
92
  if (serverStarted) {
@@ -300,8 +306,82 @@ export class TelegramLifeline {
300
306
  }
301
307
  // ── Notifications ─────────────────────────────────────────
302
308
  async notifyServerDown(reason) {
303
- // Send to General topic (1) since we don't know which topic the user is watching
304
- await this.sendToTopic(1, `Server went down: ${reason}\n\nYour messages will be queued until recovery. Use /lifeline status to check.`).catch(() => { });
309
+ // Send to Lifeline topic if available, otherwise General
310
+ const topicId = this.lifelineTopicId ?? 1;
311
+ await this.sendToTopic(topicId, `Server went down: ${reason}\n\nYour messages will be queued until recovery. Use /lifeline status to check.`).catch(() => { });
312
+ }
313
+ // ── Lifeline Topic ──────────────────────────────────────────
314
+ /**
315
+ * Ensure the Lifeline topic exists. Recreates if deleted.
316
+ */
317
+ async ensureLifelineTopic() {
318
+ const existingId = this.config.lifelineTopicId;
319
+ if (existingId) {
320
+ // Verify it still exists
321
+ try {
322
+ await this.apiCall('sendMessage', {
323
+ chat_id: this.config.chatId,
324
+ message_thread_id: existingId,
325
+ text: '🟢 Lifeline connected.',
326
+ });
327
+ return existingId;
328
+ }
329
+ catch (err) {
330
+ const errStr = String(err);
331
+ if (errStr.includes('thread not found') || errStr.includes('TOPIC_DELETED') ||
332
+ errStr.includes('TOPIC_CLOSED') || errStr.includes('not found')) {
333
+ console.log(`[Lifeline] Topic ${existingId} was deleted — recreating`);
334
+ }
335
+ else {
336
+ // Non-fatal error (network etc.) — assume it still exists
337
+ console.warn(`[Lifeline] Topic check failed (non-fatal): ${err}`);
338
+ return existingId;
339
+ }
340
+ }
341
+ }
342
+ // Create or recreate
343
+ try {
344
+ const result = await this.apiCall('createForumTopic', {
345
+ chat_id: this.config.chatId,
346
+ name: 'Lifeline',
347
+ icon_color: 9367192, // green
348
+ });
349
+ const topicId = result.message_thread_id;
350
+ this.config.lifelineTopicId = topicId;
351
+ this.persistLifelineTopicId(topicId);
352
+ console.log(`[Lifeline] ${existingId ? 'Recreated' : 'Created'} Lifeline topic: ${topicId}`);
353
+ // Send welcome message in new topic
354
+ await this.sendToTopic(topicId, '🟢 Lifeline connected. This topic is always available — even when the server is down.');
355
+ return topicId;
356
+ }
357
+ catch (err) {
358
+ console.error(`[Lifeline] Failed to create Lifeline topic: ${err}`);
359
+ return null;
360
+ }
361
+ }
362
+ /**
363
+ * Persist the Lifeline topic ID to config.json.
364
+ */
365
+ persistLifelineTopicId(topicId) {
366
+ try {
367
+ const configPath = path.join(this.projectConfig.projectDir, '.instar', 'config.json');
368
+ if (fs.existsSync(configPath)) {
369
+ const raw = fs.readFileSync(configPath, 'utf-8');
370
+ const config = JSON.parse(raw);
371
+ if (Array.isArray(config.messaging)) {
372
+ const entry = config.messaging.find((m) => m.type === 'telegram');
373
+ if (entry?.config) {
374
+ entry.config.lifelineTopicId = topicId;
375
+ const tmpPath = `${configPath}.${process.pid}.tmp`;
376
+ fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2));
377
+ fs.renameSync(tmpPath, configPath);
378
+ }
379
+ }
380
+ }
381
+ }
382
+ catch (err) {
383
+ console.warn(`[Lifeline] Failed to persist lifelineTopicId: ${err}`);
384
+ }
305
385
  }
306
386
  // ── Telegram API ──────────────────────────────────────────
307
387
  async sendToTopic(topicId, text) {
@@ -22,6 +22,8 @@ export interface TelegramConfig {
22
22
  voiceProvider?: string;
23
23
  /** Stall detection timeout in minutes (default: 5, 0 to disable) */
24
24
  stallTimeoutMinutes?: number;
25
+ /** Lifeline topic thread ID — the always-available channel. Auto-recreated if deleted. */
26
+ lifelineTopicId?: number;
25
27
  }
26
28
  export interface SendResult {
27
29
  /** Telegram message ID */
@@ -100,6 +102,19 @@ export declare class TelegramAdapter implements MessagingAdapter {
100
102
  topicId: number;
101
103
  name: string;
102
104
  }>;
105
+ /**
106
+ * Get the Lifeline topic ID (if configured).
107
+ */
108
+ getLifelineTopicId(): number | undefined;
109
+ /**
110
+ * Ensure the Lifeline topic exists. If it was deleted, recreate it.
111
+ * Called on startup and can be called periodically.
112
+ */
113
+ ensureLifelineTopic(): Promise<number | null>;
114
+ /**
115
+ * Persist the Lifeline topic ID back to config.json so it survives restarts.
116
+ */
117
+ private persistLifelineTopicId;
103
118
  /**
104
119
  * Close a forum topic.
105
120
  */
@@ -87,6 +87,8 @@ export class TelegramAdapter {
87
87
  this.polling = true;
88
88
  this.startedAt = new Date();
89
89
  this.consecutivePollErrors = 0;
90
+ // Ensure Lifeline topic exists (auto-recreate if deleted)
91
+ await this.ensureLifelineTopic();
90
92
  console.log(`[telegram] Starting long-polling...`);
91
93
  this.poll();
92
94
  // Start stall detection if configured
@@ -180,6 +182,97 @@ export class TelegramAdapter {
180
182
  console.log(`[telegram] Created forum topic: "${name}" (ID: ${result.message_thread_id})`);
181
183
  return { topicId: result.message_thread_id, name: result.name };
182
184
  }
185
+ /**
186
+ * Get the Lifeline topic ID (if configured).
187
+ */
188
+ getLifelineTopicId() {
189
+ return this.config.lifelineTopicId;
190
+ }
191
+ /**
192
+ * Ensure the Lifeline topic exists. If it was deleted, recreate it.
193
+ * Called on startup and can be called periodically.
194
+ */
195
+ async ensureLifelineTopic() {
196
+ if (!this.config.lifelineTopicId) {
197
+ // No lifeline topic configured — create one
198
+ try {
199
+ const topic = await this.createForumTopic('Lifeline', 9367192); // Green
200
+ this.config.lifelineTopicId = topic.topicId;
201
+ this.persistLifelineTopicId(topic.topicId);
202
+ console.log(`[telegram] Created Lifeline topic: ${topic.topicId}`);
203
+ return topic.topicId;
204
+ }
205
+ catch (err) {
206
+ console.error(`[telegram] Failed to create Lifeline topic: ${err}`);
207
+ return null;
208
+ }
209
+ }
210
+ // Lifeline topic ID exists — verify it's still valid by sending a test
211
+ try {
212
+ await this.apiCall('sendMessage', {
213
+ chat_id: this.config.chatId,
214
+ message_thread_id: this.config.lifelineTopicId,
215
+ text: '🟢 Lifeline connected.',
216
+ });
217
+ console.log(`[telegram] Lifeline topic verified: ${this.config.lifelineTopicId}`);
218
+ return this.config.lifelineTopicId;
219
+ }
220
+ catch (err) {
221
+ const errStr = String(err);
222
+ // Topic was deleted — "message thread not found" or "TOPIC_CLOSED" or similar
223
+ if (errStr.includes('thread not found') || errStr.includes('TOPIC_DELETED') ||
224
+ errStr.includes('TOPIC_CLOSED') || errStr.includes('not found')) {
225
+ console.log(`[telegram] Lifeline topic ${this.config.lifelineTopicId} was deleted — recreating`);
226
+ try {
227
+ const topic = await this.createForumTopic('Lifeline', 9367192);
228
+ this.config.lifelineTopicId = topic.topicId;
229
+ this.persistLifelineTopicId(topic.topicId);
230
+ console.log(`[telegram] Recreated Lifeline topic: ${topic.topicId}`);
231
+ return topic.topicId;
232
+ }
233
+ catch (recreateErr) {
234
+ console.error(`[telegram] Failed to recreate Lifeline topic: ${recreateErr}`);
235
+ return null;
236
+ }
237
+ }
238
+ // Some other error (network, etc.) — don't recreate, just warn
239
+ console.warn(`[telegram] Lifeline topic check failed (non-fatal): ${err}`);
240
+ return this.config.lifelineTopicId;
241
+ }
242
+ }
243
+ /**
244
+ * Persist the Lifeline topic ID back to config.json so it survives restarts.
245
+ */
246
+ persistLifelineTopicId(topicId) {
247
+ try {
248
+ // Find config.json in state dir's parent (stateDir is .instar/state or .instar)
249
+ const candidates = [
250
+ path.join(this.stateDir, '..', 'config.json'),
251
+ path.join(this.stateDir, 'config.json'),
252
+ ];
253
+ for (const configPath of candidates) {
254
+ if (fs.existsSync(configPath)) {
255
+ const raw = fs.readFileSync(configPath, 'utf-8');
256
+ const config = JSON.parse(raw);
257
+ // Find the telegram messaging config and update it
258
+ if (Array.isArray(config.messaging)) {
259
+ const telegramEntry = config.messaging.find((m) => m.type === 'telegram');
260
+ if (telegramEntry?.config) {
261
+ telegramEntry.config.lifelineTopicId = topicId;
262
+ const tmpPath = `${configPath}.${process.pid}.tmp`;
263
+ fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2));
264
+ fs.renameSync(tmpPath, configPath);
265
+ console.log(`[telegram] Saved lifelineTopicId=${topicId} to config`);
266
+ return;
267
+ }
268
+ }
269
+ }
270
+ }
271
+ }
272
+ catch (err) {
273
+ console.warn(`[telegram] Failed to persist lifelineTopicId: ${err}`);
274
+ }
275
+ }
183
276
  /**
184
277
  * Close a forum topic.
185
278
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "0.7.14",
3
+ "version": "0.7.15",
4
4
  "description": "Persistent autonomy infrastructure for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",