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 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
- // MITM / Zero-Knowledge Defense: Encrypt metrics before sending over socket
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: this.conn.agentId || store.get('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', payload);
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
- if (pty) {
65
- const proc = pty.spawn(process.env.SHELL || '/bin/bash', [], {
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: process.env.HOME || '/',
70
- env: buildSafeEnv(),
97
+ cwd: homeDir,
98
+ env: safeEnv,
71
99
  });
72
100
 
73
- proc.onData(data => s.emit('shell:data', { sessionId, 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
- // Fallback to interactive standard child_process spawn
145
+ // Tier 3: Pure ChildProcess fallback with suppressed ioctl noise
85
146
  const { spawn } = require('child_process');
86
- const proc = spawn(process.env.SHELL || '/bin/bash', ['-i'], {
87
- cwd: process.env.HOME || '/',
88
- env: buildSafeEnv(),
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 => s.emit('shell:data', { sessionId, data: data.toString() }));
93
- proc.stderr.on('data', data => s.emit('shell:data', { sessionId, data: data.toString() }));
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: () => proc.kill()
169
+ kill: () => {
170
+ try { proc.kill('SIGTERM'); } catch {}
171
+ }
103
172
  });
104
173
  s.emit('shell:opened', { sessionId });
105
- console.log(`[shell] Interactive Spawn session ${sessionId} opened`);
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
- proc.write(data);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinknagent",
3
- "version": "0.1.31",
3
+ "version": "0.1.34",
4
4
  "description": "ThinkNCollab server agent & MCP server — metrics, logs, alerts, shell bridge, AI planning",
5
5
  "main": "lib/agent.js",
6
6
  "author": "ThinkNCollab Team <raman@thinkncollab.com>",