instar 0.8.2 → 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.
- package/.vercel/README.txt +11 -0
- package/.vercel/project.json +1 -0
- package/dist/cli.js +0 -0
- package/dist/lifeline/ServerSupervisor.d.ts +4 -0
- package/dist/lifeline/ServerSupervisor.js +41 -20
- package/dist/lifeline/TelegramLifeline.js +10 -2
- package/dist/server/routes.js +13 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
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: ${
|
|
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'}`,
|
package/dist/server/routes.js
CHANGED
|
@@ -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
|
-
|
|
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}.`,
|