@code4bug/jarvis-agent 1.0.2
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/LICENSE +21 -0
- package/README.md +227 -0
- package/dist/agents/code-reviewer.md +69 -0
- package/dist/agents/dba.md +68 -0
- package/dist/agents/finance-advisor.md +81 -0
- package/dist/agents/index.d.ts +31 -0
- package/dist/agents/index.js +86 -0
- package/dist/agents/jarvis.md +95 -0
- package/dist/agents/stock-trader.md +81 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +9 -0
- package/dist/commands/index.d.ts +19 -0
- package/dist/commands/index.js +79 -0
- package/dist/commands/init.d.ts +15 -0
- package/dist/commands/init.js +283 -0
- package/dist/components/DangerConfirm.d.ts +17 -0
- package/dist/components/DangerConfirm.js +50 -0
- package/dist/components/MarkdownText.d.ts +12 -0
- package/dist/components/MarkdownText.js +166 -0
- package/dist/components/MessageItem.d.ts +8 -0
- package/dist/components/MessageItem.js +78 -0
- package/dist/components/MultilineInput.d.ts +34 -0
- package/dist/components/MultilineInput.js +437 -0
- package/dist/components/SlashCommandMenu.d.ts +16 -0
- package/dist/components/SlashCommandMenu.js +43 -0
- package/dist/components/StatusBar.d.ts +8 -0
- package/dist/components/StatusBar.js +26 -0
- package/dist/components/StreamingText.d.ts +6 -0
- package/dist/components/StreamingText.js +10 -0
- package/dist/components/WelcomeHeader.d.ts +6 -0
- package/dist/components/WelcomeHeader.js +25 -0
- package/dist/config/agentState.d.ts +16 -0
- package/dist/config/agentState.js +65 -0
- package/dist/config/constants.d.ts +25 -0
- package/dist/config/constants.js +67 -0
- package/dist/config/loader.d.ts +30 -0
- package/dist/config/loader.js +64 -0
- package/dist/config/systemInfo.d.ts +12 -0
- package/dist/config/systemInfo.js +95 -0
- package/dist/core/QueryEngine.d.ts +52 -0
- package/dist/core/QueryEngine.js +246 -0
- package/dist/core/hint.d.ts +14 -0
- package/dist/core/hint.js +279 -0
- package/dist/core/query.d.ts +24 -0
- package/dist/core/query.js +245 -0
- package/dist/core/safeguard.d.ts +96 -0
- package/dist/core/safeguard.js +236 -0
- package/dist/hooks/useFocus.d.ts +12 -0
- package/dist/hooks/useFocus.js +35 -0
- package/dist/hooks/useInputHistory.d.ts +14 -0
- package/dist/hooks/useInputHistory.js +102 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -0
- package/dist/screens/repl.d.ts +1 -0
- package/dist/screens/repl.js +842 -0
- package/dist/services/api/llm.d.ts +27 -0
- package/dist/services/api/llm.js +314 -0
- package/dist/services/api/mock.d.ts +9 -0
- package/dist/services/api/mock.js +102 -0
- package/dist/skills/index.d.ts +23 -0
- package/dist/skills/index.js +232 -0
- package/dist/skills/loader.d.ts +45 -0
- package/dist/skills/loader.js +108 -0
- package/dist/tools/createSkill.d.ts +8 -0
- package/dist/tools/createSkill.js +255 -0
- package/dist/tools/index.d.ts +16 -0
- package/dist/tools/index.js +23 -0
- package/dist/tools/listDirectory.d.ts +2 -0
- package/dist/tools/listDirectory.js +20 -0
- package/dist/tools/readFile.d.ts +2 -0
- package/dist/tools/readFile.js +17 -0
- package/dist/tools/runCommand.d.ts +2 -0
- package/dist/tools/runCommand.js +69 -0
- package/dist/tools/searchFiles.d.ts +2 -0
- package/dist/tools/searchFiles.js +45 -0
- package/dist/tools/writeFile.d.ts +2 -0
- package/dist/tools/writeFile.js +42 -0
- package/dist/types/index.d.ts +86 -0
- package/dist/types/index.js +2 -0
- package/package.json +55 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** 应用全局常量 */
|
|
2
|
+
export declare const APP_VERSION: string;
|
|
3
|
+
export declare const PROJECT_NAME: string;
|
|
4
|
+
/** Agentic Loop 最大迭代次数 */
|
|
5
|
+
export declare const MAX_ITERATIONS = 50;
|
|
6
|
+
export declare const SESSIONS_DIR: string;
|
|
7
|
+
/** 输入后是否隐藏 WelcomeHeader,默认 false(不隐藏) */
|
|
8
|
+
export declare const HIDE_WELCOME_AFTER_INPUT = false;
|
|
9
|
+
export declare const MODEL_NAME: string;
|
|
10
|
+
/** 是否支持思考/非思考模式切换,默认 false(隐藏该功能) */
|
|
11
|
+
export declare const ENABLE_THINKING_MODE_TOGGLE: boolean;
|
|
12
|
+
/** 上下文 token 上限 */
|
|
13
|
+
export declare const CONTEXT_TOKEN_LIMIT: number;
|
|
14
|
+
/** 默认智能体名称(硬编码兜底值) */
|
|
15
|
+
export declare const DEFAULT_AGENT_FALLBACK = "Jarvis";
|
|
16
|
+
/** 智能体定义文件目录(相对项目根目录) */
|
|
17
|
+
export declare const AGENTS_DIR = "src/agents";
|
|
18
|
+
/** 智能体默认配色 */
|
|
19
|
+
export declare const DEFAULT_AGENT_COLOR = "green";
|
|
20
|
+
/** 智能体默认标识符 */
|
|
21
|
+
export declare const DEFAULT_AGENT_EMOJI = ">";
|
|
22
|
+
/** 当前激活的智能体名称 — 启动时从 ~/.jarvis/agent.json 读取,运行时可切换 */
|
|
23
|
+
export declare const DEFAULT_AGENT: string;
|
|
24
|
+
/** 应用名称 — 取自当前激活智能体的 name */
|
|
25
|
+
export declare const APP_NAME: string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
/** 从 package.json 动态读取版本号 */
|
|
5
|
+
function resolveAppVersion() {
|
|
6
|
+
try {
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const pkgPath = path.resolve(__dirname, '../../package.json');
|
|
9
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
10
|
+
return `v${pkg.version}`;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return 'v0.0.0';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** 应用全局常量 */
|
|
17
|
+
export const APP_VERSION = resolveAppVersion();
|
|
18
|
+
export const PROJECT_NAME = path.basename(process.cwd());
|
|
19
|
+
/** Agentic Loop 最大迭代次数 */
|
|
20
|
+
export const MAX_ITERATIONS = 50;
|
|
21
|
+
/** 会话存储目录(~/.jarvis/sessions/) */
|
|
22
|
+
import os from 'os';
|
|
23
|
+
export const SESSIONS_DIR = path.join(os.homedir(), '.jarvis', 'sessions');
|
|
24
|
+
/** 输入后是否隐藏 WelcomeHeader,默认 false(不隐藏) */
|
|
25
|
+
export const HIDE_WELCOME_AFTER_INPUT = false;
|
|
26
|
+
/** 从配置文件获取当前模型名称 */
|
|
27
|
+
import { loadConfig, getActiveModel } from './loader.js';
|
|
28
|
+
const _cfg = loadConfig();
|
|
29
|
+
function resolveModelName() {
|
|
30
|
+
try {
|
|
31
|
+
const active = getActiveModel(_cfg);
|
|
32
|
+
return active?.model ?? _cfg.system.model ?? 'unknown';
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return 'unknown';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export const MODEL_NAME = resolveModelName();
|
|
39
|
+
/** 是否支持思考/非思考模式切换,默认 false(隐藏该功能) */
|
|
40
|
+
export const ENABLE_THINKING_MODE_TOGGLE = _cfg.system.enable_thinking_mode_toggle ?? false;
|
|
41
|
+
/** 上下文 token 上限 */
|
|
42
|
+
export const CONTEXT_TOKEN_LIMIT = _cfg.system.context_token_limit ?? 18000;
|
|
43
|
+
// ===== 智能体默认配置 =====
|
|
44
|
+
/** 默认智能体名称(硬编码兜底值) */
|
|
45
|
+
export const DEFAULT_AGENT_FALLBACK = 'Jarvis';
|
|
46
|
+
/** 智能体定义文件目录(相对项目根目录) */
|
|
47
|
+
export const AGENTS_DIR = 'src/agents';
|
|
48
|
+
/** 智能体默认配色 */
|
|
49
|
+
export const DEFAULT_AGENT_COLOR = 'green';
|
|
50
|
+
/** 智能体默认标识符 */
|
|
51
|
+
export const DEFAULT_AGENT_EMOJI = '>';
|
|
52
|
+
// ===== 动态应用名称(跟随激活智能体) =====
|
|
53
|
+
import { getAgent } from '../agents/index.js';
|
|
54
|
+
import { getActiveAgent } from './agentState.js';
|
|
55
|
+
/** 当前激活的智能体名称 — 启动时从 ~/.jarvis/agent.json 读取,运行时可切换 */
|
|
56
|
+
export const DEFAULT_AGENT = getActiveAgent(DEFAULT_AGENT_FALLBACK);
|
|
57
|
+
function resolveAppName() {
|
|
58
|
+
try {
|
|
59
|
+
const agent = getAgent(DEFAULT_AGENT);
|
|
60
|
+
return agent?.meta.name ?? 'Jarvis';
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return 'Jarvis';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** 应用名称 — 取自当前激活智能体的 name */
|
|
67
|
+
export const APP_NAME = resolveAppName();
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置加载器
|
|
3
|
+
*
|
|
4
|
+
* 优先级(后者覆盖前者):
|
|
5
|
+
* 1. ~/.jarvis/config.json (全局配置)
|
|
6
|
+
* 2. ./.jarvis/config.json (项目配置)
|
|
7
|
+
*/
|
|
8
|
+
export interface ModelConfig {
|
|
9
|
+
api_url: string;
|
|
10
|
+
api_key: string;
|
|
11
|
+
model: string;
|
|
12
|
+
temperature?: number;
|
|
13
|
+
max_tokens?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface SystemConfig {
|
|
16
|
+
context_token_limit?: number;
|
|
17
|
+
context_compress_threshold?: number;
|
|
18
|
+
/** 当前使用的模型名称,对应 models 中的 key */
|
|
19
|
+
model?: string;
|
|
20
|
+
/** 是否支持思考/非思考模式切换,默认 false */
|
|
21
|
+
enable_thinking_mode_toggle?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface JarvisConfig {
|
|
24
|
+
system: SystemConfig;
|
|
25
|
+
models: Record<string, ModelConfig>;
|
|
26
|
+
}
|
|
27
|
+
/** 加载合并后的最终配置(结果会被缓存,多次调用只解析一次) */
|
|
28
|
+
export declare function loadConfig(): JarvisConfig;
|
|
29
|
+
/** 根据当前配置获取活跃模型配置 */
|
|
30
|
+
export declare function getActiveModel(config: JarvisConfig): ModelConfig | null;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置加载器
|
|
3
|
+
*
|
|
4
|
+
* 优先级(后者覆盖前者):
|
|
5
|
+
* 1. ~/.jarvis/config.json (全局配置)
|
|
6
|
+
* 2. ./.jarvis/config.json (项目配置)
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import os from 'os';
|
|
11
|
+
const CONFIG_FILENAME = 'config.json';
|
|
12
|
+
const JARVIS_DIR = '.jarvis';
|
|
13
|
+
/** 读取并解析单个配置文件,失败返回 null */
|
|
14
|
+
function loadJsonFile(filePath) {
|
|
15
|
+
try {
|
|
16
|
+
if (!fs.existsSync(filePath))
|
|
17
|
+
return null;
|
|
18
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
19
|
+
return JSON.parse(raw);
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
console.error(`[config] Failed to parse ${filePath}:`, err instanceof Error ? err.message : err);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** 深度合并两个配置,local 覆盖 global */
|
|
27
|
+
function mergeConfigs(global, local) {
|
|
28
|
+
const base = {
|
|
29
|
+
system: {},
|
|
30
|
+
models: {},
|
|
31
|
+
};
|
|
32
|
+
if (global) {
|
|
33
|
+
Object.assign(base.system, global.system);
|
|
34
|
+
Object.assign(base.models, global.models);
|
|
35
|
+
}
|
|
36
|
+
if (local) {
|
|
37
|
+
Object.assign(base.system, local.system);
|
|
38
|
+
// 项目级 models 逐个覆盖,而非整体替换
|
|
39
|
+
for (const [key, val] of Object.entries(local.models ?? {})) {
|
|
40
|
+
base.models[key] = { ...base.models[key], ...val };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return base;
|
|
44
|
+
}
|
|
45
|
+
/** 缓存已加载的配置 */
|
|
46
|
+
let _cachedConfig = null;
|
|
47
|
+
/** 加载合并后的最终配置(结果会被缓存,多次调用只解析一次) */
|
|
48
|
+
export function loadConfig() {
|
|
49
|
+
if (_cachedConfig)
|
|
50
|
+
return _cachedConfig;
|
|
51
|
+
const globalPath = path.join(os.homedir(), JARVIS_DIR, CONFIG_FILENAME);
|
|
52
|
+
const localPath = path.join(process.cwd(), JARVIS_DIR, CONFIG_FILENAME);
|
|
53
|
+
const globalCfg = loadJsonFile(globalPath);
|
|
54
|
+
const localCfg = loadJsonFile(localPath);
|
|
55
|
+
_cachedConfig = mergeConfigs(globalCfg, localCfg);
|
|
56
|
+
return _cachedConfig;
|
|
57
|
+
}
|
|
58
|
+
/** 根据当前配置获取活跃模型配置 */
|
|
59
|
+
export function getActiveModel(config) {
|
|
60
|
+
const modelName = config.system.model;
|
|
61
|
+
if (!modelName)
|
|
62
|
+
return null;
|
|
63
|
+
return config.models[modelName] ?? null;
|
|
64
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 系统信息收集器
|
|
3
|
+
*
|
|
4
|
+
* 启动时执行一次,收集当前操作系统和硬件信息,
|
|
5
|
+
* 格式化后追加到智能体上下文中,帮助 LLM 更好地理解用户环境。
|
|
6
|
+
*/
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
/** 安全执行命令,失败返回空字符串 */
|
|
10
|
+
function safeExec(cmd) {
|
|
11
|
+
try {
|
|
12
|
+
return execSync(cmd, { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return '';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** 收集系统信息 */
|
|
19
|
+
function collectSystemInfo() {
|
|
20
|
+
const cpus = os.cpus();
|
|
21
|
+
const platform = os.platform();
|
|
22
|
+
const extras = {};
|
|
23
|
+
// 平台特定信息
|
|
24
|
+
if (platform === 'darwin') {
|
|
25
|
+
const macModel = safeExec('sysctl -n hw.model');
|
|
26
|
+
if (macModel)
|
|
27
|
+
extras['Mac 型号'] = macModel;
|
|
28
|
+
const macOSName = safeExec('sw_vers -productName');
|
|
29
|
+
const macOSVer = safeExec('sw_vers -productVersion');
|
|
30
|
+
if (macOSName && macOSVer)
|
|
31
|
+
extras['macOS'] = `${macOSName} ${macOSVer}`;
|
|
32
|
+
}
|
|
33
|
+
else if (platform === 'linux') {
|
|
34
|
+
const distro = safeExec('cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d= -f2 | tr -d \'"\'');
|
|
35
|
+
if (distro)
|
|
36
|
+
extras['发行版'] = distro;
|
|
37
|
+
const kernel = safeExec('uname -r');
|
|
38
|
+
if (kernel)
|
|
39
|
+
extras['内核版本'] = kernel;
|
|
40
|
+
}
|
|
41
|
+
// 通用工具版本检测
|
|
42
|
+
const gitVersion = safeExec('git --version');
|
|
43
|
+
if (gitVersion)
|
|
44
|
+
extras['Git'] = gitVersion.replace('git version ', '');
|
|
45
|
+
const pythonVersion = safeExec('python3 --version 2>/dev/null || python --version 2>/dev/null');
|
|
46
|
+
if (pythonVersion)
|
|
47
|
+
extras['Python'] = pythonVersion.replace('Python ', '');
|
|
48
|
+
return {
|
|
49
|
+
osType: `${platform} (${os.type()})`,
|
|
50
|
+
osVersion: os.release(),
|
|
51
|
+
arch: os.arch(),
|
|
52
|
+
hostname: os.hostname(),
|
|
53
|
+
cpuModel: cpus[0]?.model ?? 'unknown',
|
|
54
|
+
cpuCores: cpus.length,
|
|
55
|
+
totalMemoryGB: (os.totalmem() / 1024 / 1024 / 1024).toFixed(1),
|
|
56
|
+
freeMemoryGB: (os.freemem() / 1024 / 1024 / 1024).toFixed(1),
|
|
57
|
+
username: os.userInfo().username,
|
|
58
|
+
shell: process.env.SHELL || process.env.COMSPEC || 'unknown',
|
|
59
|
+
cwd: process.cwd(),
|
|
60
|
+
nodeVersion: process.version,
|
|
61
|
+
extras,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** 缓存,只收集一次 */
|
|
65
|
+
let _cached = null;
|
|
66
|
+
/**
|
|
67
|
+
* 获取格式化的系统信息文本,用于追加到 system prompt
|
|
68
|
+
*
|
|
69
|
+
* 返回 markdown 格式的系统环境描述
|
|
70
|
+
*/
|
|
71
|
+
export function getSystemInfoPrompt() {
|
|
72
|
+
if (_cached)
|
|
73
|
+
return _cached;
|
|
74
|
+
const info = collectSystemInfo();
|
|
75
|
+
const lines = [
|
|
76
|
+
'\n\n---',
|
|
77
|
+
'[系统环境] 以下是用户当前的操作系统和硬件信息,可用于辅助判断和生成适配当前环境的命令或代码:',
|
|
78
|
+
'',
|
|
79
|
+
`- 操作系统: ${info.osType}`,
|
|
80
|
+
`- 系统版本: ${info.osVersion}`,
|
|
81
|
+
`- 架构: ${info.arch}`,
|
|
82
|
+
`- 主机名: ${info.hostname}`,
|
|
83
|
+
`- CPU: ${info.cpuModel} (${info.cpuCores} 核)`,
|
|
84
|
+
`- 内存: ${info.totalMemoryGB} GB (可用 ${info.freeMemoryGB} GB)`,
|
|
85
|
+
`- 用户: ${info.username}`,
|
|
86
|
+
`- Shell: ${info.shell}`,
|
|
87
|
+
`- 工作目录: ${info.cwd}`,
|
|
88
|
+
`- Node.js: ${info.nodeVersion}`,
|
|
89
|
+
];
|
|
90
|
+
for (const [key, val] of Object.entries(info.extras)) {
|
|
91
|
+
lines.push(`- ${key}: ${val}`);
|
|
92
|
+
}
|
|
93
|
+
_cached = lines.join('\n');
|
|
94
|
+
return _cached;
|
|
95
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Message, Session, LoopState } from '../types/index.js';
|
|
2
|
+
import { DangerConfirmResult } from './query.js';
|
|
3
|
+
export interface EngineCallbacks {
|
|
4
|
+
onMessage: (msg: Message) => void;
|
|
5
|
+
onUpdateMessage: (id: string, updates: Partial<Message>) => void;
|
|
6
|
+
onStreamText: (text: string) => void;
|
|
7
|
+
/** 清空流式文本(每轮迭代结束时调用) */
|
|
8
|
+
onClearStreamText?: () => void;
|
|
9
|
+
onLoopStateChange: (state: LoopState) => void;
|
|
10
|
+
onSessionUpdate: (session: Session) => void;
|
|
11
|
+
/** 危险命令交互式确认 */
|
|
12
|
+
onConfirmDangerousCommand?: (command: string, reason: string, ruleName: string) => Promise<DangerConfirmResult>;
|
|
13
|
+
}
|
|
14
|
+
export declare class QueryEngine {
|
|
15
|
+
private service;
|
|
16
|
+
private session;
|
|
17
|
+
private transcript;
|
|
18
|
+
private abortSignal;
|
|
19
|
+
constructor();
|
|
20
|
+
private createSession;
|
|
21
|
+
private ensureSessionDir;
|
|
22
|
+
/** 处理用户输入 */
|
|
23
|
+
handleQuery(userInput: string, callbacks: EngineCallbacks): Promise<void>;
|
|
24
|
+
/** 终止当前任务 */
|
|
25
|
+
abort(): void;
|
|
26
|
+
/** 重置会话 */
|
|
27
|
+
reset(): void;
|
|
28
|
+
/**
|
|
29
|
+
* 切换智能体:持久化选择 + 重建 LLM service + 重置会话
|
|
30
|
+
*/
|
|
31
|
+
switchAgent(agentName: string): void;
|
|
32
|
+
/** 保存会话到文件 */
|
|
33
|
+
private saveSession;
|
|
34
|
+
/** 列出所有历史会话(按更新时间倒序),返回摘要信息 */
|
|
35
|
+
static listSessions(): {
|
|
36
|
+
id: string;
|
|
37
|
+
summary: string;
|
|
38
|
+
updatedAt: number;
|
|
39
|
+
totalTokens: number;
|
|
40
|
+
}[];
|
|
41
|
+
/** 恢复指定会话:加载历史消息和 transcript */
|
|
42
|
+
loadSession(sessionId: string): {
|
|
43
|
+
session: Session;
|
|
44
|
+
messages: Message[];
|
|
45
|
+
} | null;
|
|
46
|
+
getSession(): Session;
|
|
47
|
+
/**
|
|
48
|
+
* 清理非当前会话的所有历史会话文件
|
|
49
|
+
* @returns 删除的会话数量
|
|
50
|
+
*/
|
|
51
|
+
clearOtherSessions(): number;
|
|
52
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { v4 as uuid } from 'uuid';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { getAllTools } from '../tools/index.js';
|
|
5
|
+
import { executeQuery } from './query.js';
|
|
6
|
+
import { MockService } from '../services/api/mock.js';
|
|
7
|
+
import { LLMServiceImpl } from '../services/api/llm.js';
|
|
8
|
+
import { loadConfig, getActiveModel } from '../config/loader.js';
|
|
9
|
+
import { SESSIONS_DIR } from '../config/constants.js';
|
|
10
|
+
import { setActiveAgent } from '../config/agentState.js';
|
|
11
|
+
import { clearAuthorizations } from './safeguard.js';
|
|
12
|
+
export class QueryEngine {
|
|
13
|
+
service;
|
|
14
|
+
session;
|
|
15
|
+
transcript = [];
|
|
16
|
+
abortSignal = { aborted: false };
|
|
17
|
+
constructor() {
|
|
18
|
+
// 尝试从配置文件加载 LLM,失败则回退 MockService
|
|
19
|
+
const config = loadConfig();
|
|
20
|
+
const activeModel = getActiveModel(config);
|
|
21
|
+
if (activeModel) {
|
|
22
|
+
try {
|
|
23
|
+
this.service = new LLMServiceImpl();
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
this.service = new MockService();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
this.service = new MockService();
|
|
31
|
+
}
|
|
32
|
+
this.session = this.createSession();
|
|
33
|
+
this.ensureSessionDir();
|
|
34
|
+
}
|
|
35
|
+
createSession() {
|
|
36
|
+
return {
|
|
37
|
+
id: uuid(),
|
|
38
|
+
messages: [],
|
|
39
|
+
createdAt: Date.now(),
|
|
40
|
+
updatedAt: Date.now(),
|
|
41
|
+
totalTokens: 0,
|
|
42
|
+
totalCost: 0,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
ensureSessionDir() {
|
|
46
|
+
if (!fs.existsSync(SESSIONS_DIR)) {
|
|
47
|
+
fs.mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** 处理用户输入 */
|
|
51
|
+
async handleQuery(userInput, callbacks) {
|
|
52
|
+
this.abortSignal = { aborted: false };
|
|
53
|
+
const userMsg = {
|
|
54
|
+
id: uuid(),
|
|
55
|
+
type: 'user',
|
|
56
|
+
status: 'success',
|
|
57
|
+
content: userInput,
|
|
58
|
+
timestamp: Date.now(),
|
|
59
|
+
};
|
|
60
|
+
callbacks.onMessage(userMsg);
|
|
61
|
+
this.session.messages.push(userMsg);
|
|
62
|
+
const queryCallbacks = {
|
|
63
|
+
onMessage: (msg) => {
|
|
64
|
+
callbacks.onMessage(msg);
|
|
65
|
+
this.session.messages.push(msg);
|
|
66
|
+
},
|
|
67
|
+
onUpdateMessage: callbacks.onUpdateMessage,
|
|
68
|
+
onStreamText: (text) => {
|
|
69
|
+
// 每收到一个 chunk 视为一个 token,实时递增并通知 UI
|
|
70
|
+
this.session.totalTokens++;
|
|
71
|
+
callbacks.onStreamText(text);
|
|
72
|
+
callbacks.onSessionUpdate(this.session);
|
|
73
|
+
},
|
|
74
|
+
onClearStreamText: callbacks.onClearStreamText,
|
|
75
|
+
onLoopStateChange: callbacks.onLoopStateChange,
|
|
76
|
+
onConfirmDangerousCommand: callbacks.onConfirmDangerousCommand,
|
|
77
|
+
};
|
|
78
|
+
try {
|
|
79
|
+
this.transcript = await executeQuery(userInput, this.transcript, getAllTools(), this.service, queryCallbacks, this.abortSignal);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
const errMsg = {
|
|
83
|
+
id: uuid(),
|
|
84
|
+
type: 'error',
|
|
85
|
+
status: 'error',
|
|
86
|
+
content: `错误: ${err.message}`,
|
|
87
|
+
timestamp: Date.now(),
|
|
88
|
+
};
|
|
89
|
+
callbacks.onMessage(errMsg);
|
|
90
|
+
}
|
|
91
|
+
this.session.updatedAt = Date.now();
|
|
92
|
+
callbacks.onSessionUpdate(this.session);
|
|
93
|
+
this.saveSession();
|
|
94
|
+
}
|
|
95
|
+
/** 终止当前任务 */
|
|
96
|
+
abort() {
|
|
97
|
+
this.abortSignal.aborted = true;
|
|
98
|
+
}
|
|
99
|
+
/** 重置会话 */
|
|
100
|
+
reset() {
|
|
101
|
+
this.saveSession();
|
|
102
|
+
this.session = this.createSession();
|
|
103
|
+
this.transcript = [];
|
|
104
|
+
clearAuthorizations();
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 切换智能体:持久化选择 + 重建 LLM service + 重置会话
|
|
108
|
+
*/
|
|
109
|
+
switchAgent(agentName) {
|
|
110
|
+
setActiveAgent(agentName);
|
|
111
|
+
// 重建 LLM service 以加载新 agent 的 system prompt
|
|
112
|
+
const config = loadConfig();
|
|
113
|
+
const activeModel = getActiveModel(config);
|
|
114
|
+
if (activeModel) {
|
|
115
|
+
try {
|
|
116
|
+
this.service = new LLMServiceImpl();
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
this.service = new MockService();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
this.service = new MockService();
|
|
124
|
+
}
|
|
125
|
+
// 重置会话上下文
|
|
126
|
+
this.saveSession();
|
|
127
|
+
this.session = this.createSession();
|
|
128
|
+
this.transcript = [];
|
|
129
|
+
}
|
|
130
|
+
/** 保存会话到文件 */
|
|
131
|
+
saveSession() {
|
|
132
|
+
try {
|
|
133
|
+
// 自动提取摘要:取首条用户消息前 80 个字符
|
|
134
|
+
if (!this.session.summary) {
|
|
135
|
+
const firstUser = this.session.messages.find((m) => m.type === 'user');
|
|
136
|
+
if (firstUser) {
|
|
137
|
+
this.session.summary = firstUser.content.slice(0, 80).replace(/\n/g, ' ');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const filePath = path.join(SESSIONS_DIR, `${this.session.id}.json`);
|
|
141
|
+
fs.writeFileSync(filePath, JSON.stringify(this.session, null, 2), 'utf-8');
|
|
142
|
+
}
|
|
143
|
+
catch { /* 静默失败 */ }
|
|
144
|
+
}
|
|
145
|
+
/** 列出所有历史会话(按更新时间倒序),返回摘要信息 */
|
|
146
|
+
static listSessions() {
|
|
147
|
+
try {
|
|
148
|
+
if (!fs.existsSync(SESSIONS_DIR))
|
|
149
|
+
return [];
|
|
150
|
+
const files = fs.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith('.json'));
|
|
151
|
+
const sessions = [];
|
|
152
|
+
for (const file of files) {
|
|
153
|
+
try {
|
|
154
|
+
const raw = fs.readFileSync(path.join(SESSIONS_DIR, file), 'utf-8');
|
|
155
|
+
const s = JSON.parse(raw);
|
|
156
|
+
sessions.push({
|
|
157
|
+
id: s.id,
|
|
158
|
+
summary: s.summary || '(无摘要)',
|
|
159
|
+
updatedAt: s.updatedAt,
|
|
160
|
+
totalTokens: s.totalTokens,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
catch { /* 跳过损坏文件 */ }
|
|
164
|
+
}
|
|
165
|
+
// 按更新时间倒序
|
|
166
|
+
sessions.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
167
|
+
return sessions;
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** 恢复指定会话:加载历史消息和 transcript */
|
|
174
|
+
loadSession(sessionId) {
|
|
175
|
+
try {
|
|
176
|
+
const filePath = path.join(SESSIONS_DIR, `${sessionId}.json`);
|
|
177
|
+
if (!fs.existsSync(filePath))
|
|
178
|
+
return null;
|
|
179
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
180
|
+
const loaded = JSON.parse(raw);
|
|
181
|
+
// 保存当前会话
|
|
182
|
+
this.saveSession();
|
|
183
|
+
// 恢复会话状态
|
|
184
|
+
this.session = loaded;
|
|
185
|
+
// 从历史消息重建 transcript
|
|
186
|
+
this.transcript = [];
|
|
187
|
+
for (const msg of loaded.messages) {
|
|
188
|
+
if (msg.type === 'user') {
|
|
189
|
+
this.transcript.push({ role: 'user', content: msg.content });
|
|
190
|
+
}
|
|
191
|
+
else if (msg.type === 'reasoning' && msg.status === 'success' && msg.content) {
|
|
192
|
+
// assistant 消息的 content 必须是 ContentBlock[],与 query.ts 中的构建方式一致
|
|
193
|
+
this.transcript.push({
|
|
194
|
+
role: 'assistant',
|
|
195
|
+
content: [{ type: 'text', text: msg.content }],
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
else if (msg.type === 'tool_exec' && msg.toolName && msg.toolResult !== undefined) {
|
|
199
|
+
// 工具调用:assistant tool_use + tool_result
|
|
200
|
+
this.transcript.push({
|
|
201
|
+
role: 'assistant',
|
|
202
|
+
content: [{ type: 'tool_use', id: msg.id, name: msg.toolName, input: msg.toolArgs ?? {} }],
|
|
203
|
+
});
|
|
204
|
+
this.transcript.push({
|
|
205
|
+
role: 'tool_result',
|
|
206
|
+
content: msg.toolResult,
|
|
207
|
+
toolUseId: msg.id,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return { session: this.session, messages: loaded.messages };
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
getSession() {
|
|
218
|
+
return this.session;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* 清理非当前会话的所有历史会话文件
|
|
222
|
+
* @returns 删除的会话数量
|
|
223
|
+
*/
|
|
224
|
+
clearOtherSessions() {
|
|
225
|
+
try {
|
|
226
|
+
if (!fs.existsSync(SESSIONS_DIR))
|
|
227
|
+
return 0;
|
|
228
|
+
const files = fs.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith('.json'));
|
|
229
|
+
const currentFile = `${this.session.id}.json`;
|
|
230
|
+
let count = 0;
|
|
231
|
+
for (const file of files) {
|
|
232
|
+
if (file === currentFile)
|
|
233
|
+
continue;
|
|
234
|
+
try {
|
|
235
|
+
fs.unlinkSync(path.join(SESSIONS_DIR, file));
|
|
236
|
+
count++;
|
|
237
|
+
}
|
|
238
|
+
catch { /* 跳过删除失败的文件 */ }
|
|
239
|
+
}
|
|
240
|
+
return count;
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 智能提示生成器
|
|
3
|
+
*
|
|
4
|
+
* 根据当前激活的智能体角色 + 项目上下文,调用 LLM 生成一条
|
|
5
|
+
* 符合角色设定且贴合当前项目的输入提示信息。
|
|
6
|
+
*/
|
|
7
|
+
/** 获取角色对应的静态兜底提示(同步,可用于初始 placeholder) */
|
|
8
|
+
export declare function getFallbackHint(agentName?: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* 调用 LLM 生成一条符合当前角色 + 项目上下文的输入提示
|
|
11
|
+
*
|
|
12
|
+
* @returns 生成的提示文本,失败时返回静态兜底
|
|
13
|
+
*/
|
|
14
|
+
export declare function generateAgentHint(): Promise<string>;
|