@yeaft/webchat-agent 1.0.356 → 1.0.358

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.
@@ -0,0 +1,219 @@
1
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
2
+ import { execSync } from 'child_process';
3
+ import { homedir, platform } from 'os';
4
+ import { join, dirname } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import {
7
+ getConfigDir,
8
+ getDefaultYeaftDir,
9
+ getNodePath,
10
+ resolveServiceInstanceId,
11
+ resolveYeaftDir,
12
+ validateInstanceId,
13
+ } from './service/config.js';
14
+
15
+ const LOCAL_SERVICE_PREFIX = 'yeaft-local';
16
+ const DEFAULT_PORT = 6868;
17
+ const SYSTEMD_CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/;
18
+
19
+ /** Parse local service identity, runtime port, and resolved persistent data root. */
20
+ export function parseLocalServiceArgs(args, env = process.env, options = {}) {
21
+ const parsed = {
22
+ name: resolveServiceInstanceId(args, env),
23
+ port: DEFAULT_PORT,
24
+ yeaftDir: null,
25
+ };
26
+ for (let index = 0; index < args.length; index += 1) {
27
+ const arg = args[index];
28
+ const value = args[index + 1];
29
+ if (arg === '--name' || arg === '--instance' || arg === '--yeaft-dir') {
30
+ if (!value || value.startsWith('-')) throw new Error(`${arg} requires a value`);
31
+ index += 1;
32
+ } else if (arg === '--port') {
33
+ if (!value || value.startsWith('-')) throw new Error('--port requires a value');
34
+ if (!/^\d+$/.test(value)) throw new Error(`Invalid port: ${value}`);
35
+ parsed.port = Number(value);
36
+ if (parsed.port < 1 || parsed.port > 65535) throw new Error(`Invalid port: ${value}`);
37
+ index += 1;
38
+ } else {
39
+ throw new Error(`Unknown local service option: ${arg}`);
40
+ }
41
+ }
42
+ parsed.name = validateInstanceId(parsed.name);
43
+ const existing = options.existing || null;
44
+ const hasExplicitYeaftDir = args.includes('--yeaft-dir') || Boolean(env.YEAFT_DIR);
45
+ parsed.yeaftDir = hasExplicitYeaftDir
46
+ ? resolveYeaftDir(args, env, parsed.name)
47
+ : existing?.yeaftDir || getDefaultYeaftDir(parsed.name);
48
+ assertSystemdValue(parsed.yeaftDir, 'Yeaft data directory');
49
+ return parsed;
50
+ }
51
+
52
+ function assertSystemdValue(value, label) {
53
+ if (SYSTEMD_CONTROL_CHARACTERS.test(String(value))) {
54
+ throw new Error(`${label} cannot contain control characters when installing a systemd service`);
55
+ }
56
+ }
57
+
58
+ export function getLocalServiceName(name) {
59
+ return `${LOCAL_SERVICE_PREFIX}@${validateInstanceId(name)}`;
60
+ }
61
+
62
+ export function getLocalServiceConfigPath(name) {
63
+ return join(getConfigDir(name), 'local.json');
64
+ }
65
+
66
+ export function getLocalSystemdServicePath(name) {
67
+ return join(homedir(), '.config', 'systemd', 'user', `${getLocalServiceName(name)}.service`);
68
+ }
69
+
70
+ export function getLocalSystemdUnitPath(name) {
71
+ return getLocalSystemdServicePath(name);
72
+ }
73
+
74
+ function shellQuote(value) {
75
+ return `'${String(value).replaceAll("'", "'\\''")}'`;
76
+ }
77
+
78
+ function systemdEscape(value) {
79
+ assertSystemdValue(value, 'systemd unit value');
80
+ return String(value)
81
+ .replaceAll('%', '%%')
82
+ .replaceAll('\\', '\\\\')
83
+ .replaceAll('"', '\\"');
84
+ }
85
+
86
+ /** Read the persisted local service settings, including its resolved data root. */
87
+ export function readLocalServiceConfig(name) {
88
+ const path = getLocalServiceConfigPath(name);
89
+ if (!existsSync(path)) return null;
90
+ try {
91
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
92
+ if (!parsed || typeof parsed !== 'object') return null;
93
+ const configName = validateInstanceId(parsed.name || name);
94
+ return {
95
+ name: configName,
96
+ port: Number(parsed.port) || DEFAULT_PORT,
97
+ yeaftDir: typeof parsed.yeaftDir === 'string' && parsed.yeaftDir
98
+ ? parsed.yeaftDir
99
+ : getDefaultYeaftDir(configName),
100
+ };
101
+ } catch {
102
+ return null;
103
+ }
104
+ }
105
+
106
+ /** Persist the local service settings selected at install time. */
107
+ export function writeLocalServiceConfig(config) {
108
+ const path = getLocalServiceConfigPath(config.name);
109
+ mkdirSync(dirname(path), { recursive: true });
110
+ writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`);
111
+ }
112
+
113
+ function requireLocalServiceConfig(name) {
114
+ const config = readLocalServiceConfig(name);
115
+ if (!config) {
116
+ throw new Error(`Local service not installed for ${name}. Run "yeaft-agent local install --name ${name}" first.`);
117
+ }
118
+ return config;
119
+ }
120
+
121
+ export function generateLocalSystemdUnit(config, options = {}) {
122
+ const nodePath = getNodePath();
123
+ const cliPath = options.cliPath || join(dirname(fileURLToPath(import.meta.url)), 'cli.js');
124
+ const localDataDir = options.dataDir || join(homedir(), '.yeaft', 'server');
125
+ const yeaftDir = config.yeaftDir || getDefaultYeaftDir(config.name);
126
+ const logDir = join(getConfigDir(config.name), 'logs');
127
+ const workingDirectory = options.workingDirectory || dirname(cliPath);
128
+ const command = [
129
+ shellQuote(nodePath),
130
+ shellQuote(cliPath),
131
+ 'local',
132
+ '--name',
133
+ shellQuote(config.name),
134
+ '--port',
135
+ String(config.port),
136
+ ].join(' ');
137
+ return `[Unit]
138
+ Description=Yeaft Local Web UI (${config.name})
139
+ After=network-online.target
140
+ Wants=network-online.target
141
+
142
+ [Service]
143
+ Type=simple
144
+ ExecStart=${command}
145
+ Restart=on-failure
146
+ RestartSec=5
147
+ WorkingDirectory=${workingDirectory}
148
+ Environment="YEAFT_LOCAL_RUN=true"
149
+ Environment="YEAFT_AGENT_INSTANCE=${systemdEscape(config.name)}"
150
+ Environment="YEAFT_DIR=${systemdEscape(yeaftDir)}"
151
+ Environment="SERVER_DATA_DIR=${systemdEscape(localDataDir)}"
152
+ StandardOutput=append:${logDir}/out.log
153
+ StandardError=append:${logDir}/error.log
154
+
155
+ [Install]
156
+ WantedBy=default.target
157
+ `;
158
+ }
159
+
160
+ function installLinux(config) {
161
+ const path = getLocalSystemdServicePath(config.name);
162
+ const logDir = join(getConfigDir(config.name), 'logs');
163
+ mkdirSync(dirname(path), { recursive: true });
164
+ mkdirSync(logDir, { recursive: true });
165
+ writeFileSync(path, generateLocalSystemdUnit(config));
166
+ execSync('systemctl --user daemon-reload');
167
+ execSync(`systemctl --user enable --now ${getLocalServiceName(config.name)}`);
168
+ console.log(`Local service installed and started: ${getLocalServiceName(config.name)}`);
169
+ console.log(`Open http://127.0.0.1:${config.port}`);
170
+ console.log(`For boot without an active login session: sudo loginctl enable-linger $(whoami)`);
171
+ }
172
+
173
+ function uninstallLinux(name) {
174
+ const serviceName = getLocalServiceName(name);
175
+ const path = getLocalSystemdServicePath(name);
176
+ try { execSync(`systemctl --user disable --now ${serviceName}`, { stdio: 'ignore' }); } catch {}
177
+ if (existsSync(path)) unlinkSync(path);
178
+ try { execSync('systemctl --user daemon-reload'); } catch {}
179
+ console.log(`Local service uninstalled: ${serviceName}`);
180
+ }
181
+
182
+ function controlLinux(command, name) {
183
+ const serviceName = getLocalServiceName(name);
184
+ if (command === 'status') {
185
+ try { execSync(`systemctl --user status ${serviceName} --no-pager`, { stdio: 'inherit' }); } catch {}
186
+ return;
187
+ }
188
+ if (command === 'logs') {
189
+ execSync(`journalctl --user -u ${serviceName} -f --no-pager -n 100`, { stdio: 'inherit' });
190
+ return;
191
+ }
192
+ execSync(`systemctl --user ${command} ${serviceName}`, { stdio: 'inherit' });
193
+ const verb = command === 'start' ? 'started'
194
+ : command === 'stop' ? 'stopped'
195
+ : command === 'restart' ? 'restarted'
196
+ : command;
197
+ console.log(`Local service ${verb}: ${serviceName}`);
198
+ }
199
+
200
+ export async function handleLocalServiceCommand(command, args = []) {
201
+ const identity = parseLocalServiceArgs(args);
202
+ if (platform() !== 'linux') {
203
+ throw new Error(`Local managed service is currently supported on Linux only (current platform: ${platform()}). Use \`yeaft-agent local --background\` on this platform.`);
204
+ }
205
+ if (command === 'install') {
206
+ const config = parseLocalServiceArgs(args, process.env, {
207
+ existing: readLocalServiceConfig(identity.name),
208
+ });
209
+ writeLocalServiceConfig(config);
210
+ installLinux(config);
211
+ return;
212
+ }
213
+ if (command === 'uninstall') {
214
+ uninstallLinux(identity.name);
215
+ return;
216
+ }
217
+ const config = requireLocalServiceConfig(identity.name);
218
+ controlLinux(command, config.name);
219
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.356",
3
+ "version": "1.0.358",
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/config.js CHANGED
@@ -108,6 +108,19 @@ export function resolveServiceInstanceId(args = [], env = process.env, options =
108
108
  return validateInstanceId(options.fallbackName || getDefaultAgentName());
109
109
  }
110
110
 
111
+ /** Resolve the CLI/env/default Yeaft data root for one Agent instance. */
112
+ export function resolveYeaftDir(args = [], env = process.env, instanceId = DEFAULT_INSTANCE_ID) {
113
+ let explicitYeaftDir = null;
114
+ for (let index = 0; index < args.length; index += 1) {
115
+ if (args[index] !== '--yeaft-dir') continue;
116
+ const value = args[index + 1];
117
+ if (!value || value.startsWith('-')) throw new Error('--yeaft-dir requires a value');
118
+ explicitYeaftDir = value;
119
+ index += 1;
120
+ }
121
+ return explicitYeaftDir || env.YEAFT_DIR || getDefaultYeaftDir(instanceId);
122
+ }
123
+
111
124
  /** Legacy alias for resolveServiceInstanceId(). */
112
125
  export function getInstanceIdFromArgs(args = [], env = process.env, options = {}) {
113
126
  return resolveServiceInstanceId(args, env, options);
package/yeaft/models.js CHANGED
@@ -430,13 +430,17 @@ export const OPENAI_MAX_REASONING_EFFORT_OPTIONS = ['low', 'medium', 'high', 'xh
430
430
  export const ANTHROPIC_MANUAL_EFFORT_OPTIONS = ['low', 'medium', 'high'];
431
431
  export const ANTHROPIC_ADAPTIVE_EFFORT_OPTIONS = ['low', 'medium', 'high', 'xhigh', 'max'];
432
432
  export const ANTHROPIC_ADAPTIVE_MAX_EFFORT_OPTIONS = ['low', 'medium', 'high', 'max'];
433
- // DeepSeek reasoning models (deepseek-reasoner / deepseek-r1) expose a simple
434
- // low/medium/high effort scale. DeepSeek's OpenAI-compatible surface accepts a
435
- // reasoning effort hint; we send it through the standard openai-reasoning
436
- // `reasoning.effort` path (relay/proxy adapts to DeepSeek's wire format). No
437
- // `minimal` tier DeepSeek documents only a high/max effort distinction, so we
438
- // keep the user-facing scale to the three levels the user expects.
439
- export const DEEPSEEK_REASONING_EFFORT_OPTIONS = ['low', 'medium', 'high'];
433
+ // DeepSeek reasoning models (deepseek-reasoner / deepseek-r1 / deepseek-v4-pro)
434
+ // expose a graded effort scale. DeepSeek V4 documents "high" and "max" as the
435
+ // effective levels: "xhigh" is mapped to "max" server-side and "low"/"medium"
436
+ // are mapped to "high". There is no "minimal" tier. We keep the full user-facing
437
+ // scale (including xhigh/max) so the UI can select the highest level; the wire
438
+ // path differs by protocol:
439
+ // - OpenAI-compatible surface `reasoning.effort` (relay/proxy adapts to
440
+ // DeepSeek's wire format; xhigh/max pass through and the provider maps them).
441
+ // - Anthropic-compatible surface → `output_config.effort` (DeepSeek's Anthropic
442
+ // compatibility table explicitly supports `output_config` effort).
443
+ export const DEEPSEEK_REASONING_EFFORT_OPTIONS = ['low', 'medium', 'high', 'xhigh', 'max'];
440
444
 
441
445
  const VALID_EFFORT_OPTIONS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh', 'max']);
442
446