cc工作前备份

This commit is contained in:
iven
2026-03-12 00:23:42 +08:00
parent f75a2b798b
commit ef849c62ab
98 changed files with 12110 additions and 568 deletions

View File

@@ -0,0 +1,211 @@
/**
* ZCLAW Chinese Models Plugin
*
* Registers Chinese AI model providers for OpenClaw Gateway:
* - Zhipu GLM (智谱)
* - Qwen (通义千问)
* - Kimi (月之暗面)
* - MiniMax
*
* All providers use OpenAI-compatible API format.
*/
interface PluginAPI {
config: Record<string, any>;
registerProvider(provider: ProviderDefinition): void;
registerHook(event: string, handler: (...args: any[]) => any, meta?: Record<string, any>): void;
}
interface ProviderDefinition {
id: string;
name: string;
baseUrl: string;
apiKeyEnvVar?: string;
models: ProviderModel[];
headers?: (apiKey: string) => Record<string, string>;
}
interface ProviderModel {
id: string;
alias?: string;
contextWindow?: number;
maxOutputTokens?: number;
supportsVision?: boolean;
supportsStreaming?: boolean;
}
// 智谱 GLM Provider
const zhipuProvider: ProviderDefinition = {
id: 'zhipu',
name: 'Zhipu AI (智谱)',
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',
contextWindow: 128000,
maxOutputTokens: 4096,
supportsVision: false,
supportsStreaming: true,
},
{
id: 'glm-4-flash',
alias: 'GLM-4-Flash',
contextWindow: 128000,
maxOutputTokens: 4096,
supportsVision: false,
supportsStreaming: true,
},
],
headers: (apiKey: string) => ({
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
}),
};
// 通义千问 Provider (OpenAI-compatible via DashScope)
const qwenProvider: ProviderDefinition = {
id: 'qwen',
name: 'Qwen (通义千问)',
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',
contextWindow: 32768,
maxOutputTokens: 8192,
supportsVision: false,
supportsStreaming: true,
},
{
id: 'qwen-vl-max',
alias: 'Qwen-VL-Max',
contextWindow: 32768,
maxOutputTokens: 4096,
supportsVision: true,
supportsStreaming: true,
},
],
headers: (apiKey: string) => ({
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
}),
};
// Kimi (月之暗面) Provider
const kimiProvider: ProviderDefinition = {
id: 'kimi',
name: 'Kimi (月之暗面)',
baseUrl: 'https://api.moonshot.cn/v1',
apiKeyEnvVar: 'KIMI_API_KEY',
models: [
{
id: 'kimi-k2.5',
alias: 'Kimi-K2.5',
contextWindow: 131072,
maxOutputTokens: 8192,
supportsVision: false,
supportsStreaming: true,
},
{
id: 'moonshot-v1-128k',
alias: 'Moonshot-128K',
contextWindow: 128000,
maxOutputTokens: 4096,
supportsVision: false,
supportsStreaming: true,
},
],
headers: (apiKey: string) => ({
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
}),
};
// MiniMax Provider
const minimaxProvider: ProviderDefinition = {
id: 'minimax',
name: 'MiniMax',
baseUrl: 'https://api.minimax.chat/v1',
apiKeyEnvVar: 'MINIMAX_API_KEY',
models: [
{
id: 'minimax-m2.5',
alias: 'MiniMax-M2.5',
contextWindow: 245760,
maxOutputTokens: 16384,
supportsVision: false,
supportsStreaming: true,
},
{
id: 'abab6.5s-chat',
alias: 'ABAB-6.5s',
contextWindow: 245760,
maxOutputTokens: 8192,
supportsVision: false,
supportsStreaming: true,
},
],
headers: (apiKey: string) => ({
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
}),
};
/**
* Plugin entry point - registers all Chinese model providers
*/
export default function register(api: PluginAPI) {
const pluginConfig = api.config?.plugins?.entries?.['zclaw-chinese-models']?.config ?? {};
// Register Zhipu GLM
const zhipu = { ...zhipuProvider };
if (pluginConfig.zhipuBaseUrl) zhipu.baseUrl = pluginConfig.zhipuBaseUrl;
api.registerProvider(zhipu);
// Register Qwen
const qwen = { ...qwenProvider };
if (pluginConfig.qwenBaseUrl) qwen.baseUrl = pluginConfig.qwenBaseUrl;
api.registerProvider(qwen);
// Register Kimi
const kimi = { ...kimiProvider };
if (pluginConfig.kimiBaseUrl) kimi.baseUrl = pluginConfig.kimiBaseUrl;
api.registerProvider(kimi);
// Register MiniMax
const minimax = { ...minimaxProvider };
if (pluginConfig.minimaxBaseUrl) minimax.baseUrl = pluginConfig.minimaxBaseUrl;
api.registerProvider(minimax);
// Log registration
api.registerHook('gateway:startup', async () => {
console.log('[ZCLAW] Chinese model providers registered: zhipu, qwen, kimi, minimax');
}, { name: 'zclaw-chinese-models.startup', description: 'Log provider registration on startup' });
}

