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.
- package/.claude/skills/setup-wizard/skill.md +9 -1
- package/.vercel/README.txt +11 -0
- package/.vercel/project.json +1 -0
- package/dist/cli.js +0 -0
- package/dist/commands/setup.js +19 -1
- package/dist/lifeline/TelegramLifeline.d.ts +9 -0
- package/dist/lifeline/TelegramLifeline.js +82 -2
- package/dist/messaging/TelegramAdapter.d.ts +15 -0
- package/dist/messaging/TelegramAdapter.js +93 -0
- package/package.json +1 -1
|
@@ -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
|
-
> "
|
|
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
|
package/dist/commands/setup.js
CHANGED
|
@@ -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(
|
|
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
|
|
304
|
-
|
|
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
|
*/
|