@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.
- package/cli.js +40 -7
- package/local-run.js +45 -6
- package/local-runtime/server/handlers/agent-output.js +8 -1
- package/local-runtime/server/ws-agent.js +22 -2
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +43 -44
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +2 -2
- package/local-runtime/web/style.bundle.css +1 -1
- package/local-runtime/web/style.bundle.css.gz +0 -0
- package/local-service.js +219 -0
- package/package.json +1 -1
- package/service/config.js +13 -0
- package/yeaft/models.js +11 -7
|
Binary file
|
package/local-service.js
ADDED
|
@@ -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
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
|
|
434
|
-
//
|
|
435
|
-
//
|
|
436
|
-
//
|
|
437
|
-
//
|
|
438
|
-
//
|
|
439
|
-
|
|
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
|
|