thinknagent 0.1.31 → 0.1.33

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/metrics.js CHANGED
@@ -236,22 +236,13 @@ 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
240
239
  const store = require('./store');
241
- const { encryptE2EE } = require('./e2ee');
242
- const roomId = this.conn?.roomId || store.get('roomId');
240
+ const agentId = this.conn?.agentId || store.get('agentId');
243
241
 
244
- if (roomId) {
245
- const encrypted = encryptE2EE(payload, roomId);
246
- this.conn.emit('agent:metrics', {
247
- agentId: this.conn.agentId || store.get('agentId'),
248
- ts: payload.ts,
249
- encrypted,
250
- e2ee: true
251
- });
252
- } else {
253
- this.conn.emit('agent:metrics', payload);
254
- }
242
+ this.conn.emit('agent:metrics', {
243
+ agentId,
244
+ ...payload
245
+ });
255
246
 
256
247
  } catch (err) {
257
248
  console.error('[metrics] Poll error:', err.message);
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,13 +83,19 @@ 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
101
  proc.onData(data => s.emit('shell:data', { sessionId, data }));
@@ -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 => s.emit('shell:data', { sessionId, data: 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) s.emit('shell:data', { sessionId, data: 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
153
  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() }));
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) s.emit('shell:data', { sessionId, data: 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,10 +166,12 @@ 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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinknagent",
3
- "version": "0.1.31",
3
+ "version": "0.1.33",
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>",