@yeaft/webchat-agent 1.0.524 → 1.0.526

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.524",
3
+ "version": "1.0.526",
4
4
  "description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/service/linux.js CHANGED
@@ -12,17 +12,30 @@ export function getSystemdServicePath(instanceId = DEFAULT_INSTANCE_ID) {
12
12
  return join(homedir(), '.config', 'systemd', 'user', `${getServiceName(instanceId)}.service`);
13
13
  }
14
14
 
15
+ // systemd unit values use C-style quoting and percent specifier expansion.
16
+ function quoteUnit(value, { executable = false } = {}) {
17
+ let escaped = String(value).replace(/%/g, '%%');
18
+ if (executable) escaped = escaped.replace(/\$/g, '$$$$');
19
+ return JSON.stringify(escaped);
20
+ }
21
+
22
+ function unitPath(value) {
23
+ const path = String(value);
24
+ if (/[\r\n\0]/.test(path)) throw new Error('Service paths cannot contain line breaks or NUL');
25
+ return path.replace(/%/g, '%%');
26
+ }
27
+
15
28
  export function generateSystemdUnit(config) {
16
29
  const nodePath = getNodePath();
17
30
  const cliPath = getCliPath();
18
31
  const logDir = getLogDir(config.instanceId);
19
32
  const envLines = [];
20
- if (config.instanceId) envLines.push(`Environment=YEAFT_AGENT_INSTANCE=${config.instanceId}`);
21
- if (config.serverUrl) envLines.push(`Environment=SERVER_URL=${config.serverUrl}`);
22
- if (config.agentName) envLines.push(`Environment=AGENT_NAME=${config.agentName}`);
23
- if (config.agentSecret) envLines.push(`Environment=AGENT_SECRET=${config.agentSecret}`);
24
- if (config.workDir) envLines.push(`Environment=WORK_DIR=${config.workDir}`);
25
- if (config.yeaftDir) envLines.push(`Environment=YEAFT_DIR=${config.yeaftDir}`);
33
+ if (config.instanceId) envLines.push(`Environment=${quoteUnit(`YEAFT_AGENT_INSTANCE=${config.instanceId}`)}`);
34
+ if (config.serverUrl) envLines.push(`Environment=${quoteUnit(`SERVER_URL=${config.serverUrl}`)}`);
35
+ if (config.agentName) envLines.push(`Environment=${quoteUnit(`AGENT_NAME=${config.agentName}`)}`);
36
+ if (config.agentSecret) envLines.push(`Environment=${quoteUnit(`AGENT_SECRET=${config.agentSecret}`)}`);
37
+ if (config.workDir) envLines.push(`Environment=${quoteUnit(`WORK_DIR=${config.workDir}`)}`);
38
+ if (config.yeaftDir) envLines.push(`Environment=${quoteUnit(`YEAFT_DIR=${config.yeaftDir}`)}`);
26
39
 
27
40
  // Include node's bin dir in PATH for claude CLI access
28
41
  const nodeBinDir = dirname(nodePath);
@@ -34,17 +47,17 @@ Wants=network-online.target
34
47
 
35
48
  [Service]
36
49
  Type=simple
37
- ExecStart=${nodePath} ${cliPath}
38
- WorkingDirectory=${config.workDir || homedir()}
50
+ ExecStart=${quoteUnit(nodePath, { executable: true })} ${quoteUnit(cliPath, { executable: true })}
51
+ WorkingDirectory=${unitPath(config.workDir || homedir())}
39
52
  Restart=on-failure
40
53
  RestartSec=10
41
54
  KillMode=mixed
42
55
  TimeoutStopSec=15
43
56
  ${envLines.join('\n')}
44
- Environment=PATH=${nodeBinDir}:${homedir()}/.local/bin:${homedir()}/.npm-global/bin:/usr/local/bin:/usr/bin:/bin
57
+ Environment=${quoteUnit(`PATH=${nodeBinDir}:${homedir()}/.local/bin:${homedir()}/.npm-global/bin:/usr/local/bin:/usr/bin:/bin`)}
45
58
 
46
- StandardOutput=append:${logDir}/out.log
47
- StandardError=append:${logDir}/error.log
59
+ StandardOutput=append:${unitPath(`${logDir}/out.log`)}
60
+ StandardError=append:${unitPath(`${logDir}/error.log`)}
48
61
 
49
62
  [Install]
50
63
  WantedBy=default.target
package/service/macos.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * Service — macOS (launchd) platform implementation
3
3
  */
4
- import { execSync } from 'child_process';
4
+ import { execFileSync, execSync } from 'child_process';
5
5
  import { existsSync, writeFileSync, unlinkSync, mkdirSync } from 'fs';
6
6
  import { join } from 'path';
7
7
  import { homedir } from 'os';
@@ -12,33 +12,37 @@ export function getLaunchdPlistPath(instanceId = DEFAULT_INSTANCE_ID) {
12
12
  return join(homedir(), 'Library', 'LaunchAgents', `${getLaunchdLabel(instanceId)}.plist`);
13
13
  }
14
14
 
15
- function generateLaunchdPlist(config) {
15
+ const xmlText = value => String(value).replace(/[&<>"']/g, char => ({
16
+ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&apos;',
17
+ })[char]);
18
+
19
+ export function generateLaunchdPlist(config) {
16
20
  const nodePath = getNodePath();
17
21
  const cliPath = getCliPath();
18
22
  const logDir = getLogDir(config.instanceId);
19
23
  const label = getLaunchdLabel(config.instanceId);
20
24
 
21
25
  const envDict = [];
22
- if (config.instanceId) envDict.push(` <key>YEAFT_AGENT_INSTANCE</key>\n <string>${config.instanceId}</string>`);
23
- if (config.serverUrl) envDict.push(` <key>SERVER_URL</key>\n <string>${config.serverUrl}</string>`);
24
- if (config.agentName) envDict.push(` <key>AGENT_NAME</key>\n <string>${config.agentName}</string>`);
25
- if (config.agentSecret) envDict.push(` <key>AGENT_SECRET</key>\n <string>${config.agentSecret}</string>`);
26
- if (config.workDir) envDict.push(` <key>WORK_DIR</key>\n <string>${config.workDir}</string>`);
27
- if (config.yeaftDir) envDict.push(` <key>YEAFT_DIR</key>\n <string>${config.yeaftDir}</string>`);
26
+ if (config.instanceId) envDict.push(` <key>YEAFT_AGENT_INSTANCE</key>\n <string>${xmlText(config.instanceId)}</string>`);
27
+ if (config.serverUrl) envDict.push(` <key>SERVER_URL</key>\n <string>${xmlText(config.serverUrl)}</string>`);
28
+ if (config.agentName) envDict.push(` <key>AGENT_NAME</key>\n <string>${xmlText(config.agentName)}</string>`);
29
+ if (config.agentSecret) envDict.push(` <key>AGENT_SECRET</key>\n <string>${xmlText(config.agentSecret)}</string>`);
30
+ if (config.workDir) envDict.push(` <key>WORK_DIR</key>\n <string>${xmlText(config.workDir)}</string>`);
31
+ if (config.yeaftDir) envDict.push(` <key>YEAFT_DIR</key>\n <string>${xmlText(config.yeaftDir)}</string>`);
28
32
 
29
33
  return `<?xml version="1.0" encoding="UTF-8"?>
30
34
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
31
35
  <plist version="1.0">
32
36
  <dict>
33
37
  <key>Label</key>
34
- <string>${label}</string>
38
+ <string>${xmlText(label)}</string>
35
39
  <key>ProgramArguments</key>
36
40
  <array>
37
- <string>${nodePath}</string>
38
- <string>${cliPath}</string>
41
+ <string>${xmlText(nodePath)}</string>
42
+ <string>${xmlText(cliPath)}</string>
39
43
  </array>
40
44
  <key>WorkingDirectory</key>
41
- <string>${config.workDir || homedir()}</string>
45
+ <string>${xmlText(config.workDir || homedir())}</string>
42
46
  <key>EnvironmentVariables</key>
43
47
  <dict>
44
48
  ${envDict.join('\n')}
@@ -53,9 +57,9 @@ ${envDict.join('\n')}
53
57
  <key>ThrottleInterval</key>
54
58
  <integer>10</integer>
55
59
  <key>StandardOutPath</key>
56
- <string>${logDir}/out.log</string>
60
+ <string>${xmlText(`${logDir}/out.log`)}</string>
57
61
  <key>StandardErrorPath</key>
58
- <string>${logDir}/error.log</string>
62
+ <string>${xmlText(`${logDir}/error.log`)}</string>
59
63
  </dict>
60
64
  </plist>
61
65
  `;
@@ -68,10 +72,10 @@ export function macInstall(config) {
68
72
  mkdirSync(getLogDir(config.instanceId), { recursive: true });
69
73
  // Unload first if exists
70
74
  if (existsSync(plistPath)) {
71
- try { execSync(`launchctl unload ${plistPath} 2>/dev/null`); } catch {}
75
+ try { execFileSync('launchctl', ['unload', plistPath], { stdio: 'pipe' }); } catch {}
72
76
  }
73
77
  writeFileSync(plistPath, generateLaunchdPlist(config));
74
- execSync(`launchctl load ${plistPath}`);
78
+ execFileSync('launchctl', ['load', plistPath]);
75
79
  console.log(`Service installed and started: ${getLaunchdLabel(config.instanceId)}`);
76
80
  console.log(`\nManage with:`);
77
81
  console.log(` yeaft-agent status --name ${config.instanceId}`);
@@ -83,7 +87,7 @@ export function macInstall(config) {
83
87
  export function macUninstall(instanceId = DEFAULT_INSTANCE_ID) {
84
88
  const plistPath = getLaunchdPlistPath(instanceId);
85
89
  if (existsSync(plistPath)) {
86
- try { execSync(`launchctl unload ${plistPath}`); } catch {}
90
+ try { execFileSync('launchctl', ['unload', plistPath]); } catch {}
87
91
  unlinkSync(plistPath);
88
92
  }
89
93
  console.log(`Service uninstalled: ${getLaunchdLabel(instanceId)}`);
@@ -95,14 +99,14 @@ export function macStart(instanceId = DEFAULT_INSTANCE_ID) {
95
99
  console.error('Service not installed. Run "yeaft-agent install" first.');
96
100
  process.exit(1);
97
101
  }
98
- execSync(`launchctl load ${plistPath}`);
102
+ execFileSync('launchctl', ['load', plistPath]);
99
103
  console.log(`Service started: ${getLaunchdLabel(instanceId)}`);
100
104
  }
101
105
 
102
106
  export function macStop(instanceId = DEFAULT_INSTANCE_ID) {
103
107
  const plistPath = getLaunchdPlistPath(instanceId);
104
108
  if (existsSync(plistPath)) {
105
- execSync(`launchctl unload ${plistPath}`);
109
+ execFileSync('launchctl', ['unload', plistPath]);
106
110
  }
107
111
  console.log(`Service stopped: ${getLaunchdLabel(instanceId)}`);
108
112
  }
@@ -26,7 +26,7 @@ export function getEcosystemPath(instanceId = DEFAULT_INSTANCE_ID) {
26
26
  return join(getConfigDir(instanceId), 'ecosystem.config.cjs');
27
27
  }
28
28
 
29
- function generateEcosystem(config) {
29
+ export function generateEcosystem(config) {
30
30
  const nodePath = getNodePath();
31
31
  const cliPath = getCliPath();
32
32
  const cliDir = dirname(cliPath);
@@ -43,18 +43,18 @@ function generateEcosystem(config) {
43
43
 
44
44
  return `module.exports = {
45
45
  apps: [{
46
- name: '${pm2AppName}',
47
- script: '${cliPath.replace(/\\/g, '\\\\')}',
48
- interpreter: '${nodePath.replace(/\\/g, '\\\\')}',
49
- cwd: '${cliDir.replace(/\\/g, '\\\\')}',
46
+ name: ${JSON.stringify(pm2AppName)},
47
+ script: ${JSON.stringify(cliPath)},
48
+ interpreter: ${JSON.stringify(nodePath)},
49
+ cwd: ${JSON.stringify(cliDir)},
50
50
  env: ${JSON.stringify(env, null, 6)},
51
51
  autorestart: true,
52
52
  watch: false,
53
53
  max_restarts: 10,
54
54
  restart_delay: 5000,
55
55
  log_date_format: 'YYYY-MM-DD HH:mm:ss',
56
- error_file: '${join(logDir, 'error.log').replace(/\\/g, '\\\\')}',
57
- out_file: '${join(logDir, 'out.log').replace(/\\/g, '\\\\')}',
56
+ error_file: ${JSON.stringify(join(logDir, 'error.log'))},
57
+ out_file: ${JSON.stringify(join(logDir, 'out.log'))},
58
58
  merge_logs: true,
59
59
  max_memory_restart: '500M',
60
60
  }]
@@ -105,6 +105,7 @@ export function winInstall(config) {
105
105
  // pm2-startup doesn't work well on Windows, use Startup folder approach
106
106
  const trayScript = join(dirname(getCliPath()), 'scripts', 'agent-tray.ps1');
107
107
  const startupDir = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
108
+ mkdirSync(startupDir, { recursive: true });
108
109
  const startupBat = join(startupDir, `${pm2AppName}.bat`);
109
110
  // Resurrect pm2 processes + launch tray icon
110
111
  let batContent = `@echo off\r\npm2 resurrect\r\n`;
package/yeaft/engine.js CHANGED
@@ -2924,6 +2924,10 @@ export class Engine {
2924
2924
  recentTurnCap: Math.max(10, requestConfig.yeaft?.recentTurnsLimit ?? 10),
2925
2925
  relatedTurnCap: Math.min(5, requestConfig.yeaft?.relatedTurnsLimit ?? 5),
2926
2926
  messageTokenBudget: historyBudget,
2927
+ // Completed turns contribute only visible user/assistant text. The
2928
+ // active turn starts at turnStartIdx and keeps its complete tool
2929
+ // protocol for every in-loop provider request.
2930
+ keepToolTurns: 0,
2927
2931
  currentTurnStartIndex: turnStartIdx,
2928
2932
  language: requestConfig.language,
2929
2933
  }) : null;
@@ -2931,6 +2935,7 @@ export class Engine {
2931
2935
  const requestHistory = buckets?.messages || (() => {
2932
2936
  const historical = trimSnapshotForBudget(conversationMessages.slice(0, turnStartIdx), {
2933
2937
  messageTokenBudget: historyBudget,
2938
+ keepToolTurns: 0,
2934
2939
  language: requestConfig.language,
2935
2940
  });
2936
2941
  historyMessageCount = historical.length;