View File

@@ -0,0 +1,32 @@
{
"id": "zclaw-chinese-models",
"name": "ZCLAW Chinese Models",
"version": "0.1.0",
"description": "Chinese AI model providers for ZCLAW: Zhipu GLM, Qwen, Kimi, MiniMax",
"author": "ZCLAW",
"entry": "index.ts",
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"zhipuApiKey": { "type": "string" },
"zhipuBaseUrl": { "type": "string" },
"qwenApiKey": { "type": "string" },
"qwenBaseUrl": { "type": "string" },
"kimiApiKey": { "type": "string" },
"kimiBaseUrl": { "type": "string" },
"minimaxApiKey": { "type": "string" },
"minimaxBaseUrl": { "type": "string" }
}
},
"uiHints": {
"zhipuApiKey": { "label": "智谱 API Key", "sensitive": true },
"zhipuBaseUrl": { "label": "智谱 Base URL", "placeholder": "https://open.bigmodel.cn/api/paas/v4" },
"qwenApiKey": { "label": "通义千问 API Key", "sensitive": true },
"qwenBaseUrl": { "label": "通义千问 Base URL", "placeholder": "https://dashscope.aliyuncs.com/compatible-mode/v1" },
"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" }
}
}

View File

@@ -0,0 +1,231 @@
/**
* ZCLAW Feishu Channel Plugin
*
* Registers Feishu (飞书/Lark) as a messaging channel for OpenClaw Gateway.
* Supports:
* - Receiving messages via Feishu Event Subscription
* - Sending text/rich messages back to Feishu
* - OAuth token management (tenant_access_token)
* - Webhook-based event handling
*/
interface PluginAPI {
config: Record<string, any>;
registerChannel(opts: { plugin: ChannelPlugin }): void;
registerHook(event: string, handler: (...args: any[]) => any, meta?: Record<string, any>): void;
registerGatewayMethod(method: string, handler: (ctx: any) => void): void;
registerService(id: string, service: BackgroundService): void;
}
interface BackgroundService {
start(): Promise<void>;
stop(): Promise<void>;
}
interface ChannelPlugin {
id: string;
meta: {
id: string;
label: string;
selectionLabel: string;
docsPath?: string;
blurb: string;
aliases: string[];
detailLabel?: string;
};
capabilities: {
chatTypes: string[];
};
config: {
listAccountIds: (cfg: any) => string[];
resolveAccount: (cfg: any, accountId?: string) => any;
};
outbound: {
deliveryMode: string;
sendText: (opts: SendTextOpts) => Promise<{ ok: boolean; error?: string }>;
sendRichText?: (opts: SendRichTextOpts) => Promise<{ ok: boolean; error?: string }>;
};
gateway?: {
start?: (ctx: any) => Promise<void>;
stop?: (ctx: any) => Promise<void>;
};
}
interface SendTextOpts {
text: string;
chatId: string;
accountId?: string;
}
interface SendRichTextOpts {
content: any;
chatId: string;
accountId?: string;
}
// Feishu API helpers
class FeishuClient {
private baseUrl = 'https://open.feishu.cn/open-apis';
private appId: string;
private appSecret: string;
private tenantToken: string | null = null;
private tokenExpiry: number = 0;
constructor(appId: string, appSecret: string) {
this.appId = appId;
this.appSecret = appSecret;
}
async getTenantToken(): Promise<string> {
if (this.tenantToken && Date.now() < this.tokenExpiry) {
return this.tenantToken;
}
const response = await fetch(`${this.baseUrl}/auth/v3/tenant_access_token/internal`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
app_id: this.appId,
app_secret: this.appSecret,
}),
});
const data: any = await response.json();
if (data.code !== 0) {
throw new Error(`Feishu auth failed: ${data.msg}`);
}
this.tenantToken = data.tenant_access_token;
// Token valid for 2 hours, refresh at 1.5h
this.tokenExpiry = Date.now() + 90 * 60 * 1000;
return this.tenantToken!;
}
async sendMessage(chatId: string, msgType: string, content: any): Promise<{ ok: boolean; error?: string }> {
try {
const token = await this.getTenantToken();
const response = await fetch(`${this.baseUrl}/im/v1/messages?receive_id_type=chat_id`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
receive_id: chatId,
msg_type: msgType,
content: JSON.stringify(content),
}),
});
const data: any = await response.json();
if (data.code !== 0) {
return { ok: false, error: data.msg };
}
return { ok: true };
} catch (error: any) {
return { ok: false, error: error.message };
}
}
async sendText(chatId: string, text: string): Promise<{ ok: boolean; error?: string }> {
return this.sendMessage(chatId, 'text', { text });
}
async sendRichText(chatId: string, content: any): Promise<{ ok: boolean; error?: string }> {
return this.sendMessage(chatId, 'post', content);
}
}
// Global client instance per account
const clients = new Map<string, FeishuClient>();
function getClient(account: { appId: string; appSecret: string }, accountId: string): FeishuClient {
if (!clients.has(accountId)) {
clients.set(accountId, new FeishuClient(account.appId, account.appSecret));
}
return clients.get(accountId)!;
}
// Channel definition
const feishuChannel: ChannelPlugin = {
id: 'feishu',
meta: {
id: 'feishu',
label: '飞书 (Feishu)',
selectionLabel: 'Feishu / Lark',
blurb: 'Connect to Feishu (飞书) for messaging via Feishu Bot API.',
aliases: ['lark', 'feishu'],
detailLabel: '飞书机器人',
},
capabilities: {
chatTypes: ['direct', 'group'],
},
config: {
listAccountIds: (cfg: any) =>
Object.keys(cfg.channels?.feishu?.accounts ?? {}),
resolveAccount: (cfg: any, accountId?: string) =>
cfg.channels?.feishu?.accounts?.[accountId ?? 'default'] ?? { accountId },
},
outbound: {
deliveryMode: 'direct',
sendText: async ({ text, chatId, accountId }: SendTextOpts) => {
const cfg = (globalThis as any).__openclaw_config;
const account = feishuChannel.config.resolveAccount(cfg, accountId);
if (!account?.appId || !account?.appSecret) {
return { ok: false, error: 'Feishu account not configured (missing appId/appSecret)' };
}
const client = getClient(account, accountId ?? 'default');
return client.sendText(chatId, text);
},
sendRichText: async ({ content, chatId, accountId }: SendRichTextOpts) => {
const cfg = (globalThis as any).__openclaw_config;
const account = feishuChannel.config.resolveAccount(cfg, accountId);
if (!account?.appId || !account?.appSecret) {
return { ok: false, error: 'Feishu account not configured' };
}
const client = getClient(account, accountId ?? 'default');
return client.sendRichText(chatId, content);
},
},
};
/**
* Plugin entry point
*/
export default function register(api: PluginAPI) {
// Register Feishu as a channel
api.registerChannel({ plugin: feishuChannel });
// Register custom RPC method for Feishu status
api.registerGatewayMethod('feishu.status', ({ respond }: any) => {
const accountIds = feishuChannel.config.listAccountIds(api.config);
respond(true, {
channel: 'feishu',
accounts: accountIds.length,
configured: accountIds.length > 0,
});
});
// Startup hook
api.registerHook('gateway:startup', async () => {
const accountIds = feishuChannel.config.listAccountIds(api.config);
if (accountIds.length > 0) {
console.log(`[ZCLAW] Feishu channel registered with ${accountIds.length} account(s)`);
// Pre-warm token for each account
for (const id of accountIds) {
try {
const account = feishuChannel.config.resolveAccount(api.config, id);
if (account?.appId && account?.appSecret && account?.enabled !== false) {
const client = getClient(account, id);
await client.getTenantToken();
console.log(`[ZCLAW] Feishu account "${id}" token acquired`);
}
} catch (err: any) {
console.warn(`[ZCLAW] Feishu account "${id}" token failed: ${err.message}`);
}
}
} else {
console.log('[ZCLAW] Feishu channel registered (no accounts configured yet)');
}
}, { name: 'zclaw-feishu.startup', description: 'Initialize Feishu connections on startup' });
}

View File

@@ -0,0 +1,27 @@
{
"id": "zclaw-feishu",
"name": "ZCLAW Feishu Channel",
"version": "0.1.0",
"description": "Feishu (飞书/Lark) messaging channel plugin for OpenClaw",
"author": "ZCLAW",
"entry": "index.ts",
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"appId": { "type": "string" },
"appSecret": { "type": "string" },
"verificationToken": { "type": "string" },
"encryptKey": { "type": "string" },
"webhookUrl": { "type": "string" }
},
"required": ["appId", "appSecret"]
},
"uiHints": {
"appId": { "label": "飞书 App ID", "placeholder": "cli_xxxxxxxxxxxx" },
"appSecret": { "label": "飞书 App Secret", "sensitive": true },
"verificationToken": { "label": "Verification Token", "sensitive": true },
"encryptKey": { "label": "Encrypt Key", "sensitive": true },
"webhookUrl": { "label": "Webhook URL (可选)", "placeholder": "https://your-server/feishu/webhook" }
}
}

258
plugins/zclaw-ui/index.ts Normal file
View File

@@ -0,0 +1,258 @@
/**
* ZCLAW UI Extensions Plugin
*
* Registers custom Gateway RPC methods that the ZCLAW Tauri desktop UI
* calls for features beyond standard OpenClaw protocol.
*
* Custom methods:
* - zclaw.clones.list → list all agent clones (分身)
* - zclaw.clones.create → create a new clone
* - zclaw.clones.update → update clone config
* - zclaw.clones.delete → delete a clone
* - zclaw.stats.usage → token usage statistics
* - zclaw.stats.sessions → session statistics
* - zclaw.config.quick → quick configuration (name/role/scenarios)
* - zclaw.workspace.info → workspace information
* - zclaw.plugins.status → all ZCLAW plugin statuses
*/
import * as fs from 'fs';
import * as path from 'path';
interface PluginAPI {
config: Record<string, any>;
registerGatewayMethod(method: string, handler: (ctx: RpcContext) => void): void;
registerHook(event: string, handler: (...args: any[]) => any, meta?: Record<string, any>): void;
}
interface RpcContext {
params: Record<string, any>;
respond(ok: boolean, payload: any): void;
}
// Clone (分身) management - stored in ZCLAW config
interface CloneConfig {
id: string;
name: string;
role?: string;
nickname?: string;
scenarios?: string[];
model?: string;
createdAt: 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');
// Helper: read/write ZCLAW data
function readZclawData(): { clones: CloneConfig[]; quickConfig?: Record<string, any> } {
try {
if (fs.existsSync(zclawDataPath)) {
return JSON.parse(fs.readFileSync(zclawDataPath, 'utf-8'));
}
} catch { /* ignore */ }
return { clones: [] };
}
function writeZclawData(data: any) {
fs.mkdirSync(path.dirname(zclawDataPath), { recursive: true });
fs.writeFileSync(zclawDataPath, JSON.stringify(data, null, 2), 'utf-8');
}
// === Clone Management ===
api.registerGatewayMethod('zclaw.clones.list', ({ respond }: RpcContext) => {
const data = readZclawData();
respond(true, { clones: data.clones });
});
api.registerGatewayMethod('zclaw.clones.create', ({ params, respond }: RpcContext) => {
const data = readZclawData();
const clone: CloneConfig = {
id: `clone_${Date.now().toString(36)}`,
name: params.name || 'New Clone',
role: params.role,
nickname: params.nickname,
scenarios: params.scenarios || [],
model: params.model,
createdAt: new Date().toISOString(),
};
data.clones.push(clone);
writeZclawData(data);
respond(true, { clone });
});
api.registerGatewayMethod('zclaw.clones.update', ({ params, respond }: RpcContext) => {
const data = readZclawData();
const index = data.clones.findIndex((c: CloneConfig) => c.id === params.id);
if (index === -1) {
respond(false, { error: 'Clone not found' });
return;
}
data.clones[index] = { ...data.clones[index], ...params.updates };
writeZclawData(data);
respond(true, { clone: data.clones[index] });
});
api.registerGatewayMethod('zclaw.clones.delete', ({ params, respond }: RpcContext) => {
const data = readZclawData();
data.clones = data.clones.filter((c: CloneConfig) => c.id !== params.id);
writeZclawData(data);
respond(true, { ok: true });
});
// === Statistics ===
api.registerGatewayMethod('zclaw.stats.usage', ({ respond }: RpcContext) => {
// Read session files to compute token usage
const sessionsDir = path.join(configDir, 'agents');
const stats = {
totalSessions: 0,
totalMessages: 0,
totalTokens: 0,
byModel: {} as Record<string, { messages: number; inputTokens: number; outputTokens: number }>,
};
try {
if (fs.existsSync(sessionsDir)) {
const agentDirs = fs.readdirSync(sessionsDir);
for (const agentDir of agentDirs) {
const sessDir = path.join(sessionsDir, agentDir, 'sessions');
if (!fs.existsSync(sessDir)) continue;
const sessionFiles = fs.readdirSync(sessDir).filter((f: string) => f.endsWith('.jsonl'));
stats.totalSessions += sessionFiles.length;
for (const file of sessionFiles) {
try {
const content = fs.readFileSync(path.join(sessDir, file), 'utf-8');
const lines = content.split('\n').filter(Boolean);
for (const line of lines) {
try {
const entry = JSON.parse(line);
if (entry.role === 'assistant' || entry.role === 'user') {
stats.totalMessages++;
}
if (entry.usage) {
const model = entry.model || 'unknown';
if (!stats.byModel[model]) {
stats.byModel[model] = { messages: 0, inputTokens: 0, outputTokens: 0 };
}
stats.byModel[model].messages++;
stats.byModel[model].inputTokens += entry.usage.input_tokens || 0;
stats.byModel[model].outputTokens += entry.usage.output_tokens || 0;
stats.totalTokens += (entry.usage.input_tokens || 0) + (entry.usage.output_tokens || 0);
}
} catch { /* skip malformed lines */ }
}
} catch { /* skip unreadable files */ }
}
}
}
} catch { /* sessions dir may not exist yet */ }
respond(true, stats);
});
api.registerGatewayMethod('zclaw.stats.sessions', ({ respond }: RpcContext) => {
const sessionsDir = path.join(configDir, 'agents');
const sessions: any[] = [];
try {
if (fs.existsSync(sessionsDir)) {
const agentDirs = fs.readdirSync(sessionsDir);
for (const agentDir of agentDirs) {
const sessDir = path.join(sessionsDir, agentDir, 'sessions');
if (!fs.existsSync(sessDir)) continue;
const sessionFiles = fs.readdirSync(sessDir).filter((f: string) => f.endsWith('.jsonl'));
for (const file of sessionFiles) {
const stat = fs.statSync(path.join(sessDir, file));
sessions.push({
id: file.replace('.jsonl', ''),
agentId: agentDir,
createdAt: stat.birthtime.toISOString(),
updatedAt: stat.mtime.toISOString(),
size: stat.size,
});
}
}
}
} catch { /* ignore */ }
sessions.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
respond(true, { sessions });
});
// === Quick Configuration ===
api.registerGatewayMethod('zclaw.config.quick', ({ params, respond }: RpcContext) => {
const data = readZclawData();
if (params.get) {
respond(true, { quickConfig: data.quickConfig || {} });
return;
}
// Save quick config
data.quickConfig = {
userName: params.userName,
userRole: params.userRole,
agentNickname: params.agentNickname,
scenarios: params.scenarios || [],
workspaceDir: params.workspaceDir,
};
writeZclawData(data);
respond(true, { ok: true, quickConfig: data.quickConfig });
});
// === Workspace Info ===
api.registerGatewayMethod('zclaw.workspace.info', ({ respond }: RpcContext) => {
const workspace = api.config?.agents?.defaults?.workspace || '~/.openclaw/zclaw-workspace';
const resolvedPath = workspace.replace('~', process.env.HOME || process.env.USERPROFILE || '');
let exists = false;
let fileCount = 0;
let totalSize = 0;
try {
exists = fs.existsSync(resolvedPath);
if (exists) {
const files = fs.readdirSync(resolvedPath, { recursive: true }) as string[];
for (const file of files) {
const fullPath = path.join(resolvedPath, file);
try {
const stat = fs.statSync(fullPath);
if (stat.isFile()) {
fileCount++;
totalSize += stat.size;
}
} catch { /* skip */ }
}
}
} catch { /* ignore */ }
respond(true, {
path: workspace,
resolvedPath,
exists,
fileCount,
totalSize,
});
});
// === Plugin Status ===
api.registerGatewayMethod('zclaw.plugins.status', ({ respond }: RpcContext) => {
respond(true, {
plugins: [
{ id: 'zclaw-chinese-models', status: 'active', version: '0.1.0' },
{ id: 'zclaw-feishu', status: api.config?.channels?.feishu?.enabled ? 'active' : 'inactive', version: '0.1.0' },
{ id: 'zclaw-ui', status: 'active', version: '0.1.0' },
],
});
});
// Startup log
api.registerHook('gateway:startup', async () => {
console.log('[ZCLAW] UI extension RPC methods registered');
}, { name: 'zclaw-ui.startup', description: 'Log ZCLAW UI extensions registration' });
}

View File

@@ -0,0 +1,8 @@
{
"id": "zclaw-ui",
"name": "ZCLAW UI Extensions",
"version": "0.1.0",
"description": "Custom Gateway RPC methods for ZCLAW Tauri desktop UI",
"author": "ZCLAW",
"entry": "index.ts"
}