feat(hands): restructure Hands UI with Chinese localization
Major changes: - Add HandList.tsx component for left sidebar - Add HandTaskPanel.tsx for middle content area - Restructure Sidebar tabs: 分身/HANDS/Workflow - Remove Hands tab from RightPanel - Localize all UI text to Chinese - Archive legacy OpenClaw documentation - Add Hands integration lessons document - Update feature checklist with new components UI improvements: - Left sidebar now shows Hands list with status icons - Middle area shows selected Hand's tasks and results - Consistent styling with Tailwind CSS - Chinese status labels and buttons Documentation: - Create docs/archive/openclaw-legacy/ for old docs - Add docs/knowledge-base/hands-integration-lessons.md - Update docs/knowledge-base/feature-checklist.md - Update docs/knowledge-base/README.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
/**
|
||||
* ZCLAW Chinese Models Plugin
|
||||
*
|
||||
*
|
||||
* Registers Chinese AI model providers for OpenClaw Gateway:
|
||||
* - Zhipu GLM (智谱)
|
||||
* - Qwen (通义千问)
|
||||
* - Kimi (月之暗面)
|
||||
* - MiniMax
|
||||
*
|
||||
* - DeepSeek
|
||||
* - Baidu ERNIE (文心一言)
|
||||
* - iFlytek Spark (讯飞星火)
|
||||
*
|
||||
* All providers use OpenAI-compatible API format.
|
||||
*/
|
||||
|
||||
@@ -41,22 +44,6 @@ const zhipuProvider: ProviderDefinition = {
|
||||
baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
|
||||
apiKeyEnvVar: 'ZHIPU_API_KEY',
|
||||
models: [
|
||||
{
|
||||
id: 'glm-5',
|
||||
alias: 'GLM-5',
|
||||
contextWindow: 128000,
|
||||
maxOutputTokens: 4096,
|
||||
supportsVision: true,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
{
|
||||
id: 'glm-4.7',
|
||||
alias: 'GLM-4.7',
|
||||
contextWindow: 128000,
|
||||
maxOutputTokens: 4096,
|
||||
supportsVision: true,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
{
|
||||
id: 'glm-4-plus',
|
||||
alias: 'GLM-4-Plus',
|
||||
@@ -73,6 +60,22 @@ const zhipuProvider: ProviderDefinition = {
|
||||
supportsVision: false,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
{
|
||||
id: 'glm-4v-plus',
|
||||
alias: 'GLM-4V-Plus (视觉)',
|
||||
contextWindow: 128000,
|
||||
maxOutputTokens: 4096,
|
||||
supportsVision: true,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
{
|
||||
id: 'glm-z1-airx',
|
||||
alias: 'GLM-Z1-AirX (推理)',
|
||||
contextWindow: 128000,
|
||||
maxOutputTokens: 16384,
|
||||
supportsVision: false,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
],
|
||||
headers: (apiKey: string) => ({
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
@@ -87,14 +90,6 @@ const qwenProvider: ProviderDefinition = {
|
||||
baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
||||
apiKeyEnvVar: 'QWEN_API_KEY',
|
||||
models: [
|
||||
{
|
||||
id: 'qwen3.5-plus',
|
||||
alias: 'Qwen3.5+',
|
||||
contextWindow: 131072,
|
||||
maxOutputTokens: 8192,
|
||||
supportsVision: false,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
{
|
||||
id: 'qwen-max',
|
||||
alias: 'Qwen-Max',
|
||||
@@ -103,14 +98,38 @@ const qwenProvider: ProviderDefinition = {
|
||||
supportsVision: false,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
{
|
||||
id: 'qwen-plus',
|
||||
alias: 'Qwen-Plus',
|
||||
contextWindow: 128000,
|
||||
maxOutputTokens: 8192,
|
||||
supportsVision: false,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
{
|
||||
id: 'qwen-turbo',
|
||||
alias: 'Qwen-Turbo',
|
||||
contextWindow: 128000,
|
||||
maxOutputTokens: 8192,
|
||||
supportsVision: false,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
{
|
||||
id: 'qwen-vl-max',
|
||||
alias: 'Qwen-VL-Max',
|
||||
alias: 'Qwen-VL-Max (视觉)',
|
||||
contextWindow: 32768,
|
||||
maxOutputTokens: 4096,
|
||||
maxOutputTokens: 8192,
|
||||
supportsVision: true,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
{
|
||||
id: 'qwen-long',
|
||||
alias: 'Qwen-Long (长上下文)',
|
||||
contextWindow: 1000000,
|
||||
maxOutputTokens: 10000,
|
||||
supportsVision: false,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
],
|
||||
headers: (apiKey: string) => ({
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
@@ -126,17 +145,25 @@ const kimiProvider: ProviderDefinition = {
|
||||
apiKeyEnvVar: 'KIMI_API_KEY',
|
||||
models: [
|
||||
{
|
||||
id: 'kimi-k2.5',
|
||||
alias: 'Kimi-K2.5',
|
||||
contextWindow: 131072,
|
||||
maxOutputTokens: 8192,
|
||||
id: 'moonshot-v1-8k',
|
||||
alias: 'Kimi (8K)',
|
||||
contextWindow: 8192,
|
||||
maxOutputTokens: 4096,
|
||||
supportsVision: false,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
{
|
||||
id: 'moonshot-v1-32k',
|
||||
alias: 'Kimi (32K)',
|
||||
contextWindow: 32768,
|
||||
maxOutputTokens: 4096,
|
||||
supportsVision: false,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
{
|
||||
id: 'moonshot-v1-128k',
|
||||
alias: 'Moonshot-128K',
|
||||
contextWindow: 128000,
|
||||
alias: 'Kimi (128K)',
|
||||
contextWindow: 131072,
|
||||
maxOutputTokens: 4096,
|
||||
supportsVision: false,
|
||||
supportsStreaming: true,
|
||||
@@ -156,21 +183,119 @@ const minimaxProvider: ProviderDefinition = {
|
||||
apiKeyEnvVar: 'MINIMAX_API_KEY',
|
||||
models: [
|
||||
{
|
||||
id: 'minimax-m2.5',
|
||||
alias: 'MiniMax-M2.5',
|
||||
contextWindow: 245760,
|
||||
id: 'abab6.5s-chat',
|
||||
alias: 'MiniMax-6.5s',
|
||||
contextWindow: 245000,
|
||||
maxOutputTokens: 16384,
|
||||
supportsVision: false,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
{
|
||||
id: 'abab6.5s-chat',
|
||||
alias: 'ABAB-6.5s',
|
||||
contextWindow: 245760,
|
||||
id: 'abab6.5g-chat',
|
||||
alias: 'MiniMax-6.5g',
|
||||
contextWindow: 128000,
|
||||
maxOutputTokens: 8192,
|
||||
supportsVision: false,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
{
|
||||
id: 'abab5.5-chat',
|
||||
alias: 'MiniMax-5.5',
|
||||
contextWindow: 16384,
|
||||
maxOutputTokens: 4096,
|
||||
supportsVision: false,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
],
|
||||
headers: (apiKey: string) => ({
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
};
|
||||
|
||||
// DeepSeek Provider
|
||||
const deepseekProvider: ProviderDefinition = {
|
||||
id: 'deepseek',
|
||||
name: 'DeepSeek',
|
||||
baseUrl: 'https://api.deepseek.com/v1',
|
||||
apiKeyEnvVar: 'DEEPSEEK_API_KEY',
|
||||
models: [
|
||||
{
|
||||
id: 'deepseek-chat',
|
||||
alias: 'DeepSeek Chat',
|
||||
contextWindow: 64000,
|
||||
maxOutputTokens: 4096,
|
||||
supportsVision: false,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
{
|
||||
id: 'deepseek-reasoner',
|
||||
alias: 'DeepSeek Reasoner (R1)',
|
||||
contextWindow: 64000,
|
||||
maxOutputTokens: 8192,
|
||||
supportsVision: false,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
],
|
||||
headers: (apiKey: string) => ({
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
};
|
||||
|
||||
// 百度文心一言 Provider (OpenAI-compatible endpoint)
|
||||
const baiduProvider: ProviderDefinition = {
|
||||
id: 'baidu',
|
||||
name: 'Baidu ERNIE (文心一言)',
|
||||
baseUrl: 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat',
|
||||
apiKeyEnvVar: 'BAIDU_API_KEY',
|
||||
models: [
|
||||
{
|
||||
id: 'ernie-4.0-8k',
|
||||
alias: 'ERNIE-4.0 (8K)',
|
||||
contextWindow: 8192,
|
||||
maxOutputTokens: 2048,
|
||||
supportsVision: false,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
{
|
||||
id: 'ernie-3.5-8k',
|
||||
alias: 'ERNIE-3.5 (8K)',
|
||||
contextWindow: 8192,
|
||||
maxOutputTokens: 2048,
|
||||
supportsVision: false,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
],
|
||||
headers: (apiKey: string) => ({
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
};
|
||||
|
||||
// 讯飞星火 Provider (OpenAI-compatible endpoint)
|
||||
const sparkProvider: ProviderDefinition = {
|
||||
id: 'spark',
|
||||
name: 'iFlytek Spark (讯飞星火)',
|
||||
baseUrl: 'https://spark-api-open.xf-yun.com/v1',
|
||||
apiKeyEnvVar: 'SPARK_API_KEY',
|
||||
models: [
|
||||
{
|
||||
id: 'generalv3.5',
|
||||
alias: '星火 3.5',
|
||||
contextWindow: 8192,
|
||||
maxOutputTokens: 4096,
|
||||
supportsVision: false,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
{
|
||||
id: 'generalv4.0',
|
||||
alias: '星火 4.0',
|
||||
contextWindow: 8192,
|
||||
maxOutputTokens: 4096,
|
||||
supportsVision: false,
|
||||
supportsStreaming: true,
|
||||
},
|
||||
],
|
||||
headers: (apiKey: string) => ({
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
@@ -204,8 +329,23 @@ export default function register(api: PluginAPI) {
|
||||
if (pluginConfig.minimaxBaseUrl) minimax.baseUrl = pluginConfig.minimaxBaseUrl;
|
||||
api.registerProvider(minimax);
|
||||
|
||||
// Register DeepSeek
|
||||
const deepseek = { ...deepseekProvider };
|
||||
if (pluginConfig.deepseekBaseUrl) deepseek.baseUrl = pluginConfig.deepseekBaseUrl;
|
||||
api.registerProvider(deepseek);
|
||||
|
||||
// Register Baidu ERNIE
|
||||
const baidu = { ...baiduProvider };
|
||||
if (pluginConfig.baiduBaseUrl) baidu.baseUrl = pluginConfig.baiduBaseUrl;
|
||||
api.registerProvider(baidu);
|
||||
|
||||
// Register iFlytek Spark
|
||||
const spark = { ...sparkProvider };
|
||||
if (pluginConfig.sparkBaseUrl) spark.baseUrl = pluginConfig.sparkBaseUrl;
|
||||
api.registerProvider(spark);
|
||||
|
||||
// Log registration
|
||||
api.registerHook('gateway:startup', async () => {
|
||||
console.log('[ZCLAW] Chinese model providers registered: zhipu, qwen, kimi, minimax');
|
||||
console.log('[ZCLAW] Chinese model providers registered: zhipu, qwen, kimi, minimax, deepseek, baidu, spark');
|
||||
}, { name: 'zclaw-chinese-models.startup', description: 'Log provider registration on startup' });
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"id": "zclaw-chinese-models",
|
||||
"name": "ZCLAW Chinese Models",
|
||||
"version": "0.1.0",
|
||||
"description": "Chinese AI model providers for ZCLAW: Zhipu GLM, Qwen, Kimi, MiniMax",
|
||||
"version": "0.2.0",
|
||||
"description": "Chinese AI model providers for ZCLAW: Zhipu GLM, Qwen, Kimi, MiniMax, DeepSeek, Baidu ERNIE, iFlytek Spark",
|
||||
"author": "ZCLAW",
|
||||
"entry": "index.ts",
|
||||
"configSchema": {
|
||||
@@ -16,7 +16,13 @@
|
||||
"kimiApiKey": { "type": "string" },
|
||||
"kimiBaseUrl": { "type": "string" },
|
||||
"minimaxApiKey": { "type": "string" },
|
||||
"minimaxBaseUrl": { "type": "string" }
|
||||
"minimaxBaseUrl": { "type": "string" },
|
||||
"deepseekApiKey": { "type": "string" },
|
||||
"deepseekBaseUrl": { "type": "string" },
|
||||
"baiduApiKey": { "type": "string" },
|
||||
"baiduBaseUrl": { "type": "string" },
|
||||
"sparkApiKey": { "type": "string" },
|
||||
"sparkBaseUrl": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"uiHints": {
|
||||
@@ -27,6 +33,12 @@
|
||||
"kimiApiKey": { "label": "Kimi API Key", "sensitive": true },
|
||||
"kimiBaseUrl": { "label": "Kimi Base URL", "placeholder": "https://api.moonshot.cn/v1" },
|
||||
"minimaxApiKey": { "label": "MiniMax API Key", "sensitive": true },
|
||||
"minimaxBaseUrl": { "label": "MiniMax Base URL", "placeholder": "https://api.minimax.chat/v1" }
|
||||
"minimaxBaseUrl": { "label": "MiniMax Base URL", "placeholder": "https://api.minimax.chat/v1" },
|
||||
"deepseekApiKey": { "label": "DeepSeek API Key", "sensitive": true },
|
||||
"deepseekBaseUrl": { "label": "DeepSeek Base URL", "placeholder": "https://api.deepseek.com/v1" },
|
||||
"baiduApiKey": { "label": "百度 API Key", "sensitive": true },
|
||||
"baiduBaseUrl": { "label": "百度 Base URL", "placeholder": "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat" },
|
||||
"sparkApiKey": { "label": "讯飞星火 API Key", "sensitive": true },
|
||||
"sparkBaseUrl": { "label": "讯飞星火 Base URL", "placeholder": "https://spark-api-open.xf-yun.com/v1" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@zclaw/chinese-models",
|
||||
"version": "0.1.0",
|
||||
"description": "Chinese AI model providers for ZCLAW: Zhipu GLM, Qwen, Kimi, MiniMax",
|
||||
"name": "@zclaw/zclaw-chinese-models",
|
||||
"version": "0.2.0",
|
||||
"description": "Chinese AI model providers for ZCLAW: Zhipu GLM, Qwen, Kimi, MiniMax, DeepSeek, Baidu ERNIE, iFlytek Spark",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
@@ -17,7 +17,12 @@
|
||||
"glm",
|
||||
"qwen",
|
||||
"kimi",
|
||||
"minimax"
|
||||
"minimax",
|
||||
"deepseek",
|
||||
"baidu",
|
||||
"ernie",
|
||||
"spark",
|
||||
"iflytek"
|
||||
],
|
||||
"author": "ZCLAW Team",
|
||||
"license": "MIT",
|
||||
@@ -29,6 +34,6 @@
|
||||
"files": [
|
||||
"dist",
|
||||
"index.ts",
|
||||
"plugin.json"
|
||||
"openclaw.plugin.json"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
"verificationToken": { "type": "string" },
|
||||
"encryptKey": { "type": "string" },
|
||||
"webhookUrl": { "type": "string" }
|
||||
},
|
||||
"required": ["appId", "appSecret"]
|
||||
}
|
||||
},
|
||||
"uiHints": {
|
||||
"appId": { "label": "飞书 App ID", "placeholder": "cli_xxxxxxxxxxxx" },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@zclaw/feishu",
|
||||
"name": "@zclaw/zclaw-feishu",
|
||||
"version": "0.1.0",
|
||||
"description": "Feishu (Lark) channel plugin for ZCLAW",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@@ -30,6 +30,13 @@ interface RpcContext {
|
||||
respond(ok: boolean, payload: any): void;
|
||||
}
|
||||
|
||||
interface SkillInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
path: string;
|
||||
source: 'builtin' | 'extra';
|
||||
}
|
||||
|
||||
// Clone (分身) management - stored in ZCLAW config
|
||||
interface CloneConfig {
|
||||
id: string;
|
||||
@@ -38,12 +45,23 @@ interface CloneConfig {
|
||||
nickname?: string;
|
||||
scenarios?: string[];
|
||||
model?: string;
|
||||
workspaceDir?: string;
|
||||
workspaceResolvedPath?: string;
|
||||
restrictFiles?: boolean;
|
||||
privacyOptIn?: boolean;
|
||||
userName?: string;
|
||||
userRole?: string;
|
||||
bootstrapReady?: boolean;
|
||||
bootstrapFiles?: Array<{ name: string; path: string; exists: boolean }>;
|
||||
createdAt: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export default function register(api: PluginAPI) {
|
||||
const configDir = process.env.OPENCLAW_HOME || path.join(process.env.HOME || process.env.USERPROFILE || '', '.openclaw');
|
||||
const zclawDataPath = path.join(configDir, 'zclaw-data.json');
|
||||
const templateRoot = path.resolve(__dirname, '../../config');
|
||||
const bootstrapFiles = ['SOUL.md', 'AGENTS.md', 'IDENTITY.md', 'USER.md'] as const;
|
||||
|
||||
// Helper: read/write ZCLAW data
|
||||
function readZclawData(): { clones: CloneConfig[]; quickConfig?: Record<string, any> } {
|
||||
@@ -60,6 +78,145 @@ export default function register(api: PluginAPI) {
|
||||
fs.writeFileSync(zclawDataPath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
function uniquePaths(paths: string[]) {
|
||||
return Array.from(new Set(paths.filter(Boolean).map((item) => path.resolve(item))));
|
||||
}
|
||||
|
||||
function sanitizePathSegment(value: string) {
|
||||
return value
|
||||
.trim()
|
||||
.replace(/[<>:"/\\|?*\u0000-\u001F]/g, '-')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-|-$/g, '')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
function resolveWorkspacePath(workspaceDir: string | undefined, clone: Pick<CloneConfig, 'id' | 'name'>) {
|
||||
const baseWorkspace =
|
||||
workspaceDir ||
|
||||
api.config?.agents?.defaults?.workspace ||
|
||||
'~/.openclaw/zclaw-workspace';
|
||||
const expandedBase = baseWorkspace.replace('~', process.env.HOME || process.env.USERPROFILE || '');
|
||||
const resolvedBase = path.resolve(expandedBase);
|
||||
const cloneDirName = sanitizePathSegment(clone.name) || clone.id;
|
||||
return path.join(resolvedBase, 'agents', cloneDirName);
|
||||
}
|
||||
|
||||
function readTemplate(fileName: typeof bootstrapFiles[number], fallback: string) {
|
||||
const filePath = path.join(templateRoot, fileName);
|
||||
try {
|
||||
if (fs.existsSync(filePath)) {
|
||||
return fs.readFileSync(filePath, 'utf-8');
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function renderIdentity(clone: CloneConfig) {
|
||||
const nickname = clone.nickname || clone.name;
|
||||
return `# ${clone.name} 身份
|
||||
|
||||
- **名字**: ${clone.name}
|
||||
- **昵称**: ${nickname}
|
||||
- **Emoji**: ${nickname.slice(0, 1) || '🦞'}
|
||||
- **描述**: ${clone.role || 'ZCLAW Agent'}
|
||||
- **模型**: ${clone.model || '继承当前默认模型'}
|
||||
- **创建时间**: ${clone.createdAt}
|
||||
`;
|
||||
}
|
||||
|
||||
function renderSoul(clone: CloneConfig) {
|
||||
const scenarios = clone.scenarios?.length ? clone.scenarios.join('、') : '通用协作';
|
||||
return `# ${clone.name} 人格
|
||||
|
||||
你是 ${clone.name},一个运行在 ZCLAW / OpenClaw 体系中的 Agent。
|
||||
|
||||
## 角色定位
|
||||
|
||||
- **角色**: ${clone.role || '未设置'}
|
||||
- **擅长场景**: ${scenarios}
|
||||
- **工作方式**: 高执行力、中文优先、面向交付
|
||||
|
||||
## 边界
|
||||
|
||||
- 文件访问限制: ${clone.restrictFiles ? '已开启' : '未开启'}
|
||||
- 优化计划: ${clone.privacyOptIn ? '已加入' : '未加入'}
|
||||
- 工作目录: ${clone.workspaceResolvedPath || clone.workspaceDir || '继承系统默认'}
|
||||
`;
|
||||
}
|
||||
|
||||
function renderUser(clone: CloneConfig) {
|
||||
return `# 用户配置
|
||||
|
||||
- **名字**: ${clone.userName || '未设置'}
|
||||
- **角色**: ${clone.userRole || '未设置'}
|
||||
- **语言**: 中文 (zh-CN)
|
||||
- **时区**: Asia/Shanghai (UTC+8)
|
||||
|
||||
## 协作说明
|
||||
|
||||
- 默认按照用户当前任务优先级推进工作
|
||||
- 在风险操作前先确认
|
||||
- 输出以可执行结果为导向
|
||||
`;
|
||||
}
|
||||
|
||||
function renderAgents(clone: CloneConfig) {
|
||||
const scenarios = clone.scenarios?.length ? clone.scenarios.map((item) => `- ${item}`).join('\n') : '- 通用协作';
|
||||
return `# ${clone.name} Agent 指令
|
||||
|
||||
## 当前 Agent
|
||||
|
||||
- **名称**: ${clone.name}
|
||||
- **角色**: ${clone.role || '未设置'}
|
||||
- **昵称**: ${clone.nickname || clone.name}
|
||||
- **工作目录**: ${clone.workspaceResolvedPath || clone.workspaceDir || '继承系统默认'}
|
||||
|
||||
## 专注场景
|
||||
|
||||
${scenarios}
|
||||
|
||||
## 运行要求
|
||||
|
||||
1. 优先完成当前明确目标
|
||||
2. 长任务分阶段汇报
|
||||
3. 风险操作先确认
|
||||
4. 保持产出可复现、可审计
|
||||
`;
|
||||
}
|
||||
|
||||
function ensureCloneWorkspace(clone: CloneConfig) {
|
||||
const workspaceResolvedPath = resolveWorkspacePath(clone.workspaceDir, clone);
|
||||
fs.mkdirSync(workspaceResolvedPath, { recursive: true });
|
||||
|
||||
const contents: Record<typeof bootstrapFiles[number], string> = {
|
||||
'IDENTITY.md': renderIdentity(clone),
|
||||
'SOUL.md': renderSoul(clone),
|
||||
'USER.md': renderUser(clone),
|
||||
'AGENTS.md': renderAgents(clone),
|
||||
};
|
||||
|
||||
const files = bootstrapFiles.map((fileName) => {
|
||||
const filePath = path.join(workspaceResolvedPath, fileName);
|
||||
const content = contents[fileName] || readTemplate(fileName, `# ${fileName}`);
|
||||
fs.writeFileSync(filePath, content, 'utf-8');
|
||||
return {
|
||||
name: fileName,
|
||||
path: filePath,
|
||||
exists: fs.existsSync(filePath),
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
...clone,
|
||||
workspaceResolvedPath,
|
||||
bootstrapReady: files.every((file) => file.exists),
|
||||
bootstrapFiles: files,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
// === Clone Management ===
|
||||
|
||||
api.registerGatewayMethod('zclaw.clones.list', ({ respond }: RpcContext) => {
|
||||
@@ -69,15 +226,21 @@ export default function register(api: PluginAPI) {
|
||||
|
||||
api.registerGatewayMethod('zclaw.clones.create', ({ params, respond }: RpcContext) => {
|
||||
const data = readZclawData();
|
||||
const clone: CloneConfig = {
|
||||
const rawClone: CloneConfig = {
|
||||
id: `clone_${Date.now().toString(36)}`,
|
||||
name: params.name || 'New Clone',
|
||||
role: params.role,
|
||||
nickname: params.nickname,
|
||||
scenarios: params.scenarios || [],
|
||||
model: params.model,
|
||||
workspaceDir: params.workspaceDir,
|
||||
restrictFiles: params.restrictFiles,
|
||||
privacyOptIn: params.privacyOptIn,
|
||||
userName: params.userName,
|
||||
userRole: params.userRole,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
const clone = ensureCloneWorkspace(rawClone);
|
||||
data.clones.push(clone);
|
||||
writeZclawData(data);
|
||||
respond(true, { clone });
|
||||
@@ -90,9 +253,14 @@ export default function register(api: PluginAPI) {
|
||||
respond(false, { error: 'Clone not found' });
|
||||
return;
|
||||
}
|
||||
data.clones[index] = { ...data.clones[index], ...params.updates };
|
||||
const updatedClone = ensureCloneWorkspace({
|
||||
...data.clones[index],
|
||||
...params.updates,
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
data.clones[index] = updatedClone;
|
||||
writeZclawData(data);
|
||||
respond(true, { clone: data.clones[index] });
|
||||
respond(true, { clone: updatedClone });
|
||||
});
|
||||
|
||||
api.registerGatewayMethod('zclaw.clones.delete', ({ params, respond }: RpcContext) => {
|
||||
@@ -191,22 +359,62 @@ export default function register(api: PluginAPI) {
|
||||
respond(true, { quickConfig: data.quickConfig || {} });
|
||||
return;
|
||||
}
|
||||
// Save quick config
|
||||
const { get, ...rest } = params;
|
||||
data.quickConfig = {
|
||||
userName: params.userName,
|
||||
userRole: params.userRole,
|
||||
agentNickname: params.agentNickname,
|
||||
scenarios: params.scenarios || [],
|
||||
workspaceDir: params.workspaceDir,
|
||||
...(data.quickConfig || {}),
|
||||
...rest,
|
||||
scenarios: Array.isArray(rest.scenarios) ? rest.scenarios : (data.quickConfig?.scenarios || []),
|
||||
};
|
||||
writeZclawData(data);
|
||||
respond(true, { ok: true, quickConfig: data.quickConfig });
|
||||
});
|
||||
|
||||
api.registerGatewayMethod('zclaw.skills.list', ({ respond }: RpcContext) => {
|
||||
const data = readZclawData();
|
||||
const builtinRoot = path.resolve(__dirname, '../../skills');
|
||||
const extraRoots = [
|
||||
...(api.config?.skills?.load?.extraDirs || []),
|
||||
...(data.quickConfig?.skillsExtraDirs || []),
|
||||
].map((item: string) => item.replace('~', process.env.HOME || process.env.USERPROFILE || ''));
|
||||
const searchRoots = [
|
||||
{ root: builtinRoot, source: 'builtin' as const },
|
||||
...uniquePaths(extraRoots).map((root) => ({ root, source: 'extra' as const })),
|
||||
];
|
||||
|
||||
const skills: SkillInfo[] = [];
|
||||
|
||||
for (const target of searchRoots) {
|
||||
try {
|
||||
if (!fs.existsSync(target.root)) continue;
|
||||
const entries = fs.readdirSync(target.root, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
const skillPath = path.join(target.root, entry.name, 'SKILL.md');
|
||||
if (!fs.existsSync(skillPath)) continue;
|
||||
skills.push({
|
||||
id: `${target.source}:${entry.name}`,
|
||||
name: entry.name,
|
||||
path: skillPath,
|
||||
source: target.source,
|
||||
});
|
||||
}
|
||||
} catch { /* ignore skill scan failures */ }
|
||||
}
|
||||
|
||||
respond(true, {
|
||||
skills,
|
||||
extraDirs: data.quickConfig?.skillsExtraDirs || api.config?.skills?.load?.extraDirs || [],
|
||||
});
|
||||
});
|
||||
|
||||
// === Workspace Info ===
|
||||
|
||||
api.registerGatewayMethod('zclaw.workspace.info', ({ respond }: RpcContext) => {
|
||||
const workspace = api.config?.agents?.defaults?.workspace || '~/.openclaw/zclaw-workspace';
|
||||
const data = readZclawData();
|
||||
const workspace =
|
||||
data.quickConfig?.workspaceDir ||
|
||||
api.config?.agents?.defaults?.workspace ||
|
||||
'~/.openclaw/zclaw-workspace';
|
||||
const resolvedPath = workspace.replace('~', process.env.HOME || process.env.USERPROFILE || '');
|
||||
|
||||
let exists = false;
|
||||
|
||||
@@ -4,5 +4,10 @@
|
||||
"version": "0.1.0",
|
||||
"description": "Custom Gateway RPC methods for ZCLAW Tauri desktop UI",
|
||||
"author": "ZCLAW",
|
||||
"entry": "index.ts"
|
||||
"entry": "index.ts",
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@zclaw/ui",
|
||||
"name": "@zclaw/zclaw-ui",
|
||||
"version": "0.1.0",
|
||||
"description": "UI extension RPC methods for ZCLAW Tauri desktop app",
|
||||
"main": "dist/index.js",
|
||||
|
||||
Reference in New Issue
Block a user