@seandong/seno 0.1.0
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/README.md +70 -0
- package/dist/agent/conversation.d.ts +39 -0
- package/dist/agent/conversation.js +60 -0
- package/dist/agent/conversation.js.map +1 -0
- package/dist/agent/loop.d.ts +41 -0
- package/dist/agent/loop.js +203 -0
- package/dist/agent/loop.js.map +1 -0
- package/dist/agent/session.d.ts +63 -0
- package/dist/agent/session.js +135 -0
- package/dist/agent/session.js.map +1 -0
- package/dist/cli/commands.d.ts +52 -0
- package/dist/cli/commands.js +667 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/logger.d.ts +38 -0
- package/dist/cli/logger.js +79 -0
- package/dist/cli/logger.js.map +1 -0
- package/dist/cli/output.d.ts +75 -0
- package/dist/cli/output.js +305 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/prompt.d.ts +30 -0
- package/dist/cli/prompt.js +196 -0
- package/dist/cli/prompt.js.map +1 -0
- package/dist/cli/repl.d.ts +27 -0
- package/dist/cli/repl.js +485 -0
- package/dist/cli/repl.js.map +1 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +170 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/model.d.ts +10 -0
- package/dist/commands/model.js +270 -0
- package/dist/commands/model.js.map +1 -0
- package/dist/config/manager.d.ts +67 -0
- package/dist/config/manager.js +194 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/config/types.d.ts +98 -0
- package/dist/config/types.js +2 -0
- package/dist/config/types.js.map +1 -0
- package/dist/errors.d.ts +37 -0
- package/dist/errors.js +54 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +185 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/anthropic.d.ts +27 -0
- package/dist/llm/anthropic.js +189 -0
- package/dist/llm/anthropic.js.map +1 -0
- package/dist/llm/factory.d.ts +47 -0
- package/dist/llm/factory.js +163 -0
- package/dist/llm/factory.js.map +1 -0
- package/dist/llm/openai-codex.d.ts +45 -0
- package/dist/llm/openai-codex.js +398 -0
- package/dist/llm/openai-codex.js.map +1 -0
- package/dist/llm/openai.d.ts +16 -0
- package/dist/llm/openai.js +288 -0
- package/dist/llm/openai.js.map +1 -0
- package/dist/llm/provider.d.ts +19 -0
- package/dist/llm/provider.js +2 -0
- package/dist/llm/provider.js.map +1 -0
- package/dist/llm/types.d.ts +102 -0
- package/dist/llm/types.js +2 -0
- package/dist/llm/types.js.map +1 -0
- package/dist/mcp/bridge.d.ts +30 -0
- package/dist/mcp/bridge.js +73 -0
- package/dist/mcp/bridge.js.map +1 -0
- package/dist/mcp/config.d.ts +6 -0
- package/dist/mcp/config.js +26 -0
- package/dist/mcp/config.js.map +1 -0
- package/dist/mcp/manager.d.ts +54 -0
- package/dist/mcp/manager.js +171 -0
- package/dist/mcp/manager.js.map +1 -0
- package/dist/prompts/system.d.ts +14 -0
- package/dist/prompts/system.js +194 -0
- package/dist/prompts/system.js.map +1 -0
- package/dist/skills/loader.d.ts +7 -0
- package/dist/skills/loader.js +81 -0
- package/dist/skills/loader.js.map +1 -0
- package/dist/skills/registry.d.ts +48 -0
- package/dist/skills/registry.js +104 -0
- package/dist/skills/registry.js.map +1 -0
- package/dist/skills/sync.d.ts +34 -0
- package/dist/skills/sync.js +179 -0
- package/dist/skills/sync.js.map +1 -0
- package/dist/skills/types.d.ts +29 -0
- package/dist/skills/types.js +2 -0
- package/dist/skills/types.js.map +1 -0
- package/dist/tools/ask.d.ts +16 -0
- package/dist/tools/ask.js +57 -0
- package/dist/tools/ask.js.map +1 -0
- package/dist/tools/registry.d.ts +54 -0
- package/dist/tools/registry.js +114 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/shell.d.ts +10 -0
- package/dist/tools/shell.js +131 -0
- package/dist/tools/shell.js.map +1 -0
- package/dist/tools/ssh.d.ts +40 -0
- package/dist/tools/ssh.js +302 -0
- package/dist/tools/ssh.js.map +1 -0
- package/dist/tools/types.d.ts +20 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/utils/retry.d.ts +20 -0
- package/dist/utils/retry.js +33 -0
- package/dist/utils/retry.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SenoConfig } from '../config/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* 同步结果
|
|
4
|
+
*/
|
|
5
|
+
export interface SyncResult {
|
|
6
|
+
success: boolean;
|
|
7
|
+
skillCount?: number;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 获取 Skills 目录路径 ~/.seno/skills/
|
|
12
|
+
*/
|
|
13
|
+
export declare function getSkillsDir(): string;
|
|
14
|
+
/**
|
|
15
|
+
* 检查是否需要同步 Skills
|
|
16
|
+
*
|
|
17
|
+
* 满足任一条件返回 true:
|
|
18
|
+
* 1. ~/.seno/skills/index.json 不存在
|
|
19
|
+
* 2. ones_skills.last_updated_at 不存在
|
|
20
|
+
* 3. last_updated_at 距今超过 1 天
|
|
21
|
+
*/
|
|
22
|
+
export declare function needsSync(config: SenoConfig): Promise<boolean>;
|
|
23
|
+
/**
|
|
24
|
+
* 执行 Skills 同步
|
|
25
|
+
*
|
|
26
|
+
* 流程:
|
|
27
|
+
* 1. 检查 git 可用性
|
|
28
|
+
* 2. clone 仓库到临时目录
|
|
29
|
+
* 3. 验证 index.json 存在
|
|
30
|
+
* 4. 全量替换 ~/.seno/skills/
|
|
31
|
+
* 5. 更新 config.ones_skills.last_updated_at
|
|
32
|
+
* 6. 清理临时目录
|
|
33
|
+
*/
|
|
34
|
+
export declare function syncSkills(config: SenoConfig): Promise<SyncResult>;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { mkdir, rm, cp, access, readFile } from 'node:fs/promises';
|
|
4
|
+
import { getConfigDir, loadConfig, saveConfig } from '../config/manager.js';
|
|
5
|
+
import { logger } from '../cli/logger.js';
|
|
6
|
+
const SKILLS_DIR_NAME = 'skills';
|
|
7
|
+
const TEMP_CLONE_DIR_NAME = 'ones-skills';
|
|
8
|
+
const SYNC_INTERVAL_MS = 24 * 60 * 60 * 1000; // 1 天
|
|
9
|
+
/**
|
|
10
|
+
* 获取 Skills 目录路径 ~/.seno/skills/
|
|
11
|
+
*/
|
|
12
|
+
export function getSkillsDir() {
|
|
13
|
+
return join(getConfigDir(), SKILLS_DIR_NAME);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 获取临时克隆目录路径 ~/.seno/ones-skills/
|
|
17
|
+
*/
|
|
18
|
+
function getTempCloneDir() {
|
|
19
|
+
return join(getConfigDir(), TEMP_CLONE_DIR_NAME);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 检查文件是否存在
|
|
23
|
+
*/
|
|
24
|
+
async function fileExists(path) {
|
|
25
|
+
try {
|
|
26
|
+
await access(path);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 检查 git 是否可用
|
|
35
|
+
*/
|
|
36
|
+
async function checkGitAvailable() {
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
const child = spawn('git', ['--version'], {
|
|
39
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
|
40
|
+
});
|
|
41
|
+
child.on('close', (code) => resolve(code === 0));
|
|
42
|
+
child.on('error', () => resolve(false));
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 执行 git clone --depth 1
|
|
47
|
+
*/
|
|
48
|
+
function gitClone(repoUrl, targetDir) {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const child = spawn('git', ['clone', '--depth', '1', repoUrl, targetDir], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
51
|
+
let stderr = '';
|
|
52
|
+
child.stderr?.on('data', (data) => {
|
|
53
|
+
stderr += data.toString();
|
|
54
|
+
});
|
|
55
|
+
child.on('close', (code) => {
|
|
56
|
+
if (code === 0)
|
|
57
|
+
resolve();
|
|
58
|
+
else
|
|
59
|
+
reject(new Error(`git clone 失败 (exit ${code}): ${stderr.trim()}`));
|
|
60
|
+
});
|
|
61
|
+
child.on('error', (err) => reject(err));
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 检查是否需要同步 Skills
|
|
66
|
+
*
|
|
67
|
+
* 满足任一条件返回 true:
|
|
68
|
+
* 1. ~/.seno/skills/index.json 不存在
|
|
69
|
+
* 2. ones_skills.last_updated_at 不存在
|
|
70
|
+
* 3. last_updated_at 距今超过 1 天
|
|
71
|
+
*/
|
|
72
|
+
export async function needsSync(config) {
|
|
73
|
+
// 检查 index.json 是否存在
|
|
74
|
+
const indexPath = join(getSkillsDir(), 'index.json');
|
|
75
|
+
if (!(await fileExists(indexPath))) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
// 检查更新时间
|
|
79
|
+
const lastUpdatedAt = config.ones_skills?.last_updated_at;
|
|
80
|
+
if (!lastUpdatedAt) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
const lastSync = new Date(lastUpdatedAt).getTime();
|
|
84
|
+
if (isNaN(lastSync)) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
return Date.now() - lastSync > SYNC_INTERVAL_MS;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* 执行 Skills 同步
|
|
91
|
+
*
|
|
92
|
+
* 流程:
|
|
93
|
+
* 1. 检查 git 可用性
|
|
94
|
+
* 2. clone 仓库到临时目录
|
|
95
|
+
* 3. 验证 index.json 存在
|
|
96
|
+
* 4. 全量替换 ~/.seno/skills/
|
|
97
|
+
* 5. 更新 config.ones_skills.last_updated_at
|
|
98
|
+
* 6. 清理临时目录
|
|
99
|
+
*/
|
|
100
|
+
export async function syncSkills(config) {
|
|
101
|
+
const tempDir = getTempCloneDir();
|
|
102
|
+
const skillsDir = getSkillsDir();
|
|
103
|
+
try {
|
|
104
|
+
// 1. 检查 git 可用性
|
|
105
|
+
if (!(await checkGitAvailable())) {
|
|
106
|
+
return {
|
|
107
|
+
success: false,
|
|
108
|
+
error: 'git 未安装,请先安装 git 后重试',
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// 2. 确定仓库地址
|
|
112
|
+
if (!config.ones_skills?.repository) {
|
|
113
|
+
return {
|
|
114
|
+
success: false,
|
|
115
|
+
error: '未配置 ones_skills.repository,请在 ~/.seno/config.json 中设置',
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const repoUrl = config.ones_skills.repository;
|
|
119
|
+
// 3. 清理可能残留的临时目录
|
|
120
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
121
|
+
// 4. 克隆仓库
|
|
122
|
+
await gitClone(repoUrl, tempDir);
|
|
123
|
+
// 5. 验证克隆内容含 index.json
|
|
124
|
+
const tempIndexPath = join(tempDir, 'index.json');
|
|
125
|
+
if (!(await fileExists(tempIndexPath))) {
|
|
126
|
+
return {
|
|
127
|
+
success: false,
|
|
128
|
+
error: '仓库中未找到 index.json',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// 6. 全量替换 skills 目录
|
|
132
|
+
await rm(skillsDir, { recursive: true, force: true });
|
|
133
|
+
await mkdir(skillsDir, { recursive: true });
|
|
134
|
+
await cp(tempDir, skillsDir, {
|
|
135
|
+
recursive: true,
|
|
136
|
+
filter: (src) => !src.includes('/.git'),
|
|
137
|
+
});
|
|
138
|
+
// 7. 读取 skill 数量
|
|
139
|
+
let skillCount = 0;
|
|
140
|
+
try {
|
|
141
|
+
const indexContent = await readFile(join(skillsDir, 'index.json'), 'utf-8');
|
|
142
|
+
const index = JSON.parse(indexContent);
|
|
143
|
+
skillCount = index.skills.length;
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// 解析失败不阻塞
|
|
147
|
+
}
|
|
148
|
+
// 8. 更新配置
|
|
149
|
+
try {
|
|
150
|
+
const freshConfig = await loadConfig();
|
|
151
|
+
if (freshConfig) {
|
|
152
|
+
if (!freshConfig.ones_skills) {
|
|
153
|
+
freshConfig.ones_skills = { repository: repoUrl };
|
|
154
|
+
}
|
|
155
|
+
freshConfig.ones_skills.last_updated_at = new Date().toISOString();
|
|
156
|
+
await saveConfig(freshConfig);
|
|
157
|
+
// 同步回调用方的 config 对象
|
|
158
|
+
if (!config.ones_skills) {
|
|
159
|
+
config.ones_skills = { repository: repoUrl };
|
|
160
|
+
}
|
|
161
|
+
config.ones_skills.last_updated_at = freshConfig.ones_skills.last_updated_at;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
logger.error('skills', `更新 config 失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
166
|
+
}
|
|
167
|
+
return { success: true, skillCount };
|
|
168
|
+
}
|
|
169
|
+
finally {
|
|
170
|
+
// 9. 清理临时目录
|
|
171
|
+
try {
|
|
172
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// 清理失败不阻塞
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/skills/sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill 能力域
|
|
3
|
+
*/
|
|
4
|
+
export type SkillDomain = 'devops' | 'qa' | 'plugin';
|
|
5
|
+
/**
|
|
6
|
+
* Skill 元数据
|
|
7
|
+
*/
|
|
8
|
+
export interface SkillMeta {
|
|
9
|
+
/** 技能唯一标识符,如 'ones-k3s-cli-install' */
|
|
10
|
+
name: string;
|
|
11
|
+
/** 能力域 */
|
|
12
|
+
domain: SkillDomain;
|
|
13
|
+
/** 类型,目前固定为 'skill' */
|
|
14
|
+
type: string;
|
|
15
|
+
/** 技能描述(中文,供 LLM 理解用途) */
|
|
16
|
+
description: string;
|
|
17
|
+
/** 搜索标签 */
|
|
18
|
+
tags: string[];
|
|
19
|
+
/** SKILL.md 所在目录路径,相对于项目根目录 */
|
|
20
|
+
path: string;
|
|
21
|
+
/** 技能版本号 */
|
|
22
|
+
version?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Skill 索引文件结构
|
|
26
|
+
*/
|
|
27
|
+
export interface SkillIndex {
|
|
28
|
+
skills: SkillMeta[];
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/skills/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Tool } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* 用户输入回调类型
|
|
4
|
+
* REPL 层注入的回调函数,用于在终端获取用户输入
|
|
5
|
+
*/
|
|
6
|
+
export type PromptUserFn = (question: string) => Promise<string>;
|
|
7
|
+
/**
|
|
8
|
+
* 设置用户输入回调
|
|
9
|
+
* 由 REPL 层在 readline 创建后调用
|
|
10
|
+
*/
|
|
11
|
+
export declare function setPromptUser(fn: PromptUserFn): void;
|
|
12
|
+
/**
|
|
13
|
+
* 创建 ask_user 工具
|
|
14
|
+
* 让 Agent 在执行危险操作前暂停并请求用户确认或收集参数
|
|
15
|
+
*/
|
|
16
|
+
export declare function createAskUserTool(): Tool;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { UserInterruptError } from '../errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* 模块级 promptUser 引用
|
|
4
|
+
* 通过 setPromptUser() 延迟绑定,由 REPL 层在创建 readline 后注入
|
|
5
|
+
*/
|
|
6
|
+
let _promptUser = null;
|
|
7
|
+
/**
|
|
8
|
+
* 设置用户输入回调
|
|
9
|
+
* 由 REPL 层在 readline 创建后调用
|
|
10
|
+
*/
|
|
11
|
+
export function setPromptUser(fn) {
|
|
12
|
+
_promptUser = fn;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 创建 ask_user 工具
|
|
16
|
+
* 让 Agent 在执行危险操作前暂停并请求用户确认或收集参数
|
|
17
|
+
*/
|
|
18
|
+
export function createAskUserTool() {
|
|
19
|
+
return {
|
|
20
|
+
name: 'ask_user',
|
|
21
|
+
description: '向用户请求确认或输入信息。用于危险操作确认(如卸载、删除)或收集必要参数(如服务器地址、版本号)。重要:每次调用只问一个问题,用户每次只能输入一行文本。如需收集多个参数,必须多次调用此工具,每次只问一个问题。',
|
|
22
|
+
inputSchema: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
question: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: '要向用户提问的内容。每次只问一个问题,不要在一次调用中包含多个问题。',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
required: ['question'],
|
|
31
|
+
},
|
|
32
|
+
execute: async (args) => {
|
|
33
|
+
const question = args.question;
|
|
34
|
+
if (!_promptUser) {
|
|
35
|
+
return {
|
|
36
|
+
content: '用户输入不可用(非交互模式)',
|
|
37
|
+
isError: true,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const answer = await _promptUser(question);
|
|
42
|
+
return { content: answer };
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
// 用户中断:放行,让错误传播到 agent loop 以终止当前任务
|
|
46
|
+
if (error instanceof UserInterruptError) {
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
content: `获取用户输入失败: ${error instanceof Error ? error.message : String(error)}`,
|
|
51
|
+
isError: true,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=ask.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ask.js","sourceRoot":"","sources":["../../src/tools/ask.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAQlD;;;GAGG;AACH,IAAI,WAAW,GAAwB,IAAI,CAAC;AAE5C;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,EAAgB;IAC5C,WAAW,GAAG,EAAE,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,WAAW,EACT,0GAA0G;QAC5G,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,QAAQ,EAAE;oBACR,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,oCAAoC;iBAClD;aACF;YACD,QAAQ,EAAE,CAAC,UAAU,CAAC;SACvB;QACD,OAAO,EAAE,KAAK,EAAE,IAA6B,EAAuB,EAAE;YACpE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAkB,CAAC;YAEzC,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,OAAO;oBACL,OAAO,EAAE,gBAAgB;oBACzB,OAAO,EAAE,IAAI;iBACd,CAAC;YACJ,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,CAAC;gBAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;YAC7B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,oCAAoC;gBACpC,IAAI,KAAK,YAAY,kBAAkB,EAAE,CAAC;oBACxC,MAAM,KAAK,CAAC;gBACd,CAAC;gBACD,OAAO;oBACL,OAAO,EAAE,aAAa,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;oBAC9E,OAAO,EAAE,IAAI;iBACd,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Tool, ToolResult } from './types.js';
|
|
2
|
+
import type { ToolDefinition } from '../llm/types.js';
|
|
3
|
+
import type { SkillMeta } from '../skills/types.js';
|
|
4
|
+
import type { MCPManager } from '../mcp/manager.js';
|
|
5
|
+
/**
|
|
6
|
+
* 统一工具注册中心
|
|
7
|
+
* 管理所有可用工具(MCP 工具 + 未来的原生工具)
|
|
8
|
+
*/
|
|
9
|
+
export declare class ToolRegistry {
|
|
10
|
+
private tools;
|
|
11
|
+
private sshPool;
|
|
12
|
+
private skillRegistry;
|
|
13
|
+
/**
|
|
14
|
+
* 注册单个工具
|
|
15
|
+
*/
|
|
16
|
+
register(tool: Tool): void;
|
|
17
|
+
/**
|
|
18
|
+
* 注册 Native Tools(本地工具)
|
|
19
|
+
*/
|
|
20
|
+
registerNativeTools(): void;
|
|
21
|
+
/**
|
|
22
|
+
* 加载 Skill 索引
|
|
23
|
+
*/
|
|
24
|
+
loadSkills(indexPath: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* 获取已加载的技能列表(供 System Prompt 构建使用)
|
|
27
|
+
*/
|
|
28
|
+
getSkills(): SkillMeta[];
|
|
29
|
+
/**
|
|
30
|
+
* 清理资源(关闭 SSH 连接等)
|
|
31
|
+
*/
|
|
32
|
+
cleanup(): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* 批量注册 MCP 工具
|
|
35
|
+
* 从 MCPManager 获取所有工具,转换为统一 Tool 接口
|
|
36
|
+
*/
|
|
37
|
+
registerMCPTools(manager: MCPManager): void;
|
|
38
|
+
/**
|
|
39
|
+
* 获取所有工具的 Anthropic 格式定义(传给 LLM)
|
|
40
|
+
*/
|
|
41
|
+
getToolDefinitions(): ToolDefinition[];
|
|
42
|
+
/**
|
|
43
|
+
* 执行工具
|
|
44
|
+
*/
|
|
45
|
+
executeTool(name: string, args: Record<string, unknown>): Promise<ToolResult>;
|
|
46
|
+
/**
|
|
47
|
+
* 查询工具是否存在
|
|
48
|
+
*/
|
|
49
|
+
hasTool(name: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* 已注册工具数量
|
|
52
|
+
*/
|
|
53
|
+
get size(): number;
|
|
54
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { UserInterruptError } from '../errors.js';
|
|
2
|
+
import { getToolDefinitions, callMcpTool } from '../mcp/bridge.js';
|
|
3
|
+
import { createShellTool } from './shell.js';
|
|
4
|
+
import { SSHConnectionPool, createSSHTool } from './ssh.js';
|
|
5
|
+
import { createAskUserTool } from './ask.js';
|
|
6
|
+
import { SkillRegistry, createListSkillsTool } from '../skills/registry.js';
|
|
7
|
+
import { createLoadSkillTool } from '../skills/loader.js';
|
|
8
|
+
/**
|
|
9
|
+
* 统一工具注册中心
|
|
10
|
+
* 管理所有可用工具(MCP 工具 + 未来的原生工具)
|
|
11
|
+
*/
|
|
12
|
+
export class ToolRegistry {
|
|
13
|
+
tools = new Map();
|
|
14
|
+
sshPool = new SSHConnectionPool();
|
|
15
|
+
skillRegistry = new SkillRegistry();
|
|
16
|
+
/**
|
|
17
|
+
* 注册单个工具
|
|
18
|
+
*/
|
|
19
|
+
register(tool) {
|
|
20
|
+
this.tools.set(tool.name, tool);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 注册 Native Tools(本地工具)
|
|
24
|
+
*/
|
|
25
|
+
registerNativeTools() {
|
|
26
|
+
this.register(createShellTool());
|
|
27
|
+
this.register(createSSHTool(this.sshPool));
|
|
28
|
+
this.register(createAskUserTool());
|
|
29
|
+
this.register(createListSkillsTool(this.skillRegistry));
|
|
30
|
+
this.register(createLoadSkillTool(this.skillRegistry));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 加载 Skill 索引
|
|
34
|
+
*/
|
|
35
|
+
async loadSkills(indexPath) {
|
|
36
|
+
await this.skillRegistry.load(indexPath);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 获取已加载的技能列表(供 System Prompt 构建使用)
|
|
40
|
+
*/
|
|
41
|
+
getSkills() {
|
|
42
|
+
return this.skillRegistry.getAll();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 清理资源(关闭 SSH 连接等)
|
|
46
|
+
*/
|
|
47
|
+
async cleanup() {
|
|
48
|
+
await this.sshPool.closeAll();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 批量注册 MCP 工具
|
|
52
|
+
* 从 MCPManager 获取所有工具,转换为统一 Tool 接口
|
|
53
|
+
*/
|
|
54
|
+
registerMCPTools(manager) {
|
|
55
|
+
const definitions = getToolDefinitions(manager);
|
|
56
|
+
for (const def of definitions) {
|
|
57
|
+
const tool = {
|
|
58
|
+
name: def.name,
|
|
59
|
+
description: def.description,
|
|
60
|
+
inputSchema: def.input_schema,
|
|
61
|
+
execute: async (args) => {
|
|
62
|
+
const result = await callMcpTool(manager, def.name, args);
|
|
63
|
+
return { content: result.content, isError: result.isError };
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
this.tools.set(tool.name, tool);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 获取所有工具的 Anthropic 格式定义(传给 LLM)
|
|
71
|
+
*/
|
|
72
|
+
getToolDefinitions() {
|
|
73
|
+
return Array.from(this.tools.values()).map((tool) => ({
|
|
74
|
+
name: tool.name,
|
|
75
|
+
description: tool.description,
|
|
76
|
+
input_schema: tool.inputSchema,
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 执行工具
|
|
81
|
+
*/
|
|
82
|
+
async executeTool(name, args) {
|
|
83
|
+
const tool = this.tools.get(name);
|
|
84
|
+
if (!tool) {
|
|
85
|
+
return { content: `未知工具: ${name}`, isError: true };
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
return await tool.execute(args);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
// 用户中断:放行,让错误传播到 agent loop 以终止当前任务
|
|
92
|
+
if (error instanceof UserInterruptError) {
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
content: `工具执行失败 [${name}]: ${error instanceof Error ? error.message : String(error)}`,
|
|
97
|
+
isError: true,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 查询工具是否存在
|
|
103
|
+
*/
|
|
104
|
+
hasTool(name) {
|
|
105
|
+
return this.tools.has(name);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 已注册工具数量
|
|
109
|
+
*/
|
|
110
|
+
get size() {
|
|
111
|
+
return this.tools.size;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/tools/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { getLogsDir } from '../config/manager.js';
|
|
5
|
+
const MAX_OUTPUT_LENGTH = 10000;
|
|
6
|
+
const KEEP_LENGTH = 5000;
|
|
7
|
+
const DEFAULT_TIMEOUT_SECONDS = 60;
|
|
8
|
+
/**
|
|
9
|
+
* 写入 Shell 执行日志到 ~/.seno/logs/shell.log
|
|
10
|
+
*/
|
|
11
|
+
function logShellExecution(command, exitCode, durationMs) {
|
|
12
|
+
try {
|
|
13
|
+
const logsDir = getLogsDir();
|
|
14
|
+
mkdirSync(logsDir, { recursive: true });
|
|
15
|
+
const logPath = join(logsDir, 'shell.log');
|
|
16
|
+
const timestamp = new Date().toISOString();
|
|
17
|
+
const line = `[${timestamp}] [Shell] command="${command}" exitCode=${exitCode} duration=${durationMs}ms\n`;
|
|
18
|
+
appendFileSync(logPath, line, 'utf-8');
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// 日志写入失败不影响命令执行
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 截断过长的输出
|
|
26
|
+
* 超过 MAX_OUTPUT_LENGTH 时保留前后各 KEEP_LENGTH 字符
|
|
27
|
+
*/
|
|
28
|
+
export function truncateOutput(output) {
|
|
29
|
+
if (output.length <= MAX_OUTPUT_LENGTH) {
|
|
30
|
+
return output;
|
|
31
|
+
}
|
|
32
|
+
const head = output.slice(0, KEEP_LENGTH);
|
|
33
|
+
const tail = output.slice(-KEEP_LENGTH);
|
|
34
|
+
return `${head}\n\n[... 输出已截断,共 ${output.length} 字符 ...]\n\n${tail}`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 执行 Shell 命令
|
|
38
|
+
*/
|
|
39
|
+
function executeCommand(command, timeoutSeconds) {
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
const startTime = Date.now();
|
|
42
|
+
let output = '';
|
|
43
|
+
let finished = false;
|
|
44
|
+
let timedOut = false;
|
|
45
|
+
const child = spawn(command, {
|
|
46
|
+
shell: true,
|
|
47
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
48
|
+
});
|
|
49
|
+
// 合并 stdout 和 stderr
|
|
50
|
+
child.stdout?.on('data', (data) => {
|
|
51
|
+
output += data.toString();
|
|
52
|
+
});
|
|
53
|
+
child.stderr?.on('data', (data) => {
|
|
54
|
+
output += data.toString();
|
|
55
|
+
});
|
|
56
|
+
// 超时处理
|
|
57
|
+
const timer = setTimeout(() => {
|
|
58
|
+
if (!finished) {
|
|
59
|
+
timedOut = true;
|
|
60
|
+
child.kill('SIGTERM');
|
|
61
|
+
}
|
|
62
|
+
}, timeoutSeconds * 1000);
|
|
63
|
+
child.on('close', (exitCode) => {
|
|
64
|
+
finished = true;
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
const durationMs = Date.now() - startTime;
|
|
67
|
+
logShellExecution(command, exitCode, durationMs);
|
|
68
|
+
if (timedOut) {
|
|
69
|
+
const partialOutput = truncateOutput(output);
|
|
70
|
+
resolve({
|
|
71
|
+
content: `命令执行超时(${timeoutSeconds}秒)\n\n已收集的部分输出:\n${partialOutput}`,
|
|
72
|
+
isError: true,
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const truncated = truncateOutput(output);
|
|
77
|
+
if (exitCode === 0) {
|
|
78
|
+
resolve({
|
|
79
|
+
content: truncated || '(命令执行成功,无输出)',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
resolve({
|
|
84
|
+
content: `命令退出码: ${exitCode}\n\n${truncated}`,
|
|
85
|
+
isError: true,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
child.on('error', (error) => {
|
|
90
|
+
finished = true;
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
const durationMs = Date.now() - startTime;
|
|
93
|
+
logShellExecution(command, null, durationMs);
|
|
94
|
+
resolve({
|
|
95
|
+
content: `命令执行失败: ${error.message}`,
|
|
96
|
+
isError: true,
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 创建 Shell 命令执行工具
|
|
103
|
+
*/
|
|
104
|
+
export function createShellTool() {
|
|
105
|
+
return {
|
|
106
|
+
name: 'execute_command',
|
|
107
|
+
description: '在本地执行 Shell 命令。用于文件操作、环境检测、脚本执行等本地任务。',
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: {
|
|
111
|
+
command: {
|
|
112
|
+
type: 'string',
|
|
113
|
+
description: '要执行的完整 Shell 命令',
|
|
114
|
+
},
|
|
115
|
+
timeout: {
|
|
116
|
+
type: 'number',
|
|
117
|
+
description: '超时时间(秒),默认 60 秒',
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
required: ['command'],
|
|
121
|
+
},
|
|
122
|
+
execute: async (args) => {
|
|
123
|
+
const command = args.command;
|
|
124
|
+
const timeout = typeof args.timeout === 'number'
|
|
125
|
+
? args.timeout
|
|
126
|
+
: DEFAULT_TIMEOUT_SECONDS;
|
|
127
|
+
return executeCommand(command, timeout);
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=shell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/tools/shell.ts"],"names":[],"mappings":"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"}
|