@yeaft/webchat-agent 0.0.4 → 0.0.6

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.
Files changed (3) hide show
  1. package/connection.js +50 -31
  2. package/package.json +1 -1
  3. package/workbench.js +61 -30
package/connection.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import WebSocket from 'ws';
2
- import { execSync } from 'child_process';
2
+ import { execSync, execFile } from 'child_process';
3
3
  import ctx from './context.js';
4
4
  import { encrypt, decrypt, isEncrypted, decodeKey } from './encryption.js';
5
5
  import { handleTerminalCreate, handleTerminalInput, handleTerminalResize, handleTerminalClose } from './terminal.js';
@@ -267,37 +267,56 @@ async function handleMessage(msg) {
267
267
  break;
268
268
 
269
269
  case 'upgrade_agent':
270
- console.log('[Agent] Upgrade requested, running npm upgrade...');
271
- try {
272
- const pkgName = ctx.pkgName || '@yeaft/webchat-agent';
273
- execSync(`npm install -g ${pkgName}@latest`, { stdio: 'pipe' });
274
- console.log('[Agent] Upgrade successful, restarting...');
275
- sendToServer({ type: 'upgrade_agent_ack', success: true });
276
- // Restart after upgrade (same as restart_agent)
277
- setTimeout(() => {
278
- for (const [, term] of ctx.terminals) {
279
- if (term.pty) { try { term.pty.kill(); } catch {} }
280
- if (term.timer) clearTimeout(term.timer);
270
+ console.log('[Agent] Upgrade requested, checking for updates...');
271
+ (async () => {
272
+ try {
273
+ const pkgName = ctx.pkgName || '@yeaft/webchat-agent';
274
+ // Check latest version (async to avoid blocking heartbeat)
275
+ const latestVersion = await new Promise((resolve, reject) => {
276
+ execFile('npm', ['view', pkgName, 'version'], { stdio: 'pipe' }, (err, stdout) => {
277
+ if (err) reject(err); else resolve(stdout.toString().trim());
278
+ });
279
+ });
280
+ if (latestVersion === ctx.agentVersion) {
281
+ console.log(`[Agent] Already at latest version (${ctx.agentVersion}), skipping upgrade.`);
282
+ sendToServer({ type: 'upgrade_agent_ack', success: true, alreadyLatest: true, version: ctx.agentVersion });
283
+ return;
281
284
  }
282
- ctx.terminals.clear();
283
- for (const [, state] of ctx.conversations) {
284
- if (state.abortController) state.abortController.abort();
285
- if (state.inputStream) state.inputStream.done();
286
- }
287
- ctx.conversations.clear();
288
- stopAgentHeartbeat();
289
- if (ctx.ws) {
290
- ctx.ws.removeAllListeners('close');
291
- ctx.ws.close();
292
- }
293
- clearTimeout(ctx.reconnectTimer);
294
- console.log('[Agent] Cleanup done, exiting for auto-restart...');
295
- process.exit(1);
296
- }, 500);
297
- } catch (e) {
298
- console.error('[Agent] Upgrade failed:', e.message);
299
- sendToServer({ type: 'upgrade_agent_ack', success: false, error: e.message });
300
- }
285
+ console.log(`[Agent] Upgrading from ${ctx.agentVersion} to ${latestVersion}...`);
286
+ // Use async execFile to avoid blocking event loop (heartbeat must keep running)
287
+ await new Promise((resolve, reject) => {
288
+ execFile('npm', ['install', '-g', `${pkgName}@latest`], { stdio: 'pipe' }, (err) => {
289
+ if (err) reject(err); else resolve();
290
+ });
291
+ });
292
+ console.log('[Agent] Upgrade successful, restarting...');
293
+ sendToServer({ type: 'upgrade_agent_ack', success: true, version: latestVersion });
294
+ // Restart after upgrade (same as restart_agent)
295
+ setTimeout(() => {
296
+ for (const [, term] of ctx.terminals) {
297
+ if (term.pty) { try { term.pty.kill(); } catch {} }
298
+ if (term.timer) clearTimeout(term.timer);
299
+ }
300
+ ctx.terminals.clear();
301
+ for (const [, state] of ctx.conversations) {
302
+ if (state.abortController) state.abortController.abort();
303
+ if (state.inputStream) state.inputStream.done();
304
+ }
305
+ ctx.conversations.clear();
306
+ stopAgentHeartbeat();
307
+ if (ctx.ws) {
308
+ ctx.ws.removeAllListeners('close');
309
+ ctx.ws.close();
310
+ }
311
+ clearTimeout(ctx.reconnectTimer);
312
+ console.log('[Agent] Cleanup done, exiting for auto-restart...');
313
+ process.exit(1);
314
+ }, 500);
315
+ } catch (e) {
316
+ console.error('[Agent] Upgrade failed:', e.message);
317
+ sendToServer({ type: 'upgrade_agent_ack', success: false, error: e.message });
318
+ }
319
+ })();
301
320
  break;
302
321
  }
303
322
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/workbench.js CHANGED
@@ -31,6 +31,19 @@ export function validateGitPath(filePath) {
31
31
  return filePath && !/[`$;|&><!\n\r]/.test(filePath);
32
32
  }
33
33
 
34
+ // Binary file extensions → MIME type mapping
35
+ const BINARY_EXTENSIONS = {
36
+ '.pdf': 'application/pdf',
37
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
38
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
39
+ '.xls': 'application/vnd.ms-excel',
40
+ '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
41
+ '.ppt': 'application/vnd.ms-powerpoint',
42
+ '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
43
+ '.gif': 'image/gif', '.webp': 'image/webp', '.bmp': 'image/bmp',
44
+ '.ico': 'image/x-icon'
45
+ };
46
+
34
47
  export async function handleReadFile(msg) {
35
48
  const { conversationId, filePath, _requestUserId } = msg;
36
49
  console.log('[Agent] handleReadFile received:', { filePath, conversationId, workDir: msg.workDir });
@@ -39,37 +52,55 @@ export async function handleReadFile(msg) {
39
52
 
40
53
  try {
41
54
  const resolved = resolveAndValidatePath(filePath, workDir);
42
- const content = await readFile(resolved, 'utf-8');
43
-
44
- // 检测语言
45
55
  const ext = extname(resolved).toLowerCase();
46
- const langMap = {
47
- '.js': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript',
48
- '.ts': 'javascript', '.tsx': 'javascript', '.jsx': 'javascript',
49
- '.py': 'python', '.pyw': 'python',
50
- '.html': 'htmlmixed', '.htm': 'htmlmixed',
51
- '.css': 'css', '.scss': 'css', '.less': 'css',
52
- '.json': 'javascript',
53
- '.md': 'markdown',
54
- '.sh': 'shell', '.bash': 'shell', '.zsh': 'shell',
55
- '.cs': 'text/x-csharp', '.java': 'text/x-java',
56
- '.cpp': 'text/x-c++src', '.c': 'text/x-csrc', '.h': 'text/x-csrc',
57
- '.xml': 'xml', '.svg': 'xml',
58
- '.yaml': 'yaml', '.yml': 'yaml',
59
- '.sql': 'sql',
60
- '.go': 'go', '.rs': 'rust', '.rb': 'ruby',
61
- '.php': 'php', '.swift': 'swift'
62
- };
63
-
64
- console.log('[Agent] Sending file_content:', { filePath: resolved, contentLen: content.length, conversationId });
65
- ctx.sendToServer({
66
- type: 'file_content',
67
- conversationId,
68
- _requestUserId,
69
- filePath: resolved,
70
- content,
71
- language: langMap[ext] || null
72
- });
56
+ const mimeType = BINARY_EXTENSIONS[ext];
57
+
58
+ if (mimeType) {
59
+ // Binary file: read as Buffer, send base64
60
+ const buffer = await readFile(resolved);
61
+ console.log('[Agent] Sending binary file_content:', { filePath: resolved, size: buffer.length, mimeType, conversationId });
62
+ ctx.sendToServer({
63
+ type: 'file_content',
64
+ conversationId,
65
+ _requestUserId,
66
+ filePath: resolved,
67
+ content: buffer.toString('base64'),
68
+ binary: true,
69
+ mimeType
70
+ });
71
+ } else {
72
+ // Text file: read as utf-8
73
+ const content = await readFile(resolved, 'utf-8');
74
+
75
+ // 检测语言
76
+ const langMap = {
77
+ '.js': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript',
78
+ '.ts': 'javascript', '.tsx': 'javascript', '.jsx': 'javascript',
79
+ '.py': 'python', '.pyw': 'python',
80
+ '.html': 'htmlmixed', '.htm': 'htmlmixed',
81
+ '.css': 'css', '.scss': 'css', '.less': 'css',
82
+ '.json': 'javascript',
83
+ '.md': 'markdown',
84
+ '.sh': 'shell', '.bash': 'shell', '.zsh': 'shell',
85
+ '.cs': 'text/x-csharp', '.java': 'text/x-java',
86
+ '.cpp': 'text/x-c++src', '.c': 'text/x-csrc', '.h': 'text/x-csrc',
87
+ '.xml': 'xml', '.svg': 'xml',
88
+ '.yaml': 'yaml', '.yml': 'yaml',
89
+ '.sql': 'sql',
90
+ '.go': 'go', '.rs': 'rust', '.rb': 'ruby',
91
+ '.php': 'php', '.swift': 'swift'
92
+ };
93
+
94
+ console.log('[Agent] Sending file_content:', { filePath: resolved, contentLen: content.length, conversationId });
95
+ ctx.sendToServer({
96
+ type: 'file_content',
97
+ conversationId,
98
+ _requestUserId,
99
+ filePath: resolved,
100
+ content,
101
+ language: langMap[ext] || null
102
+ });
103
+ }
73
104
  } catch (e) {
74
105
  ctx.sendToServer({
75
106
  type: 'file_content',