@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/cli.js
CHANGED
|
@@ -41,7 +41,7 @@ const subArgs = args.slice(1);
|
|
|
41
41
|
const SERVICE_COMMANDS = ['install', 'uninstall', 'start', 'stop', 'restart', 'status', 'logs'];
|
|
42
42
|
|
|
43
43
|
if (command === 'doctor') {
|
|
44
|
-
handleDoctorCommand();
|
|
44
|
+
await handleDoctorCommand();
|
|
45
45
|
} else if (command === 'llm') {
|
|
46
46
|
await handleLlmCommand(subArgs);
|
|
47
47
|
} else if (command === 'upgrade') {
|
|
@@ -51,7 +51,7 @@ if (command === 'doctor') {
|
|
|
51
51
|
} else if (command === '--help' || command === '-h') {
|
|
52
52
|
printHelp();
|
|
53
53
|
} else if (SERVICE_COMMANDS.includes(command)) {
|
|
54
|
-
handleServiceCommand(command, subArgs);
|
|
54
|
+
await handleServiceCommand(command, subArgs);
|
|
55
55
|
} else {
|
|
56
56
|
// Normal agent startup — parse flags and set env vars
|
|
57
57
|
parseAndStart(args);
|
|
@@ -64,35 +64,40 @@ function printHelp() {
|
|
|
64
64
|
Usage:
|
|
65
65
|
yeaft-agent [options] Run agent in foreground
|
|
66
66
|
yeaft-agent install [options] Install as system service
|
|
67
|
-
yeaft-agent uninstall
|
|
68
|
-
yeaft-agent start
|
|
69
|
-
yeaft-agent stop
|
|
70
|
-
yeaft-agent restart
|
|
71
|
-
yeaft-agent status
|
|
72
|
-
yeaft-agent logs
|
|
67
|
+
yeaft-agent uninstall [options] Remove system service
|
|
68
|
+
yeaft-agent start [options] Start installed service
|
|
69
|
+
yeaft-agent stop [options] Stop installed service
|
|
70
|
+
yeaft-agent restart [options] Restart installed service
|
|
71
|
+
yeaft-agent status [options] Show service status
|
|
72
|
+
yeaft-agent logs [options] View service logs (follow mode)
|
|
73
73
|
yeaft-agent doctor Diagnose service configuration
|
|
74
74
|
yeaft-agent llm <command> Configure local Yeaft LLM providers/models
|
|
75
75
|
yeaft-agent upgrade Upgrade to latest version
|
|
76
76
|
yeaft-agent --version Show version
|
|
77
77
|
|
|
78
78
|
Options:
|
|
79
|
+
--instance <id> Local service instance id (default: default)
|
|
79
80
|
--server <url> WebSocket server URL (default: ws://localhost:3456)
|
|
80
81
|
--name <name> Agent display name (default: Worker-{platform}-{pid})
|
|
81
82
|
--secret <secret> Agent secret for authentication
|
|
82
83
|
--work-dir <dir> Default working directory (default: cwd)
|
|
84
|
+
--yeaft-dir <dir> Yeaft data directory for this instance
|
|
83
85
|
--auto-upgrade Check for updates on startup
|
|
84
86
|
|
|
85
87
|
Environment variables (alternative to flags):
|
|
88
|
+
YEAFT_AGENT_INSTANCE Local service instance id
|
|
86
89
|
SERVER_URL WebSocket server URL
|
|
87
90
|
AGENT_NAME Agent display name
|
|
88
91
|
AGENT_SECRET Agent secret
|
|
89
92
|
WORK_DIR Working directory
|
|
93
|
+
YEAFT_DIR Yeaft data directory
|
|
90
94
|
|
|
91
95
|
Examples:
|
|
92
96
|
yeaft-agent --server wss://your-server.com --name my-worker --secret xxx
|
|
93
97
|
yeaft-agent install --server wss://your-server.com --name my-worker --secret xxx
|
|
94
|
-
yeaft-agent
|
|
95
|
-
yeaft-agent
|
|
98
|
+
yeaft-agent install --instance second --server wss://your-server.com --name my-worker-2 --secret xxx
|
|
99
|
+
yeaft-agent status --instance second
|
|
100
|
+
yeaft-agent logs --instance second
|
|
96
101
|
`);
|
|
97
102
|
}
|
|
98
103
|
|
|
@@ -380,13 +385,13 @@ function parseLlmArgs(args) {
|
|
|
380
385
|
async function handleServiceCommand(command, args) {
|
|
381
386
|
const service = await import('./service.js');
|
|
382
387
|
switch (command) {
|
|
383
|
-
case 'install': service.install(args); break;
|
|
384
|
-
case 'uninstall': service.uninstall(); break;
|
|
385
|
-
case 'start': service.start(); break;
|
|
386
|
-
case 'stop': service.stop(); break;
|
|
387
|
-
case 'restart': service.restart(); break;
|
|
388
|
-
case 'status': service.status(); break;
|
|
389
|
-
case 'logs': service.logs(); break;
|
|
388
|
+
case 'install': await service.install(args); break;
|
|
389
|
+
case 'uninstall': await service.uninstall(args); break;
|
|
390
|
+
case 'start': await service.start(args); break;
|
|
391
|
+
case 'stop': await service.stop(args); break;
|
|
392
|
+
case 'restart': await service.restart(args); break;
|
|
393
|
+
case 'status': await service.status(args); break;
|
|
394
|
+
case 'logs': await service.logs(args); break;
|
|
390
395
|
}
|
|
391
396
|
}
|
|
392
397
|
|
|
@@ -402,6 +407,9 @@ function parseAndStart(args) {
|
|
|
402
407
|
const next = args[i + 1];
|
|
403
408
|
|
|
404
409
|
switch (arg) {
|
|
410
|
+
case '--instance':
|
|
411
|
+
if (next) { process.env.YEAFT_AGENT_INSTANCE = process.env.YEAFT_AGENT_INSTANCE || next; i++; }
|
|
412
|
+
break;
|
|
405
413
|
case '--server':
|
|
406
414
|
if (next) { process.env.SERVER_URL = process.env.SERVER_URL || next; i++; }
|
|
407
415
|
break;
|
|
@@ -414,6 +422,9 @@ function parseAndStart(args) {
|
|
|
414
422
|
case '--work-dir':
|
|
415
423
|
if (next) { process.env.WORK_DIR = process.env.WORK_DIR || next; i++; }
|
|
416
424
|
break;
|
|
425
|
+
case '--yeaft-dir':
|
|
426
|
+
if (next) { process.env.YEAFT_DIR = process.env.YEAFT_DIR || next; i++; }
|
|
427
|
+
break;
|
|
417
428
|
case '--auto-upgrade':
|
|
418
429
|
checkForUpdates();
|
|
419
430
|
break;
|
package/connection/index.js
CHANGED
|
@@ -5,12 +5,15 @@ import { startAgentHeartbeat, stopAgentHeartbeat, scheduleReconnect } from './he
|
|
|
5
5
|
import { handleMessage } from './message-router.js';
|
|
6
6
|
|
|
7
7
|
export function connect() {
|
|
8
|
-
// Don't include secret in URL - it will be sent via WebSocket message after connection
|
|
9
|
-
//
|
|
8
|
+
// Don't include secret in URL - it will be sent via WebSocket message after connection.
|
|
9
|
+
// instanceId is the stable local service identity; agentName is display-only.
|
|
10
|
+
// Old configs without instanceId still use agentName for backward-compatible identity.
|
|
11
|
+
const instanceId = ctx.CONFIG.instanceId || ctx.CONFIG.agentName;
|
|
10
12
|
const params = new URLSearchParams({
|
|
11
13
|
type: 'agent',
|
|
12
|
-
id:
|
|
14
|
+
id: instanceId,
|
|
13
15
|
name: ctx.CONFIG.agentName,
|
|
16
|
+
instanceId,
|
|
14
17
|
workDir: ctx.CONFIG.workDir,
|
|
15
18
|
capabilities: ctx.agentCapabilities.join(',')
|
|
16
19
|
});
|
|
@@ -38,7 +38,7 @@ import { loadMcpServers, updateMcpConfig } from '../mcp.js';
|
|
|
38
38
|
import { getLlmConfig, updateLlmConfig, getYeaftSettings, updateYeaftSettings, getSearchSettings, updateSearchSettings, fetchTavilyUsage } from '../yeaft/config-api.js';
|
|
39
39
|
import { discoverLlmModels } from '../llm-model-discovery.js';
|
|
40
40
|
import { fetchModelsDev } from '../yeaft/llm/models-dev.js';
|
|
41
|
-
import { handleYeaftSessionSend, handleYeaftSubAgentPrompt, handleYeaftModeSwitch, handleYeaftModelSwitch, resetYeaftSession, handleYeaftLoadHistory, handleYeaftLoadMoreHistory, handleYeaftAbortThread, handleYeaftAbortAll, handleYeaftAbortTurn, handleYeaftVpSubscribe, handleYeaftVpCreate, handleYeaftVpUpdate, handleYeaftVpDelete, handleYeaftVpRead, handleYeaftListSessions, handleYeaftCreateSession, handleYeaftRenameSession, handleYeaftUpdateSession, handleYeaftUpdateSessionConfig, handleYeaftArchiveSession, handleYeaftDeleteSession, handleYeaftSessionAddMember, handleYeaftSessionRemoveMember, handleYeaftSessionSetDefaultVp, handleYeaftScanWorkdirSessions, handleYeaftRestoreSession, handleYeaftDreamTrigger, handleYeaftFetchToolStats, handleYeaftFetchDebugHistory, handleYeaftMcpList, handleYeaftMcpAdd, handleYeaftMcpRemove, handleYeaftMcpReload, broadcastLanguageChange, broadcastYeaftSessionSnapshotEager } from '../yeaft/web-bridge.js';
|
|
41
|
+
import { handleYeaftSessionSend, handleYeaftSubAgentPrompt, handleYeaftTaskCancel, handleYeaftModeSwitch, handleYeaftModelSwitch, resetYeaftSession, handleYeaftLoadHistory, handleYeaftLoadMoreHistory, handleYeaftAbortThread, handleYeaftAbortAll, handleYeaftAbortTurn, handleYeaftVpSubscribe, handleYeaftVpCreate, handleYeaftVpUpdate, handleYeaftVpDelete, handleYeaftVpRead, handleYeaftListSessions, handleYeaftCreateSession, handleYeaftRenameSession, handleYeaftUpdateSession, handleYeaftUpdateSessionConfig, handleYeaftArchiveSession, handleYeaftDeleteSession, handleYeaftSessionAddMember, handleYeaftSessionRemoveMember, handleYeaftSessionSetDefaultVp, handleYeaftScanWorkdirSessions, handleYeaftRestoreSession, handleYeaftDreamTrigger, handleYeaftFetchToolStats, handleYeaftFetchDebugHistory, handleYeaftMcpList, handleYeaftMcpAdd, handleYeaftMcpRemove, handleYeaftMcpReload, broadcastLanguageChange, broadcastYeaftSessionSnapshotEager } from '../yeaft/web-bridge.js';
|
|
42
42
|
import { startYeaftStatusRefresh, refreshYeaftStatus } from '../yeaft/status-cache.js';
|
|
43
43
|
|
|
44
44
|
export async function handleMessage(msg) {
|
|
@@ -58,11 +58,13 @@ export async function handleMessage(msg) {
|
|
|
58
58
|
console.log('[WS] Server accepts plaintext, disabling outbound encryption');
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
//
|
|
61
|
+
// 只保存基本配置。instanceId 是本地服务实例身份;agentName 只用于展示。
|
|
62
62
|
ctx.saveConfig({
|
|
63
|
+
instanceId: ctx.CONFIG.instanceId,
|
|
63
64
|
serverUrl: ctx.CONFIG.serverUrl,
|
|
64
65
|
agentName: ctx.CONFIG.agentName,
|
|
65
66
|
workDir: ctx.CONFIG.workDir,
|
|
67
|
+
yeaftDir: ctx.CONFIG.yeaftDir,
|
|
66
68
|
reconnectInterval: ctx.CONFIG.reconnectInterval
|
|
67
69
|
// 不保存 agentSecret 到配置文件(安全考虑)
|
|
68
70
|
});
|
|
@@ -658,6 +660,9 @@ export async function handleMessage(msg) {
|
|
|
658
660
|
case 'yeaft_sub_agent_prompt':
|
|
659
661
|
handleYeaftSubAgentPrompt(msg);
|
|
660
662
|
break;
|
|
663
|
+
case 'yeaft_task_cancel':
|
|
664
|
+
handleYeaftTaskCancel(msg);
|
|
665
|
+
break;
|
|
661
666
|
|
|
662
667
|
// wave-6b: manual dream trigger from VP detail page
|
|
663
668
|
case 'yeaft_dream_trigger':
|
|
@@ -671,10 +676,9 @@ export async function handleMessage(msg) {
|
|
|
671
676
|
await handleYeaftFetchToolStats(msg);
|
|
672
677
|
break;
|
|
673
678
|
|
|
674
|
-
//
|
|
675
|
-
//
|
|
676
|
-
//
|
|
677
|
-
// turn is invisible.
|
|
679
|
+
// Hydrate the Yeaft debug panel from the persistent file-backed trace.
|
|
680
|
+
// Without this, the panel only shows turns that happened after it was
|
|
681
|
+
// opened — every previous turn is invisible.
|
|
678
682
|
case 'yeaft_fetch_debug_history':
|
|
679
683
|
case 'unify_fetch_debug_history':
|
|
680
684
|
await handleYeaftFetchDebugHistory(msg);
|
package/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { exec } from 'child_process';
|
|
|
9
9
|
import { promisify } from 'util';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import ctx from './context.js';
|
|
12
|
-
import { getConfigPath, loadServiceConfig } from './service.js';
|
|
12
|
+
import { DEFAULT_INSTANCE_ID, getDefaultYeaftDir, validateInstanceId, getConfigPath, loadServiceConfig } from './service.js';
|
|
13
13
|
import { loadNodePty } from './terminal.js';
|
|
14
14
|
import { connect } from './connection.js';
|
|
15
15
|
import { loadMcpServers } from './mcp.js';
|
|
@@ -60,6 +60,7 @@ function saveConfig(config) {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
const fileConfig = loadConfig();
|
|
63
|
+
const INSTANCE_ID = validateInstanceId(process.env.YEAFT_AGENT_INSTANCE || fileConfig.instanceId || DEFAULT_INSTANCE_ID);
|
|
63
64
|
|
|
64
65
|
// task-fix (5-bugs): the Yeaft web-bridge reads `ctx.CONFIG.yeaftDir`
|
|
65
66
|
// for every group / VP / memory operation. If unset, `path.join(undefined, …)`
|
|
@@ -67,7 +68,7 @@ const fileConfig = loadConfig();
|
|
|
67
68
|
// and the UI surfaces "群组操作失败: …" with a raw node error. Resolve the
|
|
68
69
|
// default (`~/.yeaft`) here and make sure the directory exists before the
|
|
69
70
|
// WebSocket connection goes live, so downstream code can assume a real path.
|
|
70
|
-
const YEAFT_DIR = process.env.YEAFT_DIR || fileConfig.yeaftDir ||
|
|
71
|
+
const YEAFT_DIR = process.env.YEAFT_DIR || fileConfig.yeaftDir || getDefaultYeaftDir(INSTANCE_ID);
|
|
71
72
|
try {
|
|
72
73
|
if (!existsSync(YEAFT_DIR)) {
|
|
73
74
|
mkdirSync(YEAFT_DIR, { recursive: true });
|
|
@@ -78,6 +79,7 @@ try {
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
const CONFIG = {
|
|
82
|
+
instanceId: INSTANCE_ID,
|
|
81
83
|
serverUrl: process.env.SERVER_URL || fileConfig.serverUrl,
|
|
82
84
|
agentName: process.env.AGENT_NAME || fileConfig.agentName,
|
|
83
85
|
workDir: process.env.WORK_DIR || fileConfig.workDir || process.cwd(),
|
package/llm-config-cli.js
CHANGED
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
modelIdsFromProviderModels,
|
|
9
9
|
} from './llm-model-discovery.js';
|
|
10
10
|
|
|
11
|
+
export const DEFAULT_GITHUB_COPILOT_MODEL = 'gpt-5.5';
|
|
12
|
+
|
|
11
13
|
const VALID_PROTOCOLS = new Set(['anthropic', 'openai-responses']);
|
|
12
14
|
const VALID_CREDENTIAL_PROVIDERS = new Set(['github-copilot']);
|
|
13
15
|
|
|
@@ -230,6 +232,52 @@ export async function useGitHubCopilot(config, options = {}) {
|
|
|
230
232
|
return { config: next, provider, discovery };
|
|
231
233
|
}
|
|
232
234
|
|
|
235
|
+
export function hasLocalLlmConfig(config = {}) {
|
|
236
|
+
const providers = Array.isArray(config.providers) ? config.providers.filter(Boolean) : [];
|
|
237
|
+
return providers.length > 0 || Boolean(config.primaryModel) || Boolean(config.fastModel);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function isDefaultSeedLlmConfig(config = {}) {
|
|
241
|
+
const providers = Array.isArray(config.providers) ? config.providers.filter(Boolean) : [];
|
|
242
|
+
if (providers.length !== 1) return false;
|
|
243
|
+
const provider = providers[0];
|
|
244
|
+
return provider?.name === 'my-proxy'
|
|
245
|
+
&& provider?.baseUrl === 'http://localhost:6628/v1'
|
|
246
|
+
&& provider?.apiKey === 'proxy'
|
|
247
|
+
&& typeof config.primaryModel === 'string'
|
|
248
|
+
&& config.primaryModel.startsWith('my-proxy/');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export async function tryAutoConfigureGitHubCopilot(configPath = getDefaultYeaftConfigPath(), options = {}) {
|
|
252
|
+
let current;
|
|
253
|
+
try {
|
|
254
|
+
current = readLocalLlmConfig(configPath);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
return { configured: false, reason: 'invalid-config', error, config: null };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const allowConfigured = Boolean(options.allowConfigured) || isDefaultSeedLlmConfig(current);
|
|
260
|
+
if (!allowConfigured && hasLocalLlmConfig(current)) {
|
|
261
|
+
return { configured: false, reason: 'already-configured', config: current };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const result = await useGitHubCopilot(current, {
|
|
266
|
+
...options,
|
|
267
|
+
model: options.model || DEFAULT_GITHUB_COPILOT_MODEL,
|
|
268
|
+
});
|
|
269
|
+
writeLocalLlmConfig(result.config, configPath);
|
|
270
|
+
return { configured: true, reason: 'configured', ...result };
|
|
271
|
+
} catch (error) {
|
|
272
|
+
return {
|
|
273
|
+
configured: false,
|
|
274
|
+
reason: error?.code === 'COPILOT_CREDENTIAL_MISSING' ? 'credential-missing' : 'unavailable',
|
|
275
|
+
error,
|
|
276
|
+
config: current,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
233
281
|
|
|
234
282
|
export async function useOpenAICompatible(config, options = {}, env = process.env) {
|
|
235
283
|
const name = options.name ? String(options.name).trim() : 'openai';
|
package/package.json
CHANGED
package/service/config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
/*
|
|
2
2
|
* Service — shared configuration and utility functions
|
|
3
3
|
*/
|
|
4
4
|
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
@@ -8,6 +8,7 @@ import { fileURLToPath } from 'url';
|
|
|
8
8
|
|
|
9
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
export const SERVICE_NAME = 'yeaft-agent';
|
|
11
|
+
export const DEFAULT_INSTANCE_ID = 'default';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Load .env file from agent directory (or cwd) into process.env
|
|
@@ -40,36 +41,98 @@ function loadDotenv() {
|
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
export function normalizeInstanceId(instanceId) {
|
|
45
|
+
const raw = String(instanceId || '').trim();
|
|
46
|
+
return raw || DEFAULT_INSTANCE_ID;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function isDefaultInstance(instanceId) {
|
|
50
|
+
return normalizeInstanceId(instanceId) === DEFAULT_INSTANCE_ID;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function validateInstanceId(instanceId) {
|
|
54
|
+
const normalized = normalizeInstanceId(instanceId);
|
|
55
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(normalized)) {
|
|
56
|
+
throw new Error('Instance id may only contain letters, numbers, dot, underscore, or dash');
|
|
57
|
+
}
|
|
58
|
+
return normalized;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function getInstanceIdFromArgs(args = [], env = process.env) {
|
|
62
|
+
let instanceId = env.YEAFT_AGENT_INSTANCE || '';
|
|
63
|
+
for (let i = 0; i < args.length; i++) {
|
|
64
|
+
const arg = args[i];
|
|
65
|
+
const next = args[i + 1];
|
|
66
|
+
if (arg === '--instance' && next) {
|
|
67
|
+
instanceId = next;
|
|
68
|
+
i++;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return validateInstanceId(instanceId || DEFAULT_INSTANCE_ID);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function getServiceName(instanceId = DEFAULT_INSTANCE_ID) {
|
|
75
|
+
const normalized = validateInstanceId(instanceId);
|
|
76
|
+
return isDefaultInstance(normalized) ? SERVICE_NAME : `${SERVICE_NAME}@${normalized}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function getPm2AppName(instanceId = DEFAULT_INSTANCE_ID) {
|
|
80
|
+
const normalized = validateInstanceId(instanceId);
|
|
81
|
+
return isDefaultInstance(normalized) ? SERVICE_NAME : `${SERVICE_NAME}-${normalized}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function getLaunchdLabel(instanceId = DEFAULT_INSTANCE_ID) {
|
|
85
|
+
const normalized = validateInstanceId(instanceId);
|
|
86
|
+
return isDefaultInstance(normalized) ? 'com.yeaft.agent' : `com.yeaft.agent.${normalized}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getDefaultYeaftDir(instanceId = DEFAULT_INSTANCE_ID) {
|
|
90
|
+
const normalized = validateInstanceId(instanceId);
|
|
91
|
+
return isDefaultInstance(normalized)
|
|
92
|
+
? join(homedir(), '.yeaft')
|
|
93
|
+
: join(homedir(), '.yeaft', 'instances', normalized);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getBaseConfigDir() {
|
|
45
97
|
if (platform() === 'win32') {
|
|
46
98
|
return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), SERVICE_NAME);
|
|
47
99
|
}
|
|
48
100
|
return join(homedir(), '.config', SERVICE_NAME);
|
|
49
101
|
}
|
|
50
102
|
|
|
51
|
-
|
|
52
|
-
|
|
103
|
+
// Standard config/log directory per platform. The default instance keeps the
|
|
104
|
+
// historical paths for compatibility; named instances live under instances/<id>.
|
|
105
|
+
export function getConfigDir(instanceId = process.env.YEAFT_AGENT_INSTANCE || DEFAULT_INSTANCE_ID) {
|
|
106
|
+
const normalized = validateInstanceId(instanceId);
|
|
107
|
+
const base = getBaseConfigDir();
|
|
108
|
+
return isDefaultInstance(normalized) ? base : join(base, 'instances', normalized);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function getLogDir(instanceId = process.env.YEAFT_AGENT_INSTANCE || DEFAULT_INSTANCE_ID) {
|
|
112
|
+
return join(getConfigDir(instanceId), 'logs');
|
|
53
113
|
}
|
|
54
114
|
|
|
55
|
-
export function getConfigPath() {
|
|
56
|
-
return join(getConfigDir(), 'config.json');
|
|
115
|
+
export function getConfigPath(instanceId = process.env.YEAFT_AGENT_INSTANCE || DEFAULT_INSTANCE_ID) {
|
|
116
|
+
return join(getConfigDir(instanceId), 'config.json');
|
|
57
117
|
}
|
|
58
118
|
|
|
59
119
|
/** Save agent configuration to standard location */
|
|
60
120
|
export function saveServiceConfig(config) {
|
|
61
|
-
const
|
|
121
|
+
const instanceId = validateInstanceId(config.instanceId || DEFAULT_INSTANCE_ID);
|
|
122
|
+
const dir = getConfigDir(instanceId);
|
|
62
123
|
mkdirSync(dir, { recursive: true });
|
|
63
|
-
mkdirSync(getLogDir(), { recursive: true });
|
|
64
|
-
writeFileSync(getConfigPath(), JSON.stringify(config, null, 2));
|
|
124
|
+
mkdirSync(getLogDir(instanceId), { recursive: true });
|
|
125
|
+
writeFileSync(getConfigPath(instanceId), JSON.stringify({ ...config, instanceId }, null, 2));
|
|
65
126
|
}
|
|
66
127
|
|
|
67
128
|
/** Load agent configuration from standard location */
|
|
68
|
-
export function loadServiceConfig() {
|
|
69
|
-
const
|
|
129
|
+
export function loadServiceConfig(instanceId = process.env.YEAFT_AGENT_INSTANCE || DEFAULT_INSTANCE_ID) {
|
|
130
|
+
const normalized = validateInstanceId(instanceId);
|
|
131
|
+
const configPath = getConfigPath(normalized);
|
|
70
132
|
if (!existsSync(configPath)) return null;
|
|
71
133
|
try {
|
|
72
|
-
|
|
134
|
+
const loaded = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
135
|
+
return { ...loaded, instanceId: loaded.instanceId || normalized };
|
|
73
136
|
} catch {
|
|
74
137
|
return null;
|
|
75
138
|
}
|
|
@@ -86,18 +149,21 @@ export function getCliPath() {
|
|
|
86
149
|
}
|
|
87
150
|
|
|
88
151
|
/**
|
|
89
|
-
* Parse
|
|
152
|
+
* Parse service options from args, merging with the selected instance config.
|
|
90
153
|
*/
|
|
91
154
|
export function parseServiceArgs(args) {
|
|
92
155
|
// Load .env if available (for dev / source-based usage)
|
|
93
156
|
loadDotenv();
|
|
94
157
|
|
|
95
|
-
const
|
|
158
|
+
const instanceId = getInstanceIdFromArgs(args);
|
|
159
|
+
const existing = loadServiceConfig(instanceId) || {};
|
|
96
160
|
const config = {
|
|
161
|
+
instanceId,
|
|
97
162
|
serverUrl: existing.serverUrl || '',
|
|
98
163
|
agentName: existing.agentName || '',
|
|
99
164
|
agentSecret: existing.agentSecret || '',
|
|
100
165
|
workDir: existing.workDir || '',
|
|
166
|
+
yeaftDir: existing.yeaftDir || '',
|
|
101
167
|
};
|
|
102
168
|
|
|
103
169
|
// Environment variables override saved config
|
|
@@ -105,16 +171,19 @@ export function parseServiceArgs(args) {
|
|
|
105
171
|
if (process.env.AGENT_NAME) config.agentName = process.env.AGENT_NAME;
|
|
106
172
|
if (process.env.AGENT_SECRET) config.agentSecret = process.env.AGENT_SECRET;
|
|
107
173
|
if (process.env.WORK_DIR) config.workDir = process.env.WORK_DIR;
|
|
174
|
+
if (process.env.YEAFT_DIR) config.yeaftDir = process.env.YEAFT_DIR;
|
|
108
175
|
|
|
109
176
|
// CLI args override everything
|
|
110
177
|
for (let i = 0; i < args.length; i++) {
|
|
111
178
|
const arg = args[i];
|
|
112
179
|
const next = args[i + 1];
|
|
113
180
|
switch (arg) {
|
|
181
|
+
case '--instance': if (next) { i++; } break;
|
|
114
182
|
case '--server': if (next) { config.serverUrl = next; i++; } break;
|
|
115
183
|
case '--name': if (next) { config.agentName = next; i++; } break;
|
|
116
184
|
case '--secret': if (next) { config.agentSecret = next; i++; } break;
|
|
117
185
|
case '--work-dir': if (next) { config.workDir = next; i++; } break;
|
|
186
|
+
case '--yeaft-dir': if (next) { config.yeaftDir = next; i++; } break;
|
|
118
187
|
}
|
|
119
188
|
}
|
|
120
189
|
|
|
@@ -122,6 +191,12 @@ export function parseServiceArgs(args) {
|
|
|
122
191
|
}
|
|
123
192
|
|
|
124
193
|
export function validateConfig(config) {
|
|
194
|
+
try {
|
|
195
|
+
validateInstanceId(config.instanceId || DEFAULT_INSTANCE_ID);
|
|
196
|
+
} catch (err) {
|
|
197
|
+
console.error(`Error: ${err.message}`);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
125
200
|
if (!config.serverUrl) {
|
|
126
201
|
console.error('Error: --server <url> is required');
|
|
127
202
|
process.exit(1);
|
package/service/index.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
/*
|
|
2
2
|
* Service — platform dispatcher
|
|
3
3
|
* Routes install/uninstall/start/stop/restart/status/logs to the correct platform module.
|
|
4
4
|
*/
|
|
5
5
|
import { existsSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
6
7
|
import { platform } from 'os';
|
|
7
8
|
import {
|
|
8
|
-
|
|
9
|
+
getConfigDir, getLogDir, getConfigPath,
|
|
9
10
|
saveServiceConfig, loadServiceConfig,
|
|
10
|
-
parseServiceArgs, validateConfig
|
|
11
|
+
parseServiceArgs, validateConfig, getInstanceIdFromArgs, getDefaultYeaftDir
|
|
11
12
|
} from './config.js';
|
|
12
13
|
import { initYeaftDir } from '../yeaft/init.js';
|
|
14
|
+
import { DEFAULT_GITHUB_COPILOT_MODEL, tryAutoConfigureGitHubCopilot } from '../llm-config-cli.js';
|
|
13
15
|
import { getSystemdServicePath, linuxInstall, linuxUninstall, linuxStart, linuxStop, linuxRestart, linuxStatus, linuxLogs } from './linux.js';
|
|
14
16
|
import { getLaunchdPlistPath, macInstall, macUninstall, macStart, macStop, macRestart, macStatus, macLogs } from './macos.js';
|
|
15
17
|
import { winInstall, winUninstall, winStart, winStop, winRestart, winStatus, winLogs } from './windows.js';
|
|
@@ -20,17 +22,42 @@ export {
|
|
|
20
22
|
saveServiceConfig, loadServiceConfig,
|
|
21
23
|
parseServiceArgs
|
|
22
24
|
};
|
|
25
|
+
export {
|
|
26
|
+
SERVICE_NAME,
|
|
27
|
+
DEFAULT_INSTANCE_ID,
|
|
28
|
+
normalizeInstanceId,
|
|
29
|
+
isDefaultInstance,
|
|
30
|
+
validateInstanceId,
|
|
31
|
+
getInstanceIdFromArgs,
|
|
32
|
+
getServiceName,
|
|
33
|
+
getPm2AppName,
|
|
34
|
+
getLaunchdLabel,
|
|
35
|
+
getDefaultYeaftDir,
|
|
36
|
+
} from './config.js';
|
|
23
37
|
|
|
24
38
|
const os = platform();
|
|
25
39
|
|
|
26
|
-
function
|
|
40
|
+
export async function autoConfigureGitHubCopilotIfAvailable(yeaftDir, options = {}) {
|
|
41
|
+
const result = await tryAutoConfigureGitHubCopilot(join(yeaftDir, 'config.json'), options);
|
|
42
|
+
if (result.configured) {
|
|
43
|
+
console.log(`Configured GitHub Copilot provider automatically with ${DEFAULT_GITHUB_COPILOT_MODEL}.`);
|
|
44
|
+
if (result.discovery?.warning) console.log(`Warning: ${result.discovery.warning}`);
|
|
45
|
+
} else if (result.reason === 'already-configured') {
|
|
46
|
+
console.log('LLM config already exists; skipped automatic GitHub Copilot setup.');
|
|
47
|
+
} else if (result.reason === 'invalid-config') {
|
|
48
|
+
console.log('Existing LLM config is invalid; skipped automatic GitHub Copilot setup.');
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function ensureInstalled(instanceId) {
|
|
27
54
|
if (os === 'linux') {
|
|
28
|
-
if (!existsSync(getSystemdServicePath())) {
|
|
55
|
+
if (!existsSync(getSystemdServicePath(instanceId))) {
|
|
29
56
|
console.error('Service not installed. Run "yeaft-agent install" first.');
|
|
30
57
|
process.exit(1);
|
|
31
58
|
}
|
|
32
59
|
} else if (os === 'darwin') {
|
|
33
|
-
if (!existsSync(getLaunchdPlistPath())) {
|
|
60
|
+
if (!existsSync(getLaunchdPlistPath(instanceId))) {
|
|
34
61
|
console.error('Service not installed. Run "yeaft-agent install" first.');
|
|
35
62
|
process.exit(1);
|
|
36
63
|
}
|
|
@@ -38,24 +65,29 @@ function ensureInstalled() {
|
|
|
38
65
|
// Windows check is done inside individual functions
|
|
39
66
|
}
|
|
40
67
|
|
|
41
|
-
export function install(args) {
|
|
68
|
+
export async function install(args) {
|
|
42
69
|
const config = parseServiceArgs(args);
|
|
43
70
|
validateConfig(config);
|
|
44
71
|
saveServiceConfig(config);
|
|
45
72
|
|
|
46
73
|
// Initialize ~/.yeaft/ directory + default config.json
|
|
47
74
|
// so `yeaft` CLI is ready to use immediately after install
|
|
48
|
-
const
|
|
75
|
+
const effectiveYeaftDir = config.yeaftDir || getDefaultYeaftDir(config.instanceId);
|
|
76
|
+
const { dir, created } = initYeaftDir(effectiveYeaftDir);
|
|
77
|
+
await autoConfigureGitHubCopilotIfAvailable(dir, {
|
|
78
|
+
allowConfigured: created.includes(join(dir, 'config.json')),
|
|
79
|
+
});
|
|
49
80
|
if (created.length > 0) {
|
|
50
81
|
console.log(`Initialized ${dir}`);
|
|
51
82
|
console.log(` Edit ${dir}/config.json to configure LLM providers.`);
|
|
52
83
|
console.log('');
|
|
53
84
|
}
|
|
54
85
|
|
|
55
|
-
console.log(`Installing
|
|
56
|
-
console.log(`
|
|
57
|
-
console.log(`
|
|
58
|
-
console.log(`
|
|
86
|
+
console.log(`Installing yeaft-agent service...`);
|
|
87
|
+
console.log(` Instance: ${config.instanceId}`);
|
|
88
|
+
console.log(` Server: ${config.serverUrl}`);
|
|
89
|
+
console.log(` Name: ${config.agentName || '(auto)'}`);
|
|
90
|
+
console.log(` WorkDir: ${config.workDir || '(home)'}`);
|
|
59
91
|
console.log('');
|
|
60
92
|
|
|
61
93
|
if (os === 'linux') linuxInstall(config);
|
|
@@ -68,45 +100,51 @@ export function install(args) {
|
|
|
68
100
|
}
|
|
69
101
|
}
|
|
70
102
|
|
|
71
|
-
export function uninstall() {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
else if (os === '
|
|
103
|
+
export function uninstall(args = []) {
|
|
104
|
+
const instanceId = getInstanceIdFromArgs(args);
|
|
105
|
+
console.log(`Uninstalling yeaft-agent service (${instanceId})...`);
|
|
106
|
+
if (os === 'linux') linuxUninstall(instanceId);
|
|
107
|
+
else if (os === 'darwin') macUninstall(instanceId);
|
|
108
|
+
else if (os === 'win32') winUninstall(instanceId);
|
|
76
109
|
else { console.error(`Unsupported platform: ${os}`); process.exit(1); }
|
|
77
110
|
}
|
|
78
111
|
|
|
79
|
-
export function start() {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
else if (os === '
|
|
112
|
+
export function start(args = []) {
|
|
113
|
+
const instanceId = getInstanceIdFromArgs(args);
|
|
114
|
+
ensureInstalled(instanceId);
|
|
115
|
+
if (os === 'linux') linuxStart(instanceId);
|
|
116
|
+
else if (os === 'darwin') macStart(instanceId);
|
|
117
|
+
else if (os === 'win32') winStart(instanceId);
|
|
84
118
|
}
|
|
85
119
|
|
|
86
|
-
export function stop() {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
else if (os === '
|
|
120
|
+
export function stop(args = []) {
|
|
121
|
+
const instanceId = getInstanceIdFromArgs(args);
|
|
122
|
+
ensureInstalled(instanceId);
|
|
123
|
+
if (os === 'linux') linuxStop(instanceId);
|
|
124
|
+
else if (os === 'darwin') macStop(instanceId);
|
|
125
|
+
else if (os === 'win32') winStop(instanceId);
|
|
91
126
|
}
|
|
92
127
|
|
|
93
|
-
export function restart() {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
else if (os === '
|
|
128
|
+
export function restart(args = []) {
|
|
129
|
+
const instanceId = getInstanceIdFromArgs(args);
|
|
130
|
+
ensureInstalled(instanceId);
|
|
131
|
+
if (os === 'linux') linuxRestart(instanceId);
|
|
132
|
+
else if (os === 'darwin') macRestart(instanceId);
|
|
133
|
+
else if (os === 'win32') winRestart(instanceId);
|
|
98
134
|
}
|
|
99
135
|
|
|
100
|
-
export function status() {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
else if (os === '
|
|
136
|
+
export function status(args = []) {
|
|
137
|
+
const instanceId = getInstanceIdFromArgs(args);
|
|
138
|
+
if (os === 'linux') linuxStatus(instanceId);
|
|
139
|
+
else if (os === 'darwin') macStatus(instanceId);
|
|
140
|
+
else if (os === 'win32') winStatus(instanceId);
|
|
104
141
|
}
|
|
105
142
|
|
|
106
|
-
export function logs() {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
else if (os === '
|
|
143
|
+
export function logs(args = []) {
|
|
144
|
+
const instanceId = getInstanceIdFromArgs(args);
|
|
145
|
+
if (os === 'linux') linuxLogs(instanceId);
|
|
146
|
+
else if (os === 'darwin') macLogs(instanceId);
|
|
147
|
+
else if (os === 'win32') winLogs(instanceId);
|
|
110
148
|
}
|
|
111
149
|
|
|
112
150
|
export { doctor };
|