instar 0.8.3 → 0.8.4

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.
@@ -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
@@ -27,6 +27,8 @@ export declare class ServerSupervisor extends EventEmitter {
27
27
  private lastHealthy;
28
28
  private startupGraceMs;
29
29
  private spawnedAt;
30
+ private retryCooldownMs;
31
+ private maxRetriesExhaustedAt;
30
32
  constructor(options: {
31
33
  projectDir: string;
32
34
  projectName: string;
@@ -53,6 +55,8 @@ export declare class ServerSupervisor extends EventEmitter {
53
55
  restartAttempts: number;
54
56
  lastHealthy: number;
55
57
  serverSession: string;
58
+ coolingDown: boolean;
59
+ cooldownRemainingMs: number;
56
60
  };
57
61
  private spawnServer;
58
62
  private isServerSessionAlive;
@@ -24,6 +24,8 @@ export class ServerSupervisor extends EventEmitter {
24
24
  lastHealthy = 0;
25
25
  startupGraceMs = 20_000; // 20 seconds grace period after spawn before health checks
26
26
  spawnedAt = 0;
27
+ retryCooldownMs = 5 * 60_000; // 5 minutes cooldown after max retries exhausted
28
+ maxRetriesExhaustedAt = 0;
27
29
  constructor(options) {
28
30
  super();
29
31
  this.projectDir = options.projectDir;
@@ -84,12 +86,18 @@ export class ServerSupervisor extends EventEmitter {
84
86
  * Get supervisor status.
85
87
  */
86
88
  getStatus() {
89
+ const coolingDown = this.maxRetriesExhaustedAt > 0;
90
+ const cooldownRemainingMs = coolingDown
91
+ ? Math.max(0, this.retryCooldownMs - (Date.now() - this.maxRetriesExhaustedAt))
92
+ : 0;
87
93
  return {
88
94
  running: this.isRunning,
89
95
  healthy: this.healthy,
90
96
  restartAttempts: this.restartAttempts,
91
97
  lastHealthy: this.lastHealthy,
92
98
  serverSession: this.serverSessionName,
99
+ coolingDown,
100
+ cooldownRemainingMs,
93
101
  };
94
102
  }
95
103
  spawnServer() {
@@ -188,28 +196,41 @@ export class ServerSupervisor extends EventEmitter {
188
196
  this.isRunning = false;
189
197
  this.emit('serverDown', 'Health check failed');
190
198
  }
199
+ // After max retries exhausted, wait for cooldown before trying again.
200
+ // This prevents permanent death from transient issues (port conflicts, etc.)
201
+ if (this.restartAttempts >= this.maxRestartAttempts) {
202
+ if (this.maxRetriesExhaustedAt === 0) {
203
+ this.maxRetriesExhaustedAt = Date.now();
204
+ console.error(`[Supervisor] Max restart attempts (${this.maxRestartAttempts}) reached. Cooling down for ${this.retryCooldownMs / 1000}s before retrying.`);
205
+ }
206
+ if ((Date.now() - this.maxRetriesExhaustedAt) >= this.retryCooldownMs) {
207
+ // Cooldown elapsed — reset and try again
208
+ console.log(`[Supervisor] Cooldown elapsed. Resetting restart counter.`);
209
+ this.restartAttempts = 0;
210
+ this.maxRetriesExhaustedAt = 0;
211
+ // Fall through to the restart logic below
212
+ }
213
+ else {
214
+ return; // Still cooling down
215
+ }
216
+ }
191
217
  // Auto-restart with backoff
192
- if (this.restartAttempts < this.maxRestartAttempts) {
193
- this.restartAttempts++;
194
- const delay = this.restartBackoffMs * Math.pow(2, this.restartAttempts - 1);
195
- console.log(`[Supervisor] Server unhealthy. Restart attempt ${this.restartAttempts}/${this.maxRestartAttempts} in ${delay}ms`);
196
- this.emit('serverRestarting', this.restartAttempts);
197
- setTimeout(() => {
198
- // Kill stale session if it exists
199
- if (this.tmuxPath && this.isServerSessionAlive()) {
200
- try {
201
- execFileSync(this.tmuxPath, ['kill-session', '-t', `=${this.serverSessionName}`], {
202
- stdio: 'ignore',
203
- });
204
- }
205
- catch { /* ignore */ }
218
+ this.restartAttempts++;
219
+ const delay = this.restartBackoffMs * Math.pow(2, this.restartAttempts - 1);
220
+ console.log(`[Supervisor] Server unhealthy. Restart attempt ${this.restartAttempts}/${this.maxRestartAttempts} in ${delay}ms`);
221
+ this.emit('serverRestarting', this.restartAttempts);
222
+ setTimeout(() => {
223
+ // Kill stale session if it exists
224
+ if (this.tmuxPath && this.isServerSessionAlive()) {
225
+ try {
226
+ execFileSync(this.tmuxPath, ['kill-session', '-t', `=${this.serverSessionName}`], {
227
+ stdio: 'ignore',
228
+ });
206
229
  }
207
- this.spawnServer();
208
- }, delay);
209
- }
210
- else {
211
- console.error(`[Supervisor] Max restart attempts (${this.maxRestartAttempts}) reached. Server down.`);
212
- }
230
+ catch { /* ignore */ }
231
+ }
232
+ this.spawnServer();
233
+ }, delay);
213
234
  }
214
235
  }
215
236
  //# sourceMappingURL=ServerSupervisor.js.map
@@ -24,7 +24,9 @@ import path from 'node:path';
24
24
  import pc from 'picocolors';
25
25
  import { loadConfig, ensureStateDir } from '../core/Config.js';
26
26
  import { registerPort, unregisterPort, startHeartbeat } from '../core/PortRegistry.js';
27
- import { installAutoStart } from '../commands/setup.js';
27
+ // setup.ts uses @inquirer/prompts which requires Node 20.12+
28
+ // Dynamic import to avoid breaking the lifeline on older Node versions
29
+ // import { installAutoStart } from '../commands/setup.js';
28
30
  import { MessageQueue } from './MessageQueue.js';
29
31
  import { ServerSupervisor } from './ServerSupervisor.js';
30
32
  /**
@@ -172,6 +174,8 @@ export class TelegramLifeline {
172
174
  // The user must always be able to reach their agent remotely — this is non-negotiable.
173
175
  try {
174
176
  if (!this.isAutostartInstalled()) {
177
+ // Dynamic import — setup.ts uses @inquirer/prompts which requires Node 20.12+
178
+ const { installAutoStart } = await import('../commands/setup.js');
175
179
  const installed = installAutoStart(this.projectConfig.projectName, this.projectConfig.projectDir, true);
176
180
  if (installed) {
177
181
  console.log(pc.green(` Auto-start self-healed: installed ${process.platform === 'darwin' ? 'LaunchAgent' : 'systemd service'}`));
@@ -310,9 +314,13 @@ export class TelegramLifeline {
310
314
  if (cmd === '/lifeline' || cmd === '/lifeline status') {
311
315
  const status = this.supervisor.getStatus();
312
316
  const queueSize = this.queue.length;
317
+ let serverLine = status.healthy ? '● healthy' : status.running ? '○ unhealthy' : '✗ down';
318
+ if (status.coolingDown) {
319
+ serverLine += ` (cooldown: ${Math.ceil(status.cooldownRemainingMs / 1000)}s)`;
320
+ }
313
321
  const lines = [
314
322
  `Lifeline Status:`,
315
- ` Server: ${status.healthy ? '● healthy' : status.running ? '○ unhealthy' : '✗ down'}`,
323
+ ` Server: ${serverLine}`,
316
324
  ` Restart attempts: ${status.restartAttempts}`,
317
325
  ` Queued messages: ${queueSize}`,
318
326
  ` Last healthy: ${status.lastHealthy ? new Date(status.lastHealthy).toISOString().slice(11, 19) : 'never'}`,
@@ -1438,7 +1438,19 @@ export function createRoutes(ctx) {
1438
1438
  }
1439
1439
  else {
1440
1440
  // No session or session dead — auto-spawn a new one
1441
- const topicName = targetSession || `topic-${topicId}`;
1441
+ // Use topic name from registry, NOT the tmux session name.
1442
+ // tmux names include the project prefix (e.g., "ai-guy-lifeline"), and
1443
+ // spawnInteractiveSession prepends it again → cascading names.
1444
+ let topicName = `topic-${topicId}`;
1445
+ try {
1446
+ if (fs.existsSync(registryPath)) {
1447
+ const reg = JSON.parse(fs.readFileSync(registryPath, 'utf-8'));
1448
+ const stored = reg.topicToName?.[String(topicId)];
1449
+ if (stored)
1450
+ topicName = stored;
1451
+ }
1452
+ }
1453
+ catch { /* fall through to default */ }
1442
1454
  console.log(`[telegram-forward] No live session for topic ${topicId}, spawning "${topicName}"...`);
1443
1455
  const contextLines = [
1444
1456
  `This session was auto-created for Telegram topic ${topicId}.`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "Persistent autonomy infrastructure for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",