@yeaft/webchat-agent 0.1.1104 → 0.1.1107
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 +28 -17
- package/connection/index.js +6 -3
- package/connection/message-router.js +10 -6
- package/index.js +4 -2
- package/llm-config-cli.js +48 -0
- package/package.json +1 -1
- package/service/config.js +90 -15
- package/service/index.js +78 -40
- package/service/linux.js +48 -37
- package/service/macos.js +35 -30
- package/service/windows.js +46 -39
- package/service.js +10 -0
- package/terminal.js +14 -2
- package/yeaft/cli.js +7 -7
- package/yeaft/conversation/persist.js +28 -118
- package/yeaft/debug-trace.js +967 -695
- package/yeaft/engine.js +127 -96
- package/yeaft/llm/adapter.js +7 -3
- package/yeaft/llm/anthropic.js +56 -15
- package/yeaft/llm/openai-responses.js +22 -12
- package/yeaft/llm/router.js +16 -2
- package/yeaft/models.js +15 -2
- package/yeaft/session.js +9 -17
- package/yeaft/sessions/session-crud.js +42 -23
- package/yeaft/systemd-scope.js +88 -0
- package/yeaft/tasks/manager.js +81 -13
- package/yeaft/tasks/shell-runner.js +11 -3
- package/yeaft/tools/bash.js +9 -2
- package/yeaft/vp-status-broker.js +8 -3
- package/yeaft/web-bridge.js +221 -24
package/service/linux.js
CHANGED
|
@@ -1,31 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
/*
|
|
2
2
|
* Service — Linux (systemd) platform implementation
|
|
3
3
|
*/
|
|
4
4
|
import { execSync } from 'child_process';
|
|
5
5
|
import { existsSync, writeFileSync, unlinkSync, mkdirSync } from 'fs';
|
|
6
6
|
import { join, dirname } from 'path';
|
|
7
7
|
import { homedir } from 'os';
|
|
8
|
-
import {
|
|
8
|
+
import { getServiceName, getLogDir, getNodePath, getCliPath, DEFAULT_INSTANCE_ID } from './config.js';
|
|
9
9
|
|
|
10
10
|
/** Pure path getter — no side effects (no directory creation). */
|
|
11
|
-
export function getSystemdServicePath() {
|
|
12
|
-
return join(homedir(), '.config', 'systemd', 'user', `${
|
|
11
|
+
export function getSystemdServicePath(instanceId = DEFAULT_INSTANCE_ID) {
|
|
12
|
+
return join(homedir(), '.config', 'systemd', 'user', `${getServiceName(instanceId)}.service`);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
function generateSystemdUnit(config) {
|
|
16
16
|
const nodePath = getNodePath();
|
|
17
17
|
const cliPath = getCliPath();
|
|
18
|
+
const logDir = getLogDir(config.instanceId);
|
|
18
19
|
const envLines = [];
|
|
20
|
+
if (config.instanceId) envLines.push(`Environment=YEAFT_AGENT_INSTANCE=${config.instanceId}`);
|
|
19
21
|
if (config.serverUrl) envLines.push(`Environment=SERVER_URL=${config.serverUrl}`);
|
|
20
22
|
if (config.agentName) envLines.push(`Environment=AGENT_NAME=${config.agentName}`);
|
|
21
23
|
if (config.agentSecret) envLines.push(`Environment=AGENT_SECRET=${config.agentSecret}`);
|
|
22
24
|
if (config.workDir) envLines.push(`Environment=WORK_DIR=${config.workDir}`);
|
|
25
|
+
if (config.yeaftDir) envLines.push(`Environment=YEAFT_DIR=${config.yeaftDir}`);
|
|
23
26
|
|
|
24
27
|
// Include node's bin dir in PATH for claude CLI access
|
|
25
28
|
const nodeBinDir = dirname(nodePath);
|
|
26
29
|
|
|
27
30
|
return `[Unit]
|
|
28
|
-
Description=Yeaft WebChat Agent
|
|
31
|
+
Description=Yeaft WebChat Agent (${config.instanceId || DEFAULT_INSTANCE_ID})
|
|
29
32
|
After=network-online.target
|
|
30
33
|
Wants=network-online.target
|
|
31
34
|
|
|
@@ -39,8 +42,8 @@ KillMode=process
|
|
|
39
42
|
${envLines.join('\n')}
|
|
40
43
|
Environment=PATH=${nodeBinDir}:${homedir()}/.local/bin:${homedir()}/.npm-global/bin:/usr/local/bin:/usr/bin:/bin
|
|
41
44
|
|
|
42
|
-
StandardOutput=append:${
|
|
43
|
-
StandardError=append:${
|
|
45
|
+
StandardOutput=append:${logDir}/out.log
|
|
46
|
+
StandardError=append:${logDir}/error.log
|
|
44
47
|
|
|
45
48
|
[Install]
|
|
46
49
|
WantedBy=default.target
|
|
@@ -48,61 +51,68 @@ WantedBy=default.target
|
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
export function linuxInstall(config) {
|
|
51
|
-
const
|
|
54
|
+
const serviceName = getServiceName(config.instanceId);
|
|
55
|
+
const servicePath = getSystemdServicePath(config.instanceId);
|
|
52
56
|
// Ensure systemd user directory exists before writing
|
|
53
57
|
mkdirSync(join(homedir(), '.config', 'systemd', 'user'), { recursive: true });
|
|
58
|
+
mkdirSync(getLogDir(config.instanceId), { recursive: true });
|
|
54
59
|
writeFileSync(servicePath, generateSystemdUnit(config));
|
|
55
60
|
execSync('systemctl --user daemon-reload');
|
|
56
|
-
execSync(`systemctl --user enable ${
|
|
57
|
-
execSync(`systemctl --user start ${
|
|
58
|
-
console.log(`Service installed and started
|
|
61
|
+
execSync(`systemctl --user enable ${serviceName}`);
|
|
62
|
+
execSync(`systemctl --user start ${serviceName}`);
|
|
63
|
+
console.log(`Service installed and started: ${serviceName}`);
|
|
59
64
|
console.log(`\nManage with:`);
|
|
60
|
-
console.log(` yeaft-agent status`);
|
|
61
|
-
console.log(` yeaft-agent logs`);
|
|
62
|
-
console.log(` yeaft-agent restart`);
|
|
63
|
-
console.log(` yeaft-agent uninstall`);
|
|
65
|
+
console.log(` yeaft-agent status --instance ${config.instanceId}`);
|
|
66
|
+
console.log(` yeaft-agent logs --instance ${config.instanceId}`);
|
|
67
|
+
console.log(` yeaft-agent restart --instance ${config.instanceId}`);
|
|
68
|
+
console.log(` yeaft-agent uninstall --instance ${config.instanceId}`);
|
|
64
69
|
console.log(`\nTo run when not logged in:`);
|
|
65
70
|
console.log(` sudo loginctl enable-linger $(whoami)`);
|
|
66
71
|
}
|
|
67
72
|
|
|
68
|
-
export function linuxUninstall() {
|
|
69
|
-
|
|
70
|
-
try { execSync(`systemctl --user
|
|
71
|
-
|
|
73
|
+
export function linuxUninstall(instanceId = DEFAULT_INSTANCE_ID) {
|
|
74
|
+
const serviceName = getServiceName(instanceId);
|
|
75
|
+
try { execSync(`systemctl --user stop ${serviceName} 2>/dev/null`); } catch {}
|
|
76
|
+
try { execSync(`systemctl --user disable ${serviceName} 2>/dev/null`); } catch {}
|
|
77
|
+
const servicePath = getSystemdServicePath(instanceId);
|
|
72
78
|
if (existsSync(servicePath)) unlinkSync(servicePath);
|
|
73
79
|
try { execSync('systemctl --user daemon-reload'); } catch {}
|
|
74
|
-
console.log(
|
|
80
|
+
console.log(`Service uninstalled: ${serviceName}`);
|
|
75
81
|
}
|
|
76
82
|
|
|
77
|
-
export function linuxStart() {
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
export function linuxStart(instanceId = DEFAULT_INSTANCE_ID) {
|
|
84
|
+
const serviceName = getServiceName(instanceId);
|
|
85
|
+
execSync(`systemctl --user start ${serviceName}`, { stdio: 'inherit' });
|
|
86
|
+
console.log(`Service started: ${serviceName}`);
|
|
80
87
|
}
|
|
81
88
|
|
|
82
|
-
export function linuxStop() {
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
export function linuxStop(instanceId = DEFAULT_INSTANCE_ID) {
|
|
90
|
+
const serviceName = getServiceName(instanceId);
|
|
91
|
+
execSync(`systemctl --user stop ${serviceName}`, { stdio: 'inherit' });
|
|
92
|
+
console.log(`Service stopped: ${serviceName}`);
|
|
85
93
|
}
|
|
86
94
|
|
|
87
|
-
export function linuxRestart() {
|
|
88
|
-
|
|
89
|
-
|
|
95
|
+
export function linuxRestart(instanceId = DEFAULT_INSTANCE_ID) {
|
|
96
|
+
const serviceName = getServiceName(instanceId);
|
|
97
|
+
execSync(`systemctl --user restart ${serviceName}`, { stdio: 'inherit' });
|
|
98
|
+
console.log(`Service restarted: ${serviceName}`);
|
|
90
99
|
}
|
|
91
100
|
|
|
92
101
|
/**
|
|
93
102
|
* Query systemd for the current service status.
|
|
94
103
|
* Returns { running: boolean, pid: string|null }.
|
|
95
104
|
*/
|
|
96
|
-
export function getLinuxServiceStatus() {
|
|
105
|
+
export function getLinuxServiceStatus(instanceId = DEFAULT_INSTANCE_ID) {
|
|
106
|
+
const serviceName = getServiceName(instanceId);
|
|
97
107
|
try {
|
|
98
|
-
const output = execSync(`systemctl --user is-active ${
|
|
108
|
+
const output = execSync(`systemctl --user is-active ${serviceName}`, {
|
|
99
109
|
encoding: 'utf-8',
|
|
100
110
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
101
111
|
}).trim();
|
|
102
112
|
if (output === 'active') {
|
|
103
113
|
let pid = null;
|
|
104
114
|
try {
|
|
105
|
-
pid = execSync(`systemctl --user show ${
|
|
115
|
+
pid = execSync(`systemctl --user show ${serviceName} --property=MainPID --value`, {
|
|
106
116
|
encoding: 'utf-8',
|
|
107
117
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
108
118
|
}).trim();
|
|
@@ -116,20 +126,21 @@ export function getLinuxServiceStatus() {
|
|
|
116
126
|
}
|
|
117
127
|
}
|
|
118
128
|
|
|
119
|
-
export function linuxStatus() {
|
|
129
|
+
export function linuxStatus(instanceId = DEFAULT_INSTANCE_ID) {
|
|
120
130
|
try {
|
|
121
|
-
execSync(`systemctl --user status ${
|
|
131
|
+
execSync(`systemctl --user status ${getServiceName(instanceId)}`, { stdio: 'inherit' });
|
|
122
132
|
} catch {
|
|
123
133
|
// systemctl status returns non-zero when service is stopped
|
|
124
134
|
}
|
|
125
135
|
}
|
|
126
136
|
|
|
127
|
-
export function linuxLogs() {
|
|
137
|
+
export function linuxLogs(instanceId = DEFAULT_INSTANCE_ID) {
|
|
138
|
+
const serviceName = getServiceName(instanceId);
|
|
128
139
|
try {
|
|
129
|
-
execSync(`journalctl --user -u ${
|
|
140
|
+
execSync(`journalctl --user -u ${serviceName} -f --no-pager -n 100`, { stdio: 'inherit' });
|
|
130
141
|
} catch {
|
|
131
142
|
// Fallback to log files
|
|
132
|
-
const logFile = join(getLogDir(), 'out.log');
|
|
143
|
+
const logFile = join(getLogDir(instanceId), 'out.log');
|
|
133
144
|
if (existsSync(logFile)) {
|
|
134
145
|
execSync(`tail -f -n 100 ${logFile}`, { stdio: 'inherit' });
|
|
135
146
|
} else {
|
package/service/macos.js
CHANGED
|
@@ -1,34 +1,37 @@
|
|
|
1
|
-
|
|
1
|
+
/*
|
|
2
2
|
* Service — macOS (launchd) platform implementation
|
|
3
3
|
*/
|
|
4
4
|
import { 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';
|
|
8
|
-
import { getLogDir, getNodePath, getCliPath } from './config.js';
|
|
8
|
+
import { getLaunchdLabel, getLogDir, getNodePath, getCliPath, DEFAULT_INSTANCE_ID } from './config.js';
|
|
9
9
|
|
|
10
10
|
/** Pure path getter — no side effects (no directory creation). */
|
|
11
|
-
export function getLaunchdPlistPath() {
|
|
12
|
-
return join(homedir(), 'Library', 'LaunchAgents',
|
|
11
|
+
export function getLaunchdPlistPath(instanceId = DEFAULT_INSTANCE_ID) {
|
|
12
|
+
return join(homedir(), 'Library', 'LaunchAgents', `${getLaunchdLabel(instanceId)}.plist`);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
function generateLaunchdPlist(config) {
|
|
16
16
|
const nodePath = getNodePath();
|
|
17
17
|
const cliPath = getCliPath();
|
|
18
|
-
const logDir = getLogDir();
|
|
18
|
+
const logDir = getLogDir(config.instanceId);
|
|
19
|
+
const label = getLaunchdLabel(config.instanceId);
|
|
19
20
|
|
|
20
21
|
const envDict = [];
|
|
22
|
+
if (config.instanceId) envDict.push(` <key>YEAFT_AGENT_INSTANCE</key>\n <string>${config.instanceId}</string>`);
|
|
21
23
|
if (config.serverUrl) envDict.push(` <key>SERVER_URL</key>\n <string>${config.serverUrl}</string>`);
|
|
22
24
|
if (config.agentName) envDict.push(` <key>AGENT_NAME</key>\n <string>${config.agentName}</string>`);
|
|
23
25
|
if (config.agentSecret) envDict.push(` <key>AGENT_SECRET</key>\n <string>${config.agentSecret}</string>`);
|
|
24
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>`);
|
|
25
28
|
|
|
26
29
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
27
30
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
28
31
|
<plist version="1.0">
|
|
29
32
|
<dict>
|
|
30
33
|
<key>Label</key>
|
|
31
|
-
<string
|
|
34
|
+
<string>${label}</string>
|
|
32
35
|
<key>ProgramArguments</key>
|
|
33
36
|
<array>
|
|
34
37
|
<string>${nodePath}</string>
|
|
@@ -59,62 +62,64 @@ ${envDict.join('\n')}
|
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
export function macInstall(config) {
|
|
62
|
-
const plistPath = getLaunchdPlistPath();
|
|
65
|
+
const plistPath = getLaunchdPlistPath(config.instanceId);
|
|
63
66
|
// Ensure LaunchAgents directory exists before writing
|
|
64
67
|
mkdirSync(join(homedir(), 'Library', 'LaunchAgents'), { recursive: true });
|
|
68
|
+
mkdirSync(getLogDir(config.instanceId), { recursive: true });
|
|
65
69
|
// Unload first if exists
|
|
66
70
|
if (existsSync(plistPath)) {
|
|
67
71
|
try { execSync(`launchctl unload ${plistPath} 2>/dev/null`); } catch {}
|
|
68
72
|
}
|
|
69
73
|
writeFileSync(plistPath, generateLaunchdPlist(config));
|
|
70
74
|
execSync(`launchctl load ${plistPath}`);
|
|
71
|
-
console.log(
|
|
75
|
+
console.log(`Service installed and started: ${getLaunchdLabel(config.instanceId)}`);
|
|
72
76
|
console.log(`\nManage with:`);
|
|
73
|
-
console.log(` yeaft-agent status`);
|
|
74
|
-
console.log(` yeaft-agent logs`);
|
|
75
|
-
console.log(` yeaft-agent restart`);
|
|
76
|
-
console.log(` yeaft-agent uninstall`);
|
|
77
|
+
console.log(` yeaft-agent status --instance ${config.instanceId}`);
|
|
78
|
+
console.log(` yeaft-agent logs --instance ${config.instanceId}`);
|
|
79
|
+
console.log(` yeaft-agent restart --instance ${config.instanceId}`);
|
|
80
|
+
console.log(` yeaft-agent uninstall --instance ${config.instanceId}`);
|
|
77
81
|
}
|
|
78
82
|
|
|
79
|
-
export function macUninstall() {
|
|
80
|
-
const plistPath = getLaunchdPlistPath();
|
|
83
|
+
export function macUninstall(instanceId = DEFAULT_INSTANCE_ID) {
|
|
84
|
+
const plistPath = getLaunchdPlistPath(instanceId);
|
|
81
85
|
if (existsSync(plistPath)) {
|
|
82
86
|
try { execSync(`launchctl unload ${plistPath}`); } catch {}
|
|
83
87
|
unlinkSync(plistPath);
|
|
84
88
|
}
|
|
85
|
-
console.log(
|
|
89
|
+
console.log(`Service uninstalled: ${getLaunchdLabel(instanceId)}`);
|
|
86
90
|
}
|
|
87
91
|
|
|
88
|
-
export function macStart() {
|
|
89
|
-
const plistPath = getLaunchdPlistPath();
|
|
92
|
+
export function macStart(instanceId = DEFAULT_INSTANCE_ID) {
|
|
93
|
+
const plistPath = getLaunchdPlistPath(instanceId);
|
|
90
94
|
if (!existsSync(plistPath)) {
|
|
91
95
|
console.error('Service not installed. Run "yeaft-agent install" first.');
|
|
92
96
|
process.exit(1);
|
|
93
97
|
}
|
|
94
98
|
execSync(`launchctl load ${plistPath}`);
|
|
95
|
-
console.log(
|
|
99
|
+
console.log(`Service started: ${getLaunchdLabel(instanceId)}`);
|
|
96
100
|
}
|
|
97
101
|
|
|
98
|
-
export function macStop() {
|
|
99
|
-
const plistPath = getLaunchdPlistPath();
|
|
102
|
+
export function macStop(instanceId = DEFAULT_INSTANCE_ID) {
|
|
103
|
+
const plistPath = getLaunchdPlistPath(instanceId);
|
|
100
104
|
if (existsSync(plistPath)) {
|
|
101
105
|
execSync(`launchctl unload ${plistPath}`);
|
|
102
106
|
}
|
|
103
|
-
console.log(
|
|
107
|
+
console.log(`Service stopped: ${getLaunchdLabel(instanceId)}`);
|
|
104
108
|
}
|
|
105
109
|
|
|
106
|
-
export function macRestart() {
|
|
107
|
-
macStop();
|
|
108
|
-
macStart();
|
|
110
|
+
export function macRestart(instanceId = DEFAULT_INSTANCE_ID) {
|
|
111
|
+
macStop(instanceId);
|
|
112
|
+
macStart(instanceId);
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
/**
|
|
112
116
|
* Query launchd for the current service status.
|
|
113
117
|
* Returns { running: boolean, pid: string|null, exitCode: string|null }.
|
|
114
118
|
*/
|
|
115
|
-
export function getMacServiceStatus() {
|
|
119
|
+
export function getMacServiceStatus(instanceId = DEFAULT_INSTANCE_ID) {
|
|
120
|
+
const label = getLaunchdLabel(instanceId);
|
|
116
121
|
try {
|
|
117
|
-
const output = execSync(
|
|
122
|
+
const output = execSync(`launchctl list | grep ${label}`, {
|
|
118
123
|
encoding: 'utf-8',
|
|
119
124
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
120
125
|
});
|
|
@@ -133,8 +138,8 @@ export function getMacServiceStatus() {
|
|
|
133
138
|
}
|
|
134
139
|
}
|
|
135
140
|
|
|
136
|
-
export function macStatus() {
|
|
137
|
-
const status = getMacServiceStatus();
|
|
141
|
+
export function macStatus(instanceId = DEFAULT_INSTANCE_ID) {
|
|
142
|
+
const status = getMacServiceStatus(instanceId);
|
|
138
143
|
if (status.running) {
|
|
139
144
|
console.log(`Service is running (PID: ${status.pid})`);
|
|
140
145
|
} else if (status.exitCode !== null) {
|
|
@@ -144,8 +149,8 @@ export function macStatus() {
|
|
|
144
149
|
}
|
|
145
150
|
}
|
|
146
151
|
|
|
147
|
-
export function macLogs() {
|
|
148
|
-
const logFile = join(getLogDir(), 'out.log');
|
|
152
|
+
export function macLogs(instanceId = DEFAULT_INSTANCE_ID) {
|
|
153
|
+
const logFile = join(getLogDir(instanceId), 'out.log');
|
|
149
154
|
if (existsSync(logFile)) {
|
|
150
155
|
execSync(`tail -f -n 100 ${logFile}`, { stdio: 'inherit' });
|
|
151
156
|
} else {
|
package/service/windows.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
/*
|
|
2
2
|
* Service — Windows (pm2) platform implementation
|
|
3
3
|
*/
|
|
4
4
|
import { execSync, spawn } from 'child_process';
|
|
5
5
|
import { existsSync, mkdirSync, writeFileSync, readFileSync, unlinkSync } from 'fs';
|
|
6
6
|
import { join, dirname } from 'path';
|
|
7
|
-
import {
|
|
7
|
+
import { getConfigDir, getLogDir, getNodePath, getCliPath, getPm2AppName, DEFAULT_INSTANCE_ID } from './config.js';
|
|
8
8
|
|
|
9
9
|
const WIN_TASK_NAME = 'YeaftAgent';
|
|
10
|
-
const PM2_APP_NAME = 'yeaft-agent';
|
|
11
10
|
|
|
12
11
|
// Legacy paths for cleanup
|
|
13
|
-
function getWinWrapperPath() { return join(getConfigDir(), 'run.vbs'); }
|
|
14
|
-
function getWinBatPath() { return join(getConfigDir(), 'run.bat'); }
|
|
12
|
+
function getWinWrapperPath(instanceId = DEFAULT_INSTANCE_ID) { return join(getConfigDir(instanceId), 'run.vbs'); }
|
|
13
|
+
function getWinBatPath(instanceId = DEFAULT_INSTANCE_ID) { return join(getConfigDir(instanceId), 'run.bat'); }
|
|
15
14
|
|
|
16
15
|
function ensurePm2() {
|
|
17
16
|
try {
|
|
@@ -22,25 +21,28 @@ function ensurePm2() {
|
|
|
22
21
|
}
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
export function getEcosystemPath() {
|
|
26
|
-
return join(getConfigDir(), 'ecosystem.config.cjs');
|
|
24
|
+
export function getEcosystemPath(instanceId = DEFAULT_INSTANCE_ID) {
|
|
25
|
+
return join(getConfigDir(instanceId), 'ecosystem.config.cjs');
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
function generateEcosystem(config) {
|
|
30
29
|
const nodePath = getNodePath();
|
|
31
30
|
const cliPath = getCliPath();
|
|
32
31
|
const cliDir = dirname(cliPath);
|
|
33
|
-
const logDir = getLogDir();
|
|
32
|
+
const logDir = getLogDir(config.instanceId);
|
|
33
|
+
const pm2AppName = getPm2AppName(config.instanceId);
|
|
34
34
|
|
|
35
35
|
const env = {};
|
|
36
|
+
if (config.instanceId) env.YEAFT_AGENT_INSTANCE = config.instanceId;
|
|
36
37
|
if (config.serverUrl) env.SERVER_URL = config.serverUrl;
|
|
37
38
|
if (config.agentName) env.AGENT_NAME = config.agentName;
|
|
38
39
|
if (config.agentSecret) env.AGENT_SECRET = config.agentSecret;
|
|
39
40
|
if (config.workDir) env.WORK_DIR = config.workDir;
|
|
41
|
+
if (config.yeaftDir) env.YEAFT_DIR = config.yeaftDir;
|
|
40
42
|
|
|
41
43
|
return `module.exports = {
|
|
42
44
|
apps: [{
|
|
43
|
-
name: '${
|
|
45
|
+
name: '${pm2AppName}',
|
|
44
46
|
script: '${cliPath.replace(/\\/g, '\\\\')}',
|
|
45
47
|
interpreter: '${nodePath.replace(/\\/g, '\\\\')}',
|
|
46
48
|
cwd: '${cliDir.replace(/\\/g, '\\\\')}',
|
|
@@ -61,15 +63,16 @@ function generateEcosystem(config) {
|
|
|
61
63
|
|
|
62
64
|
export function winInstall(config) {
|
|
63
65
|
ensurePm2();
|
|
64
|
-
const logDir = getLogDir();
|
|
66
|
+
const logDir = getLogDir(config.instanceId);
|
|
67
|
+
const pm2AppName = getPm2AppName(config.instanceId);
|
|
65
68
|
mkdirSync(logDir, { recursive: true });
|
|
66
69
|
|
|
67
70
|
// Generate ecosystem config
|
|
68
|
-
const ecoPath = getEcosystemPath();
|
|
71
|
+
const ecoPath = getEcosystemPath(config.instanceId);
|
|
69
72
|
writeFileSync(ecoPath, generateEcosystem(config));
|
|
70
73
|
|
|
71
74
|
// Stop existing instance if any
|
|
72
|
-
try { execSync(`pm2 delete ${
|
|
75
|
+
try { execSync(`pm2 delete ${pm2AppName}`, { stdio: 'pipe' }); } catch {}
|
|
73
76
|
|
|
74
77
|
// Start with pm2
|
|
75
78
|
execSync(`pm2 start "${ecoPath}"`, { stdio: 'inherit' });
|
|
@@ -81,7 +84,7 @@ export function winInstall(config) {
|
|
|
81
84
|
// pm2-startup doesn't work well on Windows, use Startup folder approach
|
|
82
85
|
const trayScript = join(dirname(getCliPath()), 'scripts', 'agent-tray.ps1');
|
|
83
86
|
const startupDir = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
|
|
84
|
-
const startupBat = join(startupDir, `${
|
|
87
|
+
const startupBat = join(startupDir, `${pm2AppName}.bat`);
|
|
85
88
|
// Resurrect pm2 processes + launch tray icon
|
|
86
89
|
let batContent = `@echo off\r\npm2 resurrect\r\n`;
|
|
87
90
|
if (existsSync(trayScript)) {
|
|
@@ -96,41 +99,43 @@ export function winInstall(config) {
|
|
|
96
99
|
}).unref();
|
|
97
100
|
}
|
|
98
101
|
|
|
99
|
-
console.log(`\nService installed and started
|
|
102
|
+
console.log(`\nService installed and started: ${pm2AppName}`);
|
|
100
103
|
console.log(` Ecosystem: ${ecoPath}`);
|
|
101
104
|
console.log(` Startup: ${startupBat}`);
|
|
102
105
|
console.log(`\nManage with:`);
|
|
103
|
-
console.log(` yeaft-agent status`);
|
|
104
|
-
console.log(` yeaft-agent logs`);
|
|
105
|
-
console.log(` yeaft-agent restart`);
|
|
106
|
-
console.log(` yeaft-agent uninstall`);
|
|
106
|
+
console.log(` yeaft-agent status --instance ${config.instanceId}`);
|
|
107
|
+
console.log(` yeaft-agent logs --instance ${config.instanceId}`);
|
|
108
|
+
console.log(` yeaft-agent restart --instance ${config.instanceId}`);
|
|
109
|
+
console.log(` yeaft-agent uninstall --instance ${config.instanceId}`);
|
|
107
110
|
}
|
|
108
111
|
|
|
109
|
-
export function winUninstall() {
|
|
110
|
-
|
|
112
|
+
export function winUninstall(instanceId = DEFAULT_INSTANCE_ID) {
|
|
113
|
+
const pm2AppName = getPm2AppName(instanceId);
|
|
114
|
+
try { execSync(`pm2 delete ${pm2AppName}`, { stdio: 'pipe' }); } catch {}
|
|
111
115
|
try { execSync('pm2 save', { stdio: 'pipe' }); } catch {}
|
|
112
116
|
// Clean up ecosystem config
|
|
113
|
-
const ecoPath = getEcosystemPath();
|
|
117
|
+
const ecoPath = getEcosystemPath(instanceId);
|
|
114
118
|
if (existsSync(ecoPath)) unlinkSync(ecoPath);
|
|
115
119
|
// Clean up Startup bat
|
|
116
|
-
const startupBat = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup', `${
|
|
120
|
+
const startupBat = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup', `${pm2AppName}.bat`);
|
|
117
121
|
if (existsSync(startupBat)) unlinkSync(startupBat);
|
|
118
122
|
// Clean up legacy files
|
|
119
|
-
const vbsPath = getWinWrapperPath();
|
|
120
|
-
const batPath = getWinBatPath();
|
|
123
|
+
const vbsPath = getWinWrapperPath(instanceId);
|
|
124
|
+
const batPath = getWinBatPath(instanceId);
|
|
121
125
|
if (existsSync(vbsPath)) unlinkSync(vbsPath);
|
|
122
126
|
if (existsSync(batPath)) unlinkSync(batPath);
|
|
123
127
|
const startupVbs = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup', `${WIN_TASK_NAME}.vbs`);
|
|
124
128
|
if (existsSync(startupVbs)) unlinkSync(startupVbs);
|
|
125
|
-
console.log(
|
|
129
|
+
console.log(`Service uninstalled: ${pm2AppName}`);
|
|
126
130
|
}
|
|
127
131
|
|
|
128
|
-
export function winStart() {
|
|
132
|
+
export function winStart(instanceId = DEFAULT_INSTANCE_ID) {
|
|
133
|
+
const pm2AppName = getPm2AppName(instanceId);
|
|
129
134
|
try {
|
|
130
|
-
execSync(`pm2 start ${
|
|
135
|
+
execSync(`pm2 start ${pm2AppName}`, { stdio: 'inherit' });
|
|
131
136
|
} catch {
|
|
132
137
|
// Try ecosystem file
|
|
133
|
-
const ecoPath = getEcosystemPath();
|
|
138
|
+
const ecoPath = getEcosystemPath(instanceId);
|
|
134
139
|
if (existsSync(ecoPath)) {
|
|
135
140
|
execSync(`pm2 start "${ecoPath}"`, { stdio: 'inherit' });
|
|
136
141
|
} else {
|
|
@@ -140,17 +145,17 @@ export function winStart() {
|
|
|
140
145
|
}
|
|
141
146
|
}
|
|
142
147
|
|
|
143
|
-
export function winStop() {
|
|
148
|
+
export function winStop(instanceId = DEFAULT_INSTANCE_ID) {
|
|
144
149
|
try {
|
|
145
|
-
execSync(`pm2 stop ${
|
|
150
|
+
execSync(`pm2 stop ${getPm2AppName(instanceId)}`, { stdio: 'inherit' });
|
|
146
151
|
} catch {
|
|
147
152
|
console.error('Service not running or not installed.');
|
|
148
153
|
}
|
|
149
154
|
}
|
|
150
155
|
|
|
151
|
-
export function winRestart() {
|
|
156
|
+
export function winRestart(instanceId = DEFAULT_INSTANCE_ID) {
|
|
152
157
|
try {
|
|
153
|
-
execSync(`pm2 restart ${
|
|
158
|
+
execSync(`pm2 restart ${getPm2AppName(instanceId)}`, { stdio: 'inherit' });
|
|
154
159
|
} catch {
|
|
155
160
|
console.error('Service not running. Use "yeaft-agent start" to start.');
|
|
156
161
|
}
|
|
@@ -160,14 +165,15 @@ export function winRestart() {
|
|
|
160
165
|
* Query pm2 for the current service status.
|
|
161
166
|
* Returns { running: boolean, pid: string|null }.
|
|
162
167
|
*/
|
|
163
|
-
export function getWinServiceStatus() {
|
|
168
|
+
export function getWinServiceStatus(instanceId = DEFAULT_INSTANCE_ID) {
|
|
169
|
+
const pm2AppName = getPm2AppName(instanceId);
|
|
164
170
|
try {
|
|
165
171
|
const output = execSync('pm2 jlist', {
|
|
166
172
|
encoding: 'utf-8',
|
|
167
173
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
168
174
|
});
|
|
169
175
|
const apps = JSON.parse(output);
|
|
170
|
-
const app = Array.isArray(apps) && apps.find(a => a.name ===
|
|
176
|
+
const app = Array.isArray(apps) && apps.find(a => a.name === pm2AppName);
|
|
171
177
|
if (app) {
|
|
172
178
|
const running = app.pm2_env && app.pm2_env.status === 'online';
|
|
173
179
|
const pid = running ? app.pid : null;
|
|
@@ -179,22 +185,23 @@ export function getWinServiceStatus() {
|
|
|
179
185
|
}
|
|
180
186
|
}
|
|
181
187
|
|
|
182
|
-
export function winStatus() {
|
|
188
|
+
export function winStatus(instanceId = DEFAULT_INSTANCE_ID) {
|
|
183
189
|
try {
|
|
184
|
-
execSync(`pm2 describe ${
|
|
190
|
+
execSync(`pm2 describe ${getPm2AppName(instanceId)}`, { stdio: 'inherit' });
|
|
185
191
|
} catch {
|
|
186
192
|
console.log('Service is not installed.');
|
|
187
193
|
}
|
|
188
194
|
}
|
|
189
195
|
|
|
190
|
-
export function winLogs() {
|
|
191
|
-
const
|
|
196
|
+
export function winLogs(instanceId = DEFAULT_INSTANCE_ID) {
|
|
197
|
+
const pm2AppName = getPm2AppName(instanceId);
|
|
198
|
+
const child = spawn('pm2', ['logs', pm2AppName, '--lines', '100'], {
|
|
192
199
|
stdio: 'inherit',
|
|
193
200
|
shell: true
|
|
194
201
|
});
|
|
195
202
|
child.on('error', () => {
|
|
196
203
|
// Fallback to reading log file directly
|
|
197
|
-
const logFile = join(getLogDir(), 'out.log');
|
|
204
|
+
const logFile = join(getLogDir(instanceId), 'out.log');
|
|
198
205
|
if (existsSync(logFile)) {
|
|
199
206
|
console.log(readFileSync(logFile, 'utf-8'));
|
|
200
207
|
} else {
|
package/service.js
CHANGED
|
@@ -16,6 +16,16 @@ export {
|
|
|
16
16
|
saveServiceConfig,
|
|
17
17
|
loadServiceConfig,
|
|
18
18
|
parseServiceArgs,
|
|
19
|
+
SERVICE_NAME,
|
|
20
|
+
DEFAULT_INSTANCE_ID,
|
|
21
|
+
normalizeInstanceId,
|
|
22
|
+
isDefaultInstance,
|
|
23
|
+
validateInstanceId,
|
|
24
|
+
getInstanceIdFromArgs,
|
|
25
|
+
getServiceName,
|
|
26
|
+
getPm2AppName,
|
|
27
|
+
getLaunchdLabel,
|
|
28
|
+
getDefaultYeaftDir,
|
|
19
29
|
install,
|
|
20
30
|
uninstall,
|
|
21
31
|
start,
|
package/terminal.js
CHANGED
|
@@ -3,6 +3,8 @@ import { existsSync, chmodSync, statSync } from 'fs';
|
|
|
3
3
|
import { join, dirname } from 'path';
|
|
4
4
|
import { createRequire } from 'module';
|
|
5
5
|
import ctx from './context.js';
|
|
6
|
+
import { getRuntimePlatformInfo } from './yeaft/runtime-platform.js';
|
|
7
|
+
import { wrapInvocationInSystemdUserScope } from './yeaft/systemd-scope.js';
|
|
6
8
|
|
|
7
9
|
// Package name of the PTY backend. We use the Homebridge prebuilt fork
|
|
8
10
|
// because upstream node-pty ships no Linux prebuilds and falls back to
|
|
@@ -91,12 +93,22 @@ export async function handleTerminalCreate(msg) {
|
|
|
91
93
|
? `${process.env.SystemRoot || 'C:\\Windows'}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`
|
|
92
94
|
: (process.env.COMSPEC || 'cmd.exe')))
|
|
93
95
|
: (process.env.SHELL || 'bash');
|
|
94
|
-
const
|
|
96
|
+
const terminalEnv = { ...process.env };
|
|
97
|
+
const terminalInvocation = wrapInvocationInSystemdUserScope(
|
|
98
|
+
{ command: shell, args: [], family: platform() === 'win32' ? 'powershell' : 'posix' },
|
|
99
|
+
{
|
|
100
|
+
runtimePlatform: getRuntimePlatformInfo(),
|
|
101
|
+
env: terminalEnv,
|
|
102
|
+
scopeId: `terminal-${terminalId}`,
|
|
103
|
+
scopePrefix: 'yeaft-terminal',
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
const ptyProcess = pty.spawn(terminalInvocation.command, terminalInvocation.args || [], {
|
|
95
107
|
name: 'xterm-256color',
|
|
96
108
|
cols: cols || 80,
|
|
97
109
|
rows: rows || 24,
|
|
98
110
|
cwd: workDir,
|
|
99
|
-
env:
|
|
111
|
+
env: terminalEnv
|
|
100
112
|
});
|
|
101
113
|
|
|
102
114
|
// 输出缓冲 - 每 16ms 批量发送
|
package/yeaft/cli.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Features:
|
|
9
9
|
* --dry-run "prompt" — Assemble system prompt + messages, don't call LLM
|
|
10
|
-
* --trace stats|recent|search <keyword>|tools|compact — Query/maintain debug
|
|
10
|
+
* --trace stats|recent|search <keyword>|tools|compact — Query/maintain debug trace files
|
|
11
11
|
* -i / --interactive — REPL mode with / commands
|
|
12
12
|
* <prompt> — One-shot query (Phase 1: engine.query)
|
|
13
13
|
* --skip-mcp — Skip MCP server connections (faster startup)
|
|
@@ -117,12 +117,12 @@ function parseArgs(argv) {
|
|
|
117
117
|
// ─── Trace query handler ───────────────────────────────────────
|
|
118
118
|
|
|
119
119
|
function handleTraceQuery(args, config) {
|
|
120
|
-
const
|
|
120
|
+
const traceDir = config.dir;
|
|
121
121
|
let trace;
|
|
122
122
|
try {
|
|
123
|
-
trace = new DebugTrace(
|
|
123
|
+
trace = new DebugTrace(traceDir);
|
|
124
124
|
} catch (e) {
|
|
125
|
-
throw new Error(`Cannot open debug
|
|
125
|
+
throw new Error(`Cannot open debug trace store at ${traceDir}: ${e.message}`);
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
try {
|
|
@@ -133,7 +133,7 @@ function handleTraceQuery(args, config) {
|
|
|
133
133
|
console.log(` Turns: ${s.turnCount}`);
|
|
134
134
|
console.log(` Tools: ${s.toolCount}`);
|
|
135
135
|
console.log(` Events: ${s.eventCount}`);
|
|
136
|
-
console.log(`
|
|
136
|
+
console.log(` Disk: ${(s.dbSizeBytes / 1024).toFixed(1)} KB`);
|
|
137
137
|
break;
|
|
138
138
|
}
|
|
139
139
|
case 'recent': {
|
|
@@ -175,8 +175,8 @@ function handleTraceQuery(args, config) {
|
|
|
175
175
|
}
|
|
176
176
|
case 'compact': {
|
|
177
177
|
const s = trace.stats();
|
|
178
|
-
console.log(`Compacting debug
|
|
179
|
-
console.log('This
|
|
178
|
+
console.log(`Compacting debug trace files (${(s.dbSizeBytes / 1048576).toFixed(1)} MB, ${s.turnCount} turns)...`);
|
|
179
|
+
console.log('This prunes old request folders and may take a moment. Do not interrupt.');
|
|
180
180
|
const { before, after } = trace.compact();
|
|
181
181
|
const saved = Math.max(0, before - after);
|
|
182
182
|
console.log(`Done. ${(before / 1048576).toFixed(1)} MB → ${(after / 1048576).toFixed(1)} MB (reclaimed ${(saved / 1048576).toFixed(1)} MB).`);
|