instar 0.7.44 → 0.7.46
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/commands/server.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { execFileSync } from 'node:child_process';
|
|
11
11
|
import fs from 'node:fs';
|
|
12
|
+
import os from 'node:os';
|
|
12
13
|
import path from 'node:path';
|
|
13
14
|
import pc from 'picocolors';
|
|
14
15
|
import { loadConfig, ensureStateDir, detectTmuxPath } from '../core/Config.js';
|
|
@@ -35,6 +36,24 @@ import { QuotaTracker } from '../monitoring/QuotaTracker.js';
|
|
|
35
36
|
import { AccountSwitcher } from '../monitoring/AccountSwitcher.js';
|
|
36
37
|
import { QuotaNotifier } from '../monitoring/QuotaNotifier.js';
|
|
37
38
|
import { classifySessionDeath } from '../monitoring/QuotaExhaustionDetector.js';
|
|
39
|
+
import { installAutoStart } from './setup.js';
|
|
40
|
+
/**
|
|
41
|
+
* Check if autostart is installed for this project.
|
|
42
|
+
* Extracted from the CLI `autostart status` handler for programmatic use.
|
|
43
|
+
*/
|
|
44
|
+
function isAutostartInstalled(projectName) {
|
|
45
|
+
if (process.platform === 'darwin') {
|
|
46
|
+
const label = `ai.instar.${projectName}`;
|
|
47
|
+
const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', `${label}.plist`);
|
|
48
|
+
return fs.existsSync(plistPath);
|
|
49
|
+
}
|
|
50
|
+
else if (process.platform === 'linux') {
|
|
51
|
+
const serviceName = `instar-${projectName}.service`;
|
|
52
|
+
const servicePath = path.join(os.homedir(), '.config', 'systemd', 'user', serviceName);
|
|
53
|
+
return fs.existsSync(servicePath);
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
38
57
|
/**
|
|
39
58
|
* Respawn a session for a topic, including thread history in the bootstrap.
|
|
40
59
|
* This prevents "thread drift" where respawned sessions lose context.
|
|
@@ -794,6 +813,27 @@ export async function startServer(options) {
|
|
|
794
813
|
console.log(pc.yellow(` Server running locally without tunnel. Fix tunnel config and restart.`));
|
|
795
814
|
}
|
|
796
815
|
}
|
|
816
|
+
// Self-healing: ensure autostart is installed so the server always restarts
|
|
817
|
+
// This is a non-negotiable requirement — the user must always be able to reach their agent remotely.
|
|
818
|
+
// If autostart isn't installed, install it silently. The agent should never require human intervention
|
|
819
|
+
// to ensure its own resilience.
|
|
820
|
+
try {
|
|
821
|
+
const hasTelegram = !!telegram;
|
|
822
|
+
const autostartInstalled = isAutostartInstalled(config.projectName);
|
|
823
|
+
if (!autostartInstalled) {
|
|
824
|
+
const installed = installAutoStart(config.projectName, config.projectDir, hasTelegram);
|
|
825
|
+
if (installed) {
|
|
826
|
+
console.log(pc.green(` Auto-start self-healed: installed ${process.platform === 'darwin' ? 'LaunchAgent' : 'systemd service'}`));
|
|
827
|
+
}
|
|
828
|
+
else {
|
|
829
|
+
console.log(pc.yellow(` Auto-start not available on ${process.platform}`));
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
catch (err) {
|
|
834
|
+
// Non-critical — don't crash the server over autostart
|
|
835
|
+
console.error(` Auto-start check failed: ${err instanceof Error ? err.message : err}`);
|
|
836
|
+
}
|
|
797
837
|
// Graceful shutdown
|
|
798
838
|
const shutdown = async () => {
|
|
799
839
|
console.log('\nShutting down...');
|
package/dist/commands/setup.js
CHANGED
|
@@ -813,10 +813,7 @@ ${argsXml}
|
|
|
813
813
|
<key>RunAtLoad</key>
|
|
814
814
|
<true/>
|
|
815
815
|
<key>KeepAlive</key>
|
|
816
|
-
<
|
|
817
|
-
<key>SuccessfulExit</key>
|
|
818
|
-
<false/>
|
|
819
|
-
</dict>
|
|
816
|
+
<true/>
|
|
820
817
|
<key>StandardOutPath</key>
|
|
821
818
|
<string>${escapeXml(path.join(logDir, `${command}-launchd.log`))}</string>
|
|
822
819
|
<key>StandardErrorPath</key>
|
|
@@ -97,7 +97,7 @@ export class ServerSupervisor extends EventEmitter {
|
|
|
97
97
|
return false;
|
|
98
98
|
try {
|
|
99
99
|
// Get the instar CLI path
|
|
100
|
-
const cliPath = new URL('
|
|
100
|
+
const cliPath = new URL('../cli.js', import.meta.url).pathname;
|
|
101
101
|
// --no-telegram: lifeline owns the Telegram connection, server should not poll
|
|
102
102
|
const nodeCmd = ['node', cliPath, 'server', 'start', '--foreground', '--no-telegram']
|
|
103
103
|
.map(arg => `'${arg.replace(/'/g, "'\\''")}'`)
|
|
@@ -45,6 +45,10 @@ export declare class TelegramLifeline {
|
|
|
45
45
|
private handleLifelineCommand;
|
|
46
46
|
private replayQueue;
|
|
47
47
|
private notifyServerDown;
|
|
48
|
+
/**
|
|
49
|
+
* Check if OS-level autostart is installed for this project.
|
|
50
|
+
*/
|
|
51
|
+
private isAutostartInstalled;
|
|
48
52
|
/**
|
|
49
53
|
* Ensure the Lifeline topic exists. Recreates if deleted.
|
|
50
54
|
*/
|
|
@@ -19,10 +19,12 @@
|
|
|
19
19
|
* the full server crashes, runs out of memory, or gets stuck.
|
|
20
20
|
*/
|
|
21
21
|
import fs from 'node:fs';
|
|
22
|
+
import os from 'node:os';
|
|
22
23
|
import path from 'node:path';
|
|
23
24
|
import pc from 'picocolors';
|
|
24
25
|
import { loadConfig, ensureStateDir } from '../core/Config.js';
|
|
25
26
|
import { registerPort, unregisterPort, startHeartbeat } from '../core/PortRegistry.js';
|
|
27
|
+
import { installAutoStart } from '../commands/setup.js';
|
|
26
28
|
import { MessageQueue } from './MessageQueue.js';
|
|
27
29
|
import { ServerSupervisor } from './ServerSupervisor.js';
|
|
28
30
|
/**
|
|
@@ -127,7 +129,7 @@ export class TelegramLifeline {
|
|
|
127
129
|
// Acquire exclusive lock — prevent multiple lifeline instances
|
|
128
130
|
if (!acquireLockFile(this.lockPath)) {
|
|
129
131
|
console.error(pc.red('[Lifeline] Another lifeline instance is already running. Exiting.'));
|
|
130
|
-
process.exit(0); // Clean exit — launchd
|
|
132
|
+
process.exit(0); // Clean exit — launchd will restart after ThrottleInterval, acting as a watchdog
|
|
131
133
|
}
|
|
132
134
|
// Register in port registry (lifeline owns the port claim)
|
|
133
135
|
try {
|
|
@@ -166,6 +168,19 @@ export class TelegramLifeline {
|
|
|
166
168
|
setTimeout(() => this.replayQueue(), 5000); // Wait for server to fully start
|
|
167
169
|
}
|
|
168
170
|
}
|
|
171
|
+
// Self-healing: ensure autostart is installed so the lifeline persists across reboots.
|
|
172
|
+
// The user must always be able to reach their agent remotely — this is non-negotiable.
|
|
173
|
+
try {
|
|
174
|
+
if (!this.isAutostartInstalled()) {
|
|
175
|
+
const installed = installAutoStart(this.projectConfig.projectName, this.projectConfig.projectDir, true);
|
|
176
|
+
if (installed) {
|
|
177
|
+
console.log(pc.green(` Auto-start self-healed: installed ${process.platform === 'darwin' ? 'LaunchAgent' : 'systemd service'}`));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// Non-critical — don't crash the lifeline over autostart
|
|
183
|
+
}
|
|
169
184
|
// Graceful shutdown
|
|
170
185
|
const shutdown = async () => {
|
|
171
186
|
console.log('\nLifeline shutting down...');
|
|
@@ -366,6 +381,22 @@ export class TelegramLifeline {
|
|
|
366
381
|
await this.sendToTopic(topicId, `Server went down: ${reason}\n\nYour messages will be queued until recovery. Use /lifeline status to check.`).catch(() => { });
|
|
367
382
|
}
|
|
368
383
|
// ── Lifeline Topic ──────────────────────────────────────────
|
|
384
|
+
/**
|
|
385
|
+
* Check if OS-level autostart is installed for this project.
|
|
386
|
+
*/
|
|
387
|
+
isAutostartInstalled() {
|
|
388
|
+
if (process.platform === 'darwin') {
|
|
389
|
+
const label = `ai.instar.${this.projectConfig.projectName}`;
|
|
390
|
+
const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', `${label}.plist`);
|
|
391
|
+
return fs.existsSync(plistPath);
|
|
392
|
+
}
|
|
393
|
+
else if (process.platform === 'linux') {
|
|
394
|
+
const serviceName = `instar-${this.projectConfig.projectName}.service`;
|
|
395
|
+
const servicePath = path.join(os.homedir(), '.config', 'systemd', 'user', serviceName);
|
|
396
|
+
return fs.existsSync(servicePath);
|
|
397
|
+
}
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
369
400
|
/**
|
|
370
401
|
* Ensure the Lifeline topic exists. Recreates if deleted.
|
|
371
402
|
*/
|