cc工作前备份
This commit is contained in:
258
plugins/zclaw-ui/index.ts
Normal file
258
plugins/zclaw-ui/index.ts
Normal 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' });
|
||||
}
|
||||
8
plugins/zclaw-ui/plugin.json
Normal file
8
plugins/zclaw-ui/plugin.json
Normal 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"
|
||||
}
|
||||
Reference in New Issue
Block a user