@quangnv13/nonstop 1.0.14 → 1.0.16
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/README.md +38 -4
- package/README.vi.md +38 -4
- package/dist/bot.js +100 -7
- package/dist/config.js +14 -2
- package/dist/i18n.js +53 -45
- package/dist/index.js +24 -2
- package/dist/runtime-manager.js +111 -6
- package/dist/runtime-state.js +30 -0
- package/dist/runtime.js +187 -5
- package/dist/session-controls.js +3 -3
- package/dist/terminal.js +14 -1
- package/dist/ui.js +295 -101
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ const logger_js_1 = require("./logger.js");
|
|
|
6
6
|
const runtime_js_1 = require("./runtime.js");
|
|
7
7
|
const ui_js_1 = require("./ui.js");
|
|
8
8
|
const runtime_manager_js_1 = require("./runtime-manager.js");
|
|
9
|
+
const runtime_state_js_1 = require("./runtime-state.js");
|
|
9
10
|
async function main() {
|
|
10
11
|
(0, config_js_1.ensureEnvExampleFile)();
|
|
11
12
|
const args = new Set(process.argv.slice(2));
|
|
@@ -14,10 +15,11 @@ async function main() {
|
|
|
14
15
|
const config = (0, config_js_1.loadConfigFromDisk)();
|
|
15
16
|
(0, config_js_1.applyConfigToProcessEnv)(config);
|
|
16
17
|
if (isStop) {
|
|
18
|
+
(0, runtime_state_js_1.saveShouldRunState)(false);
|
|
17
19
|
const status = (0, runtime_manager_js_1.getRuntimeStatus)();
|
|
18
20
|
if (status.running && status.snapshot) {
|
|
19
21
|
try {
|
|
20
|
-
const msg = (0, runtime_manager_js_1.stopBackgroundRuntime)(status.snapshot);
|
|
22
|
+
const msg = (0, runtime_manager_js_1.stopBackgroundRuntime)(status.snapshot, config.language);
|
|
21
23
|
console.log(msg);
|
|
22
24
|
}
|
|
23
25
|
catch (error) {
|
|
@@ -26,7 +28,9 @@ async function main() {
|
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
30
|
else {
|
|
29
|
-
console.log(
|
|
31
|
+
console.log(config.language === 'vi'
|
|
32
|
+
? '⚠ Runtime nền của nonstop không đang chạy.'
|
|
33
|
+
: 'nonstop background runtime is not running.');
|
|
30
34
|
}
|
|
31
35
|
return;
|
|
32
36
|
}
|
|
@@ -39,6 +43,8 @@ async function main() {
|
|
|
39
43
|
process.exitCode = 1;
|
|
40
44
|
return;
|
|
41
45
|
}
|
|
46
|
+
(0, runtime_state_js_1.saveShouldRunState)(true);
|
|
47
|
+
void (0, runtime_manager_js_1.checkUpdateOnStartup)(true, config.language);
|
|
42
48
|
const runtime = new runtime_js_1.NonstopRuntime(config, 'background');
|
|
43
49
|
await runtime.startBot();
|
|
44
50
|
const shutdown = async () => {
|
|
@@ -64,6 +70,22 @@ async function main() {
|
|
|
64
70
|
});
|
|
65
71
|
return;
|
|
66
72
|
}
|
|
73
|
+
// Auto-restart if it was running but is not currently running (e.g. after system restart)
|
|
74
|
+
const status = (0, runtime_manager_js_1.getRuntimeStatus)();
|
|
75
|
+
if (!status.running && (0, runtime_state_js_1.loadShouldRunState)()) {
|
|
76
|
+
console.log(config.language === 'vi'
|
|
77
|
+
? '↻ Phát hiện trạng thái trước đó đang chạy. Đang tự khởi động lại runtime nền...'
|
|
78
|
+
: '↻ Detected previous running state. Auto-restarting background runtime...');
|
|
79
|
+
try {
|
|
80
|
+
const msg = (0, runtime_manager_js_1.startBackgroundRuntime)(config.language);
|
|
81
|
+
console.log(msg);
|
|
82
|
+
// Chờ một chút để tiến trình nền khởi động và ghi state/heartbeat
|
|
83
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
67
89
|
await (0, ui_js_1.launchControlCenter)();
|
|
68
90
|
}
|
|
69
91
|
void main();
|
package/dist/runtime-manager.js
CHANGED
|
@@ -37,9 +37,13 @@ exports.getRuntimeStatus = getRuntimeStatus;
|
|
|
37
37
|
exports.getEntryScriptPath = getEntryScriptPath;
|
|
38
38
|
exports.startBackgroundRuntime = startBackgroundRuntime;
|
|
39
39
|
exports.stopBackgroundRuntime = stopBackgroundRuntime;
|
|
40
|
+
exports.getCurrentVersion = getCurrentVersion;
|
|
41
|
+
exports.checkForUpdate = checkForUpdate;
|
|
42
|
+
exports.checkUpdateOnStartup = checkUpdateOnStartup;
|
|
40
43
|
const child_process_1 = require("child_process");
|
|
41
44
|
const fs = __importStar(require("fs"));
|
|
42
45
|
const path = __importStar(require("path"));
|
|
46
|
+
const os = __importStar(require("os"));
|
|
43
47
|
const runtime_state_js_1 = require("./runtime-state.js");
|
|
44
48
|
function getRuntimeStatus() {
|
|
45
49
|
const snapshot = (0, runtime_state_js_1.loadRuntimeState)();
|
|
@@ -68,25 +72,126 @@ function getEntryScriptPath() {
|
|
|
68
72
|
}
|
|
69
73
|
throw new Error('dist/index.js not found. Please ensure the project is built.');
|
|
70
74
|
}
|
|
71
|
-
function startBackgroundRuntime() {
|
|
75
|
+
function startBackgroundRuntime(language) {
|
|
72
76
|
const entryScriptPath = getEntryScriptPath();
|
|
77
|
+
(0, runtime_state_js_1.saveShouldRunState)(true);
|
|
73
78
|
const child = (0, child_process_1.spawn)(process.execPath, [entryScriptPath, '--background'], {
|
|
74
79
|
cwd: process.cwd(),
|
|
75
80
|
detached: true,
|
|
76
81
|
stdio: 'ignore'
|
|
77
82
|
});
|
|
78
83
|
child.unref();
|
|
79
|
-
|
|
84
|
+
const pid = child.pid ?? 'unknown';
|
|
85
|
+
return language === 'vi'
|
|
86
|
+
? `✓ Đã khởi chạy runtime nền của nonstop (pid ${pid}).`
|
|
87
|
+
: `✓ Started nonstop background runtime (pid ${pid}).`;
|
|
80
88
|
}
|
|
81
|
-
function stopBackgroundRuntime(snapshot) {
|
|
89
|
+
function stopBackgroundRuntime(snapshot, language) {
|
|
90
|
+
const isVi = language === 'vi';
|
|
82
91
|
if (!snapshot || !(0, runtime_state_js_1.isPidRunning)(snapshot.pid)) {
|
|
83
|
-
return 'Background runtime is not running.';
|
|
92
|
+
return isVi ? '⚠ Runtime nền không đang chạy.' : 'Background runtime is not running.';
|
|
84
93
|
}
|
|
94
|
+
(0, runtime_state_js_1.saveShouldRunState)(false);
|
|
85
95
|
try {
|
|
86
96
|
process.kill(snapshot.pid);
|
|
87
|
-
return
|
|
97
|
+
return isVi
|
|
98
|
+
? `✓ Đã dừng runtime nền của nonstop (${snapshot.pid}).`
|
|
99
|
+
: `✓ Stopped nonstop background runtime (${snapshot.pid}).`;
|
|
88
100
|
}
|
|
89
101
|
catch (error) {
|
|
90
|
-
|
|
102
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
103
|
+
throw new Error(isVi
|
|
104
|
+
? `❌ Lỗi khi dừng runtime nền (${snapshot.pid}): ${errorMsg}`
|
|
105
|
+
: `Failed to stop background runtime (${snapshot.pid}): ${errorMsg}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function getCurrentVersion() {
|
|
109
|
+
try {
|
|
110
|
+
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
111
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
112
|
+
return pkg.version;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return '1.0.13';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function checkForUpdate(currentVersion) {
|
|
119
|
+
return new Promise((resolve) => {
|
|
120
|
+
const timer = setTimeout(() => {
|
|
121
|
+
resolve(null);
|
|
122
|
+
}, 4000);
|
|
123
|
+
(0, child_process_1.exec)('npm view @quangnv13/nonstop version', (error, stdout) => {
|
|
124
|
+
clearTimeout(timer);
|
|
125
|
+
if (error) {
|
|
126
|
+
resolve(null);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const latest = stdout.trim();
|
|
130
|
+
if (latest && latest !== currentVersion) {
|
|
131
|
+
resolve(latest);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
resolve(null);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
async function checkUpdateOnStartup(isBackground, language) {
|
|
140
|
+
const currentVersion = getCurrentVersion();
|
|
141
|
+
const latestVersion = await checkForUpdate(currentVersion);
|
|
142
|
+
if (latestVersion) {
|
|
143
|
+
if (isBackground) {
|
|
144
|
+
promptUpgradeBackground(currentVersion, latestVersion, language);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function promptUpgradeBackground(currentVersion, latestVersion, language) {
|
|
149
|
+
const platform = os.platform();
|
|
150
|
+
const isVi = language === 'vi';
|
|
151
|
+
if (platform === 'win32') {
|
|
152
|
+
const title = isVi ? 'Cập nhật nonstop' : 'nonstop Update';
|
|
153
|
+
const msg = isVi
|
|
154
|
+
? `Có phiên bản mới của nonstop: ${latestVersion} (Hiện tại: ${currentVersion}). Bạn có muốn nâng cấp không? (y/n): `
|
|
155
|
+
: `A new version of nonstop is available: ${latestVersion} (Current: ${currentVersion}). Do you want to upgrade? (y/n): `;
|
|
156
|
+
const upgradingMsg = isVi ? 'Đang nâng cấp @quangnv13/nonstop lên phiên bản mới nhất...' : 'Upgrading @quangnv13/nonstop to the latest version...';
|
|
157
|
+
const successMsg = isVi ? 'Cập nhật nonstop thành công! Nhấn phím bất kỳ để đóng...' : 'nonstop update successful! Press any key to close...';
|
|
158
|
+
const failMsg = isVi ? 'Cập nhật nonstop thất bại.' : 'nonstop update failed.';
|
|
159
|
+
const skippedMsg = isVi ? 'Đã bỏ qua cập nhật nonstop.' : 'nonstop update skipped.';
|
|
160
|
+
const psCommand = `
|
|
161
|
+
$Host.UI.RawUI.WindowTitle = '${title}';
|
|
162
|
+
Write-Host '${msg}' -NoNewline -ForegroundColor Yellow;
|
|
163
|
+
$choice = Read-Host;
|
|
164
|
+
if ($choice -eq 'y' -or $choice -eq 'yes') {
|
|
165
|
+
Write-Host '${upgradingMsg}' -ForegroundColor Blue;
|
|
166
|
+
npm install -g @quangnv13/nonstop@latest;
|
|
167
|
+
if ($LASTEXITCODE -eq 0) {
|
|
168
|
+
Write-Host '${successMsg}' -ForegroundColor Green;
|
|
169
|
+
} else {
|
|
170
|
+
Write-Host '${failMsg}' -ForegroundColor Red;
|
|
171
|
+
Start-Sleep -Seconds 3;
|
|
172
|
+
}
|
|
173
|
+
$null = [Console]::ReadKey();
|
|
174
|
+
} else {
|
|
175
|
+
Write-Host '${skippedMsg}' -ForegroundColor Gray;
|
|
176
|
+
Start-Sleep -Seconds 1;
|
|
177
|
+
}
|
|
178
|
+
`.replace(/\r?\n/g, ' ').trim();
|
|
179
|
+
const cmd = 'cmd.exe';
|
|
180
|
+
const args = [
|
|
181
|
+
'/c',
|
|
182
|
+
'start',
|
|
183
|
+
'powershell',
|
|
184
|
+
'-NoProfile',
|
|
185
|
+
'-Command',
|
|
186
|
+
psCommand
|
|
187
|
+
];
|
|
188
|
+
(0, child_process_1.spawn)(cmd, args, {
|
|
189
|
+
detached: true,
|
|
190
|
+
stdio: 'ignore',
|
|
191
|
+
shell: true
|
|
192
|
+
}).unref();
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
console.log(`Update available: ${latestVersion} (Current: ${currentVersion})`);
|
|
91
196
|
}
|
|
92
197
|
}
|
package/dist/runtime-state.js
CHANGED
|
@@ -34,17 +34,38 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.saveRuntimeState = saveRuntimeState;
|
|
37
|
+
exports.saveShouldRunState = saveShouldRunState;
|
|
38
|
+
exports.loadShouldRunState = loadShouldRunState;
|
|
37
39
|
exports.loadRuntimeState = loadRuntimeState;
|
|
38
40
|
exports.clearRuntimeState = clearRuntimeState;
|
|
39
41
|
exports.isPidRunning = isPidRunning;
|
|
40
42
|
exports.getRuntimeStatePath = getRuntimeStatePath;
|
|
43
|
+
exports.getIpcSocketPath = getIpcSocketPath;
|
|
41
44
|
const fs = __importStar(require("fs"));
|
|
42
45
|
const path = __importStar(require("path"));
|
|
46
|
+
const os = __importStar(require("os"));
|
|
43
47
|
const RUNTIME_STATE_PATH = path.join(process.cwd(), 'data', 'runtime-state.json');
|
|
48
|
+
const SHOULD_RUN_PATH = path.join(process.cwd(), 'data', 'runtime-should-run.json');
|
|
44
49
|
function saveRuntimeState(snapshot) {
|
|
45
50
|
fs.mkdirSync(path.dirname(RUNTIME_STATE_PATH), { recursive: true });
|
|
46
51
|
fs.writeFileSync(RUNTIME_STATE_PATH, JSON.stringify(snapshot, null, 2), 'utf8');
|
|
47
52
|
}
|
|
53
|
+
function saveShouldRunState(shouldRun) {
|
|
54
|
+
fs.mkdirSync(path.dirname(SHOULD_RUN_PATH), { recursive: true });
|
|
55
|
+
fs.writeFileSync(SHOULD_RUN_PATH, JSON.stringify({ shouldRun }, null, 2), 'utf8');
|
|
56
|
+
}
|
|
57
|
+
function loadShouldRunState() {
|
|
58
|
+
if (!fs.existsSync(SHOULD_RUN_PATH)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const data = JSON.parse(fs.readFileSync(SHOULD_RUN_PATH, 'utf8'));
|
|
63
|
+
return !!data.shouldRun;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
48
69
|
function loadRuntimeState() {
|
|
49
70
|
if (!fs.existsSync(RUNTIME_STATE_PATH)) {
|
|
50
71
|
return null;
|
|
@@ -76,3 +97,12 @@ function isPidRunning(pid) {
|
|
|
76
97
|
function getRuntimeStatePath() {
|
|
77
98
|
return RUNTIME_STATE_PATH;
|
|
78
99
|
}
|
|
100
|
+
function getIpcSocketPath() {
|
|
101
|
+
const username = os.userInfo().username || 'user';
|
|
102
|
+
if (process.platform === 'win32') {
|
|
103
|
+
return `\\\\.\\pipe\\nonstop-ipc-${username}`;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
return path.join(process.cwd(), 'data', `nonstop-ipc-${username}.sock`);
|
|
107
|
+
}
|
|
108
|
+
}
|
package/dist/runtime.js
CHANGED
|
@@ -41,6 +41,7 @@ const bot_js_1 = require("./bot.js");
|
|
|
41
41
|
const config_js_1 = require("./config.js");
|
|
42
42
|
const logger_js_1 = require("./logger.js");
|
|
43
43
|
const runtime_state_js_1 = require("./runtime-state.js");
|
|
44
|
+
const net = __importStar(require("net"));
|
|
44
45
|
const session_delivery_js_1 = require("./session-delivery.js");
|
|
45
46
|
const session_output_js_1 = require("./session-output.js");
|
|
46
47
|
const store_js_1 = require("./store.js");
|
|
@@ -60,6 +61,8 @@ class NonstopRuntime {
|
|
|
60
61
|
heartbeatTicker = null;
|
|
61
62
|
onSessionOutputPush = null;
|
|
62
63
|
bot = null;
|
|
64
|
+
ipcServer = null;
|
|
65
|
+
activeIpcSockets = new Set();
|
|
63
66
|
constructor(config, mode) {
|
|
64
67
|
this.config = config;
|
|
65
68
|
this.mode = mode;
|
|
@@ -112,6 +115,15 @@ class NonstopRuntime {
|
|
|
112
115
|
if (this.bot) {
|
|
113
116
|
return;
|
|
114
117
|
}
|
|
118
|
+
const ipcPath = path.join(process.cwd(), 'data', 'ipc-command.json');
|
|
119
|
+
if (fs.existsSync(ipcPath)) {
|
|
120
|
+
try {
|
|
121
|
+
fs.unlinkSync(ipcPath);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// ignore
|
|
125
|
+
}
|
|
126
|
+
}
|
|
115
127
|
logger_js_1.logger.info('nonstop runtime bootstrap complete', {
|
|
116
128
|
clientName: this.config.clientName,
|
|
117
129
|
telegramUsername: this.config.telegramUsername,
|
|
@@ -142,17 +154,21 @@ class NonstopRuntime {
|
|
|
142
154
|
});
|
|
143
155
|
// Ghi heartbeat TRƯỚC khi bot connect để UI polling nhận ngay trạng thái RUNNING
|
|
144
156
|
this.startHeartbeat();
|
|
157
|
+
this.startIpcServer();
|
|
145
158
|
await this.bot.start({
|
|
146
159
|
onStart: async (botInfo) => {
|
|
147
|
-
logger_js_1.logger.info('Telegram bot
|
|
160
|
+
logger_js_1.logger.info('Telegram bot started', {
|
|
148
161
|
username: botInfo.username,
|
|
149
162
|
mode: this.mode
|
|
150
163
|
});
|
|
151
|
-
//
|
|
164
|
+
// Send startup notification to Telegram
|
|
152
165
|
const lastChatId = (0, bot_js_1.loadLastChatId)();
|
|
153
166
|
if (lastChatId && this.bot) {
|
|
154
167
|
try {
|
|
155
|
-
|
|
168
|
+
const startupMsg = this.config.language === 'vi'
|
|
169
|
+
? `✅ nonstop client đã khởi động thành công và đang chạy!\n🖥 Client: ${this.config.clientName}`
|
|
170
|
+
: `✅ nonstop client started successfully and is running!\n🖥 Client: ${this.config.clientName}`;
|
|
171
|
+
await this.bot.pushSessionOutput(lastChatId, startupMsg);
|
|
156
172
|
}
|
|
157
173
|
catch {
|
|
158
174
|
// ignore
|
|
@@ -171,6 +187,7 @@ class NonstopRuntime {
|
|
|
171
187
|
clearInterval(this.heartbeatTicker);
|
|
172
188
|
this.heartbeatTicker = null;
|
|
173
189
|
}
|
|
190
|
+
this.stopIpcServer();
|
|
174
191
|
(0, runtime_state_js_1.clearRuntimeState)();
|
|
175
192
|
}
|
|
176
193
|
setSessionInputMode(inputMode) {
|
|
@@ -239,6 +256,7 @@ class NonstopRuntime {
|
|
|
239
256
|
this.writeHeartbeat();
|
|
240
257
|
driver.onData((chunk) => {
|
|
241
258
|
this.bufferOutput(chunk);
|
|
259
|
+
this.broadcastToIpcClients({ type: 'output', data: chunk });
|
|
242
260
|
});
|
|
243
261
|
driver.onExit((code, signal) => {
|
|
244
262
|
void this.handleDriverExit(sessionId, code, signal);
|
|
@@ -322,8 +340,36 @@ class NonstopRuntime {
|
|
|
322
340
|
}
|
|
323
341
|
this.heartbeatTicker = setInterval(() => {
|
|
324
342
|
this.writeHeartbeat();
|
|
343
|
+
void this.checkIpcCommands();
|
|
325
344
|
}, 2000);
|
|
326
345
|
}
|
|
346
|
+
async checkIpcCommands() {
|
|
347
|
+
const ipcPath = path.join(process.cwd(), 'data', 'ipc-command.json');
|
|
348
|
+
if (!fs.existsSync(ipcPath)) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
const content = fs.readFileSync(ipcPath, 'utf8');
|
|
353
|
+
const cmd = JSON.parse(content);
|
|
354
|
+
if (cmd.action === 'stop-session') {
|
|
355
|
+
logger_js_1.logger.info('Received IPC command to stop session via file');
|
|
356
|
+
await this.stopSession();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
logger_js_1.logger.error('Error handling IPC command', {
|
|
361
|
+
error: error instanceof Error ? error.message : String(error)
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
finally {
|
|
365
|
+
try {
|
|
366
|
+
fs.unlinkSync(ipcPath);
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
// ignore
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
327
373
|
writeHeartbeat() {
|
|
328
374
|
(0, runtime_state_js_1.saveRuntimeState)(this.getStatus());
|
|
329
375
|
}
|
|
@@ -334,6 +380,7 @@ class NonstopRuntime {
|
|
|
334
380
|
}
|
|
335
381
|
session.status = 'stopped';
|
|
336
382
|
this.activeDriverRef.current = null;
|
|
383
|
+
this.broadcastToIpcClients({ type: 'exit', code });
|
|
337
384
|
await this.flushOutput(true);
|
|
338
385
|
this.resetOutputRuntime();
|
|
339
386
|
this.activeSession = null;
|
|
@@ -366,6 +413,11 @@ class NonstopRuntime {
|
|
|
366
413
|
const promptDetectionText = stripAnsi(text);
|
|
367
414
|
const snapshot = renderTerminalSnapshot(this.terminalState, this.config.maxOutputLines);
|
|
368
415
|
const finalText = snapshot || limitLines(promptDetectionText, this.config.maxOutputLines);
|
|
416
|
+
if (this.activeIpcSockets.size > 0) {
|
|
417
|
+
// User is active locally. Clear buffer, sync lastSentFinalText, but skip Telegram messaging.
|
|
418
|
+
session.lastSentFinalText = finalText;
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
369
421
|
if (!text && !forceSnapshot) {
|
|
370
422
|
return;
|
|
371
423
|
}
|
|
@@ -391,7 +443,14 @@ class NonstopRuntime {
|
|
|
391
443
|
return;
|
|
392
444
|
}
|
|
393
445
|
for (const message of messages) {
|
|
394
|
-
|
|
446
|
+
try {
|
|
447
|
+
await this.onSessionOutputPush(session.listenerChatId, message.text, message.options);
|
|
448
|
+
}
|
|
449
|
+
catch (err) {
|
|
450
|
+
logger_js_1.logger.error('Failed to push session output to Telegram', {
|
|
451
|
+
error: err instanceof Error ? err.message : String(err)
|
|
452
|
+
});
|
|
453
|
+
}
|
|
395
454
|
}
|
|
396
455
|
session.lastSentFinalText = finalText;
|
|
397
456
|
}
|
|
@@ -418,6 +477,129 @@ class NonstopRuntime {
|
|
|
418
477
|
this.activeSession.lastSentFinalText = '';
|
|
419
478
|
}
|
|
420
479
|
}
|
|
480
|
+
startIpcServer() {
|
|
481
|
+
if (this.ipcServer)
|
|
482
|
+
return;
|
|
483
|
+
const socketPath = (0, runtime_state_js_1.getIpcSocketPath)();
|
|
484
|
+
if (process.platform !== 'win32') {
|
|
485
|
+
try {
|
|
486
|
+
const socketDir = path.dirname(socketPath);
|
|
487
|
+
if (!fs.existsSync(socketDir)) {
|
|
488
|
+
fs.mkdirSync(socketDir, { recursive: true });
|
|
489
|
+
}
|
|
490
|
+
if (fs.existsSync(socketPath)) {
|
|
491
|
+
fs.unlinkSync(socketPath);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
catch (err) {
|
|
495
|
+
logger_js_1.logger.error('Failed to unlink existing IPC socket file', { err });
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
this.ipcServer = net.createServer((socket) => {
|
|
499
|
+
logger_js_1.logger.info('IPC client connected to background session');
|
|
500
|
+
this.activeIpcSockets.add(socket);
|
|
501
|
+
// Immediately send the current screen snapshot to the newly connected client
|
|
502
|
+
const snapshot = renderTerminalSnapshot(this.terminalState, this.config.maxRenderLines);
|
|
503
|
+
const initOutput = '\u001b[2J\u001b[H' + snapshot;
|
|
504
|
+
socket.write(JSON.stringify({ type: 'output', data: initOutput }) + '\n');
|
|
505
|
+
let buffer = '';
|
|
506
|
+
socket.on('data', (data) => {
|
|
507
|
+
buffer += data.toString();
|
|
508
|
+
let boundary = buffer.indexOf('\n');
|
|
509
|
+
while (boundary !== -1) {
|
|
510
|
+
const line = buffer.slice(0, boundary).trim();
|
|
511
|
+
buffer = buffer.slice(boundary + 1);
|
|
512
|
+
if (line) {
|
|
513
|
+
try {
|
|
514
|
+
const msg = JSON.parse(line);
|
|
515
|
+
this.handleIpcClientMessage(msg);
|
|
516
|
+
}
|
|
517
|
+
catch (err) {
|
|
518
|
+
logger_js_1.logger.error('Error parsing IPC client message', { err, line });
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
boundary = buffer.indexOf('\n');
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
socket.on('error', (err) => {
|
|
525
|
+
logger_js_1.logger.error('IPC client socket error', { err });
|
|
526
|
+
});
|
|
527
|
+
socket.on('close', () => {
|
|
528
|
+
logger_js_1.logger.info('IPC client disconnected');
|
|
529
|
+
this.activeIpcSockets.delete(socket);
|
|
530
|
+
if (this.activeIpcSockets.size === 0) {
|
|
531
|
+
// Immediately flush the final state of the session to Telegram
|
|
532
|
+
void this.flushOutput(true, true);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
this.ipcServer.listen(socketPath, () => {
|
|
537
|
+
logger_js_1.logger.info(`IPC Server listening on ${socketPath}`);
|
|
538
|
+
});
|
|
539
|
+
this.ipcServer.on('error', (err) => {
|
|
540
|
+
logger_js_1.logger.error('IPC Server error', { err });
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
handleIpcClientMessage(msg) {
|
|
544
|
+
if (!msg || typeof msg !== 'object')
|
|
545
|
+
return;
|
|
546
|
+
switch (msg.type) {
|
|
547
|
+
case 'input':
|
|
548
|
+
if (typeof msg.data === 'string') {
|
|
549
|
+
this.sendSessionInput(msg.data);
|
|
550
|
+
}
|
|
551
|
+
break;
|
|
552
|
+
case 'resize':
|
|
553
|
+
if (typeof msg.cols === 'number' && typeof msg.rows === 'number') {
|
|
554
|
+
const driver = this.activeDriverRef.current;
|
|
555
|
+
if (driver && this.activeSession?.status === 'running') {
|
|
556
|
+
driver.resize(msg.cols, msg.rows);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
break;
|
|
560
|
+
default:
|
|
561
|
+
logger_js_1.logger.warn('Unknown IPC message type', { msg });
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
stopIpcServer() {
|
|
565
|
+
if (this.ipcServer) {
|
|
566
|
+
for (const socket of this.activeIpcSockets) {
|
|
567
|
+
try {
|
|
568
|
+
socket.destroy();
|
|
569
|
+
}
|
|
570
|
+
catch {
|
|
571
|
+
// ignore
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
this.activeIpcSockets.clear();
|
|
575
|
+
this.ipcServer.close();
|
|
576
|
+
this.ipcServer = null;
|
|
577
|
+
const socketPath = (0, runtime_state_js_1.getIpcSocketPath)();
|
|
578
|
+
if (process.platform !== 'win32') {
|
|
579
|
+
try {
|
|
580
|
+
if (fs.existsSync(socketPath)) {
|
|
581
|
+
fs.unlinkSync(socketPath);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
catch {
|
|
585
|
+
// ignore
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
broadcastToIpcClients(msg) {
|
|
591
|
+
if (this.activeIpcSockets.size === 0)
|
|
592
|
+
return;
|
|
593
|
+
const packet = JSON.stringify(msg) + '\n';
|
|
594
|
+
for (const socket of this.activeIpcSockets) {
|
|
595
|
+
try {
|
|
596
|
+
socket.write(packet);
|
|
597
|
+
}
|
|
598
|
+
catch (err) {
|
|
599
|
+
logger_js_1.logger.error('Failed to write to IPC client socket', { err });
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
421
603
|
}
|
|
422
604
|
exports.NonstopRuntime = NonstopRuntime;
|
|
423
605
|
function createSessionId(preset) {
|
|
@@ -437,7 +619,7 @@ function resolveKeyInput(key, preset) {
|
|
|
437
619
|
case 'send_escape':
|
|
438
620
|
case 'escape':
|
|
439
621
|
case 'interrupt':
|
|
440
|
-
if (preset === 'codex' || preset === 'antigravity') {
|
|
622
|
+
if (preset === 'codex' || preset === 'antigravity' || preset === 'claude') {
|
|
441
623
|
return '\u001b';
|
|
442
624
|
}
|
|
443
625
|
return null;
|
package/dist/session-controls.js
CHANGED
|
@@ -9,14 +9,14 @@ function buildSessionActionMarkup(options) {
|
|
|
9
9
|
[
|
|
10
10
|
{
|
|
11
11
|
text: inputMode
|
|
12
|
-
? (isVi ? '⌨️ Tắt
|
|
13
|
-
: (isVi ? '⌨️ Bật
|
|
12
|
+
? (isVi ? '⌨️ Tắt Nhập' : '⌨️ Input OFF')
|
|
13
|
+
: (isVi ? '⌨️ Bật Nhập' : '⌨️ Input ON'),
|
|
14
14
|
callback_data: `session_cmd:${options.sessionId}:toggle_input`
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
17
|
text: autoEnter
|
|
18
18
|
? (isVi ? '⏎ Tắt AutoEnter' : '⏎ AutoEnter OFF')
|
|
19
|
-
: (isVi ? '⏎ Bật AutoEnter' : '⏎
|
|
19
|
+
: (isVi ? '⏎ Bật AutoEnter' : '⏎ AutoEnter ON'),
|
|
20
20
|
callback_data: `session_cmd:${options.sessionId}:toggle_enter`
|
|
21
21
|
},
|
|
22
22
|
{
|
package/dist/terminal.js
CHANGED
|
@@ -92,7 +92,7 @@ class NodePtyTerminalDriver {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
exports.NodePtyTerminalDriver = NodePtyTerminalDriver;
|
|
95
|
-
exports.SUPPORTED_PRESETS = ['powershell', 'bash', 'codex', 'antigravity'];
|
|
95
|
+
exports.SUPPORTED_PRESETS = ['powershell', 'bash', 'codex', 'antigravity', 'claude'];
|
|
96
96
|
function resolvePreset(presetName) {
|
|
97
97
|
const isWindows = process.platform === 'win32';
|
|
98
98
|
switch (presetName) {
|
|
@@ -132,6 +132,19 @@ function resolvePreset(presetName) {
|
|
|
132
132
|
}
|
|
133
133
|
return { command, args };
|
|
134
134
|
}
|
|
135
|
+
case 'claude': {
|
|
136
|
+
const command = process.env.CLAUDE_CMD || 'claude';
|
|
137
|
+
let args = [];
|
|
138
|
+
if (process.env.CLAUDE_ARGS) {
|
|
139
|
+
try {
|
|
140
|
+
args = JSON.parse(process.env.CLAUDE_ARGS);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
args = process.env.CLAUDE_ARGS.split(/\s+/).filter(Boolean);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { command, args };
|
|
147
|
+
}
|
|
135
148
|
default:
|
|
136
149
|
throw new Error(`Unsupported preset "${presetName}".`);
|
|
137
150
|
}
|