thinknagent 0.1.31 → 0.1.34
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/lib/alerts.js +9 -2
- package/lib/metrics.js +7 -3
- package/lib/shell.js +108 -17
- package/package.json +1 -1
package/lib/alerts.js
CHANGED
|
@@ -15,14 +15,21 @@
|
|
|
15
15
|
* "for" = seconds the condition must persist before firing (0 = fire immediately)
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
const DEFAULT_ALERT_RULES = [
|
|
19
|
+
{ id: 'cpu-high', metric: 'cpu.usage', op: 'gt', value: 85, for: 30, severity: 'warning' },
|
|
20
|
+
{ id: 'cpu-crit', metric: 'cpu.usage', op: 'gt', value: 95, for: 15, severity: 'critical' },
|
|
21
|
+
{ id: 'mem-high', metric: 'memory.usedPct', op: 'gt', value: 90, for: 30, severity: 'warning' },
|
|
22
|
+
{ id: 'disk-root', metric: 'disk./', op: 'gt', value: 90, for: 0, severity: 'critical' },
|
|
23
|
+
];
|
|
24
|
+
|
|
18
25
|
class AlertEngine {
|
|
19
26
|
constructor({ connection, rules = [] }) {
|
|
20
27
|
this.conn = connection;
|
|
21
|
-
this.rules = rules;
|
|
28
|
+
this.rules = (rules && rules.length > 0) ? rules : DEFAULT_ALERT_RULES;
|
|
22
29
|
|
|
23
30
|
// per-rule state: { firstTriggeredAt, fired }
|
|
24
31
|
this._state = {};
|
|
25
|
-
for (const r of rules) {
|
|
32
|
+
for (const r of this.rules) {
|
|
26
33
|
this._state[r.id] = { firstTriggeredAt: null, fired: false };
|
|
27
34
|
}
|
|
28
35
|
}
|
package/lib/metrics.js
CHANGED
|
@@ -236,21 +236,25 @@ class MetricsPoller {
|
|
|
236
236
|
// pehle alert engine aur history ko raw metrics pass karo — 24/7 Edge evaluation
|
|
237
237
|
this.onMetricsEmit?.(payload);
|
|
238
238
|
|
|
239
|
-
//
|
|
239
|
+
// Military-grade AES-256-GCM End-to-End Encryption (E2EE)
|
|
240
240
|
const store = require('./store');
|
|
241
241
|
const { encryptE2EE } = require('./e2ee');
|
|
242
242
|
const roomId = this.conn?.roomId || store.get('roomId');
|
|
243
|
+
const agentId = this.conn?.agentId || store.get('agentId');
|
|
243
244
|
|
|
244
245
|
if (roomId) {
|
|
245
246
|
const encrypted = encryptE2EE(payload, roomId);
|
|
246
247
|
this.conn.emit('agent:metrics', {
|
|
247
|
-
agentId
|
|
248
|
+
agentId,
|
|
248
249
|
ts: payload.ts,
|
|
249
250
|
encrypted,
|
|
250
251
|
e2ee: true
|
|
251
252
|
});
|
|
252
253
|
} else {
|
|
253
|
-
this.conn.emit('agent:metrics',
|
|
254
|
+
this.conn.emit('agent:metrics', {
|
|
255
|
+
agentId,
|
|
256
|
+
...payload
|
|
257
|
+
});
|
|
254
258
|
}
|
|
255
259
|
|
|
256
260
|
} catch (err) {
|
package/lib/shell.js
CHANGED
|
@@ -1,12 +1,34 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
let pty;
|
|
3
|
+
let pty = null;
|
|
4
4
|
try {
|
|
5
5
|
pty = require('node-pty');
|
|
6
6
|
} catch {
|
|
7
7
|
pty = null;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
let detectedPtyLauncher = null;
|
|
11
|
+
function getPtyLauncher() {
|
|
12
|
+
if (detectedPtyLauncher) return detectedPtyLauncher;
|
|
13
|
+
if (pty) {
|
|
14
|
+
detectedPtyLauncher = 'node-pty';
|
|
15
|
+
return 'node-pty';
|
|
16
|
+
}
|
|
17
|
+
const { execSync } = require('child_process');
|
|
18
|
+
try {
|
|
19
|
+
execSync('python3 -c "import pty"', { stdio: 'ignore' });
|
|
20
|
+
detectedPtyLauncher = 'python3';
|
|
21
|
+
return 'python3';
|
|
22
|
+
} catch {}
|
|
23
|
+
try {
|
|
24
|
+
execSync('python -c "import pty"', { stdio: 'ignore' });
|
|
25
|
+
detectedPtyLauncher = 'python';
|
|
26
|
+
return 'python';
|
|
27
|
+
} catch {}
|
|
28
|
+
detectedPtyLauncher = 'spawn';
|
|
29
|
+
return 'spawn';
|
|
30
|
+
}
|
|
31
|
+
|
|
10
32
|
// explicit allowlist — agent process ke secrets PTY mein nahi jayenge
|
|
11
33
|
const PTY_ENV_ALLOWLIST = new Set([
|
|
12
34
|
'PATH', 'HOME', 'SHELL', 'TERM', 'LANG', 'LC_ALL', 'LC_CTYPE',
|
|
@@ -61,16 +83,22 @@ class ShellBridge {
|
|
|
61
83
|
this._sessions.delete(id);
|
|
62
84
|
}
|
|
63
85
|
|
|
64
|
-
|
|
65
|
-
|
|
86
|
+
const launcher = getPtyLauncher();
|
|
87
|
+
const shellPath = process.env.SHELL || '/bin/bash';
|
|
88
|
+
const homeDir = process.env.HOME || '/';
|
|
89
|
+
const safeEnv = buildSafeEnv();
|
|
90
|
+
|
|
91
|
+
if (launcher === 'node-pty' && pty) {
|
|
92
|
+
// Tier 1: node-pty native module
|
|
93
|
+
const proc = pty.spawn(shellPath, [], {
|
|
66
94
|
name: 'xterm-256color',
|
|
67
95
|
cols,
|
|
68
96
|
rows,
|
|
69
|
-
cwd:
|
|
70
|
-
env:
|
|
97
|
+
cwd: homeDir,
|
|
98
|
+
env: safeEnv,
|
|
71
99
|
});
|
|
72
100
|
|
|
73
|
-
proc.onData(data =>
|
|
101
|
+
proc.onData(data => this._sendData(s, sessionId, data));
|
|
74
102
|
proc.onExit(({ exitCode }) => {
|
|
75
103
|
s.emit('shell:exit', { sessionId, exitCode });
|
|
76
104
|
this._sessions.delete(sessionId);
|
|
@@ -79,18 +107,57 @@ class ShellBridge {
|
|
|
79
107
|
|
|
80
108
|
this._sessions.set(sessionId, proc);
|
|
81
109
|
s.emit('shell:opened', { sessionId });
|
|
82
|
-
console.log(`[shell] PTY session ${sessionId} opened (${cols}x${rows})`);
|
|
110
|
+
console.log(`[shell] PTY session ${sessionId} opened via node-pty (${cols}x${rows})`);
|
|
111
|
+
} else if (launcher === 'python3' || launcher === 'python') {
|
|
112
|
+
// Tier 2: Native OS pseudo-terminal via standard library pty.spawn
|
|
113
|
+
// Eliminates 'Inappropriate ioctl for device' & 'no job control' completely
|
|
114
|
+
const { spawn } = require('child_process');
|
|
115
|
+
const pyCode = 'import pty, os; os.environ["TERM"]="xterm-256color"; pty.spawn([os.environ.get("SHELL", "/bin/bash")])';
|
|
116
|
+
const proc = spawn(launcher, ['-c', pyCode], {
|
|
117
|
+
cwd: homeDir,
|
|
118
|
+
env: safeEnv,
|
|
119
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
proc.stdout.on('data', data => this._sendData(s, sessionId, data.toString()));
|
|
123
|
+
proc.stderr.on('data', data => {
|
|
124
|
+
const clean = data.toString()
|
|
125
|
+
.replace(/^bash: cannot set terminal process group.*?\n/gm, '')
|
|
126
|
+
.replace(/^bash: no job control in this shell.*?\n/gm, '');
|
|
127
|
+
if (clean) this._sendData(s, sessionId, clean);
|
|
128
|
+
});
|
|
129
|
+
proc.on('exit', exitCode => {
|
|
130
|
+
s.emit('shell:exit', { sessionId, exitCode: exitCode || 0 });
|
|
131
|
+
this._sessions.delete(sessionId);
|
|
132
|
+
console.log(`[shell] Python PTY session ${sessionId} exited (code ${exitCode})`);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
this._sessions.set(sessionId, {
|
|
136
|
+
write: data => proc.stdin.write(data),
|
|
137
|
+
resize: () => {},
|
|
138
|
+
kill: () => {
|
|
139
|
+
try { proc.kill('SIGTERM'); } catch {}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
s.emit('shell:opened', { sessionId });
|
|
143
|
+
console.log(`[shell] Genuine OS PTY session ${sessionId} opened via ${launcher}`);
|
|
83
144
|
} else {
|
|
84
|
-
//
|
|
145
|
+
// Tier 3: Pure ChildProcess fallback with suppressed ioctl noise
|
|
85
146
|
const { spawn } = require('child_process');
|
|
86
|
-
const proc = spawn(
|
|
87
|
-
cwd:
|
|
88
|
-
env:
|
|
147
|
+
const proc = spawn(shellPath, ['--login'], {
|
|
148
|
+
cwd: homeDir,
|
|
149
|
+
env: safeEnv,
|
|
89
150
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
90
151
|
});
|
|
91
152
|
|
|
92
|
-
proc.stdout.on('data', data =>
|
|
93
|
-
proc.stderr.on('data', data =>
|
|
153
|
+
proc.stdout.on('data', data => this._sendData(s, sessionId, data.toString()));
|
|
154
|
+
proc.stderr.on('data', data => {
|
|
155
|
+
// Filter out terminal ioctl startup noise on non-tty pipes
|
|
156
|
+
const clean = data.toString()
|
|
157
|
+
.replace(/^bash: cannot set terminal process group.*?\n/gm, '')
|
|
158
|
+
.replace(/^bash: no job control in this shell.*?\n/gm, '');
|
|
159
|
+
if (clean) this._sendData(s, sessionId, clean);
|
|
160
|
+
});
|
|
94
161
|
proc.on('exit', exitCode => {
|
|
95
162
|
s.emit('shell:exit', { sessionId, exitCode: exitCode || 0 });
|
|
96
163
|
this._sessions.delete(sessionId);
|
|
@@ -99,17 +166,28 @@ class ShellBridge {
|
|
|
99
166
|
this._sessions.set(sessionId, {
|
|
100
167
|
write: data => proc.stdin.write(data),
|
|
101
168
|
resize: () => {},
|
|
102
|
-
kill: () =>
|
|
169
|
+
kill: () => {
|
|
170
|
+
try { proc.kill('SIGTERM'); } catch {}
|
|
171
|
+
}
|
|
103
172
|
});
|
|
104
173
|
s.emit('shell:opened', { sessionId });
|
|
105
|
-
console.log(`[shell]
|
|
174
|
+
console.log(`[shell] Fallback login shell session ${sessionId} opened`);
|
|
106
175
|
}
|
|
107
176
|
});
|
|
108
177
|
|
|
109
|
-
s.on('shell:input', ({ sessionId, data }) => {
|
|
178
|
+
s.on('shell:input', ({ sessionId, data, e2ee }) => {
|
|
110
179
|
const proc = this._sessions.get(sessionId);
|
|
111
180
|
if (!proc) return;
|
|
112
|
-
|
|
181
|
+
let text = data;
|
|
182
|
+
if (e2ee || (typeof data === 'string' && data.startsWith('e2ee:'))) {
|
|
183
|
+
const store = require('./store');
|
|
184
|
+
const { decryptE2EE } = require('./e2ee');
|
|
185
|
+
const roomId = this.conn?.roomId || store.get('roomId');
|
|
186
|
+
if (roomId) {
|
|
187
|
+
text = decryptE2EE(data, roomId);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
proc.write(text);
|
|
113
191
|
});
|
|
114
192
|
|
|
115
193
|
s.on('shell:resize', ({ sessionId, cols, rows }) => {
|
|
@@ -132,6 +210,19 @@ class ShellBridge {
|
|
|
132
210
|
}
|
|
133
211
|
}
|
|
134
212
|
|
|
213
|
+
_sendData(s, sessionId, rawChunk) {
|
|
214
|
+
if (!rawChunk) return;
|
|
215
|
+
const store = require('./store');
|
|
216
|
+
const { encryptE2EE } = require('./e2ee');
|
|
217
|
+
const roomId = this.conn?.roomId || store.get('roomId');
|
|
218
|
+
if (roomId) {
|
|
219
|
+
const encrypted = encryptE2EE(rawChunk, roomId);
|
|
220
|
+
s.emit('shell:data', { sessionId, data: encrypted, e2ee: true });
|
|
221
|
+
} else {
|
|
222
|
+
s.emit('shell:data', { sessionId, data: rawChunk, e2ee: false });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
135
226
|
killAll() {
|
|
136
227
|
for (const [id] of this._sessions) this._killSession(id);
|
|
137
228
|
}
|
package/package.json
CHANGED