v1.7.0: Integrate ClawWork Economic AI Agent Platform
This commit is contained in:
508
src/tools/clawwork.ts
Normal file
508
src/tools/clawwork.ts
Normal file
@@ -0,0 +1,508 @@
|
||||
/**
|
||||
* ClawWork Integration for QwenClaw
|
||||
*
|
||||
* Economic accountability layer for AI agents
|
||||
* Integrates with Agents Council and FULL RAG
|
||||
*/
|
||||
|
||||
import { spawn } from "child_process";
|
||||
import { join } from "path";
|
||||
import { existsSync, readFileSync, writeFile } from "fs";
|
||||
|
||||
const CLAWWORK_DIR = process.env.CLAWWORK_DIR || join(process.env.HOME || process.env.USERPROFILE || "", "ClawWork");
|
||||
const CONFIG_FILE = join(process.env.HOME || process.env.USERPROFILE || "", ".clawwork", "config.json");
|
||||
const STATE_FILE = join(process.env.HOME || process.env.USERPROFILE || "", ".clawwork", "state.json");
|
||||
|
||||
export interface ClawWorkConfig {
|
||||
autoStart: boolean;
|
||||
dashboard: boolean;
|
||||
port: number;
|
||||
agent: AgentConfig;
|
||||
tools: ToolsConfig;
|
||||
rag: RAGConfig;
|
||||
}
|
||||
|
||||
export interface AgentConfig {
|
||||
initialBalance: number;
|
||||
survivalMode: 'conservative' | 'balanced' | 'aggressive';
|
||||
autoSubmit: boolean;
|
||||
learningThreshold: number;
|
||||
}
|
||||
|
||||
export interface ToolsConfig {
|
||||
webSearch: {
|
||||
provider: 'tavily' | 'jina';
|
||||
apiKey?: string;
|
||||
};
|
||||
codeExecution: {
|
||||
provider: 'e2b';
|
||||
apiKey?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RAGConfig {
|
||||
enabled: boolean;
|
||||
shareWithCouncil: boolean;
|
||||
vectorStore: 'sqlite' | 'chroma' | 'pinecone';
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: ClawWorkConfig = {
|
||||
autoStart: true,
|
||||
dashboard: true,
|
||||
port: 3000,
|
||||
agent: {
|
||||
initialBalance: 10,
|
||||
survivalMode: 'balanced',
|
||||
autoSubmit: true,
|
||||
learningThreshold: 50,
|
||||
},
|
||||
tools: {
|
||||
webSearch: {
|
||||
provider: 'tavily',
|
||||
},
|
||||
codeExecution: {
|
||||
provider: 'e2b',
|
||||
},
|
||||
},
|
||||
rag: {
|
||||
enabled: true,
|
||||
shareWithCouncil: true,
|
||||
vectorStore: 'sqlite',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* ClawWork Client
|
||||
*/
|
||||
export class ClawWorkClient {
|
||||
private config: ClawWorkConfig;
|
||||
private running: boolean = false;
|
||||
|
||||
constructor(config: Partial<ClawWorkConfig> = {}) {
|
||||
this.config = { ...DEFAULT_CONFIG, ...config };
|
||||
}
|
||||
|
||||
/**
|
||||
* Start ClawWork server
|
||||
*/
|
||||
async start(): Promise<void> {
|
||||
if (this.running) {
|
||||
console.log("[ClawWork] Already running");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("[ClawWork] Starting ClawWork server...");
|
||||
|
||||
const serverProcess = spawn("python", ["-m", "clawwork.server"], {
|
||||
cwd: CLAWWORK_DIR,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
serverProcess.stdout?.on("data", (data) => {
|
||||
console.log(`[ClawWork] ${data.toString().trim()}`);
|
||||
});
|
||||
|
||||
serverProcess.stderr?.on("data", (data) => {
|
||||
console.error(`[ClawWork Error] ${data.toString().trim()}`);
|
||||
});
|
||||
|
||||
this.running = true;
|
||||
|
||||
// Start dashboard if configured
|
||||
if (this.config.dashboard) {
|
||||
this.startDashboard();
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
console.log("[ClawWork] ✅ Started");
|
||||
}
|
||||
|
||||
/**
|
||||
* Start React dashboard
|
||||
*/
|
||||
private startDashboard(): void {
|
||||
console.log("[ClawWork] Starting dashboard...");
|
||||
|
||||
const dashboardProcess = spawn("npm", ["start"], {
|
||||
cwd: join(CLAWWORK_DIR, "frontend"),
|
||||
detached: true,
|
||||
stdio: "ignore",
|
||||
});
|
||||
|
||||
dashboardProcess.unref();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop ClawWork
|
||||
*/
|
||||
async stop(): Promise<void> {
|
||||
this.running = false;
|
||||
console.log("[ClawWork] Stopped");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get agent status
|
||||
*/
|
||||
async getStatus(): Promise<AgentStatus> {
|
||||
if (!existsSync(STATE_FILE)) {
|
||||
return {
|
||||
balance: this.config.agent.initialBalance,
|
||||
tokenCosts: 0,
|
||||
survivalTier: 'starting',
|
||||
tasksCompleted: 0,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const state = JSON.parse(readFileSync(STATE_FILE, "utf-8"));
|
||||
return {
|
||||
balance: state.balance || this.config.agent.initialBalance,
|
||||
tokenCosts: state.tokenCosts || 0,
|
||||
survivalTier: this.calculateSurvivalTier(state.balance),
|
||||
tasksCompleted: state.tasksCompleted || 0,
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
balance: this.config.agent.initialBalance,
|
||||
tokenCosts: 0,
|
||||
survivalTier: 'starting',
|
||||
tasksCompleted: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate survival tier based on balance
|
||||
*/
|
||||
private calculateSurvivalTier(balance: number): SurvivalTier {
|
||||
if (balance > 500) return 'thriving';
|
||||
if (balance > 100) return 'stable';
|
||||
if (balance > 20) return 'surviving';
|
||||
return 'at_risk';
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide activity (work or learn)
|
||||
*/
|
||||
async decideActivity(): Promise<ActivityDecision> {
|
||||
const status = await this.getStatus();
|
||||
|
||||
// Low balance - should learn first
|
||||
if (status.balance < this.config.agent.learningThreshold) {
|
||||
return {
|
||||
activity: 'learn',
|
||||
reason: `Balance ($${status.balance}) below threshold ($${this.config.agent.learningThreshold}). Invest in learning for higher-value tasks.`,
|
||||
};
|
||||
}
|
||||
|
||||
// Good balance - work
|
||||
return {
|
||||
activity: 'work',
|
||||
reason: `Balance ($${status.balance}) sufficient. Ready for income-generating tasks.`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available task
|
||||
*/
|
||||
async getTask(options?: TaskOptions): Promise<Task> {
|
||||
console.log("[ClawWork] Fetching task...");
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const args = ["-m", "clawwork.get_task"];
|
||||
|
||||
if (options?.minDifficulty) {
|
||||
args.push("--difficulty", options.minDifficulty);
|
||||
}
|
||||
|
||||
const proc = spawn("python", args, {
|
||||
cwd: CLAWWORK_DIR,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
let output = "";
|
||||
proc.stdout?.on("data", (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
|
||||
proc.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
try {
|
||||
const task = JSON.parse(output);
|
||||
resolve(task);
|
||||
} catch {
|
||||
reject(new Error("Failed to parse task"));
|
||||
}
|
||||
} else {
|
||||
reject(new Error("Failed to get task"));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Work on task
|
||||
*/
|
||||
async work(task: Task): Promise<WorkResult> {
|
||||
console.log(`[ClawWork] Working on task: ${task.id}`);
|
||||
|
||||
// This would integrate with QwenClaw agents to complete the task
|
||||
return {
|
||||
taskId: task.id,
|
||||
artifact: 'artifact.pdf',
|
||||
description: 'Task completed',
|
||||
qualityEstimate: 0.8,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit completed work
|
||||
*/
|
||||
async submitWork(result: WorkResult): Promise<number> {
|
||||
console.log(`[ClawWork] Submitting work: ${result.taskId}`);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const args = [
|
||||
"-m", "clawwork.submit",
|
||||
"--task", result.taskId,
|
||||
"--artifact", result.artifact,
|
||||
];
|
||||
|
||||
const proc = spawn("python", args, {
|
||||
cwd: CLAWWORK_DIR,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
let output = "";
|
||||
proc.stdout?.on("data", (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
|
||||
proc.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
try {
|
||||
const payment = JSON.parse(output).payment;
|
||||
resolve(payment);
|
||||
} catch {
|
||||
resolve(0);
|
||||
}
|
||||
} else {
|
||||
reject(new Error("Submission failed"));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Learn new skill
|
||||
*/
|
||||
async learn(knowledge: string): Promise<void> {
|
||||
if (knowledge.length < 200) {
|
||||
throw new Error("Knowledge must be at least 200 characters");
|
||||
}
|
||||
|
||||
console.log("[ClawWork] Learning new knowledge...");
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const proc = spawn("python", ["-m", "clawwork.learn"], {
|
||||
cwd: CLAWWORK_DIR,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
proc.stdin?.write(knowledge);
|
||||
proc.stdin?.end();
|
||||
|
||||
proc.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
console.log("[ClawWork] ✅ Learning complete");
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error("Learning failed"));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Search web
|
||||
*/
|
||||
async searchWeb(query: string): Promise<SearchResult[]> {
|
||||
console.log(`[ClawWork] Searching: ${query}`);
|
||||
|
||||
// Would integrate with Tavily or Jina
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create file
|
||||
*/
|
||||
async createFile(file: FileSpec): Promise<string> {
|
||||
console.log(`[ClawWork] Creating file: ${file.path}`);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const proc = spawn("python", ["-m", "clawwork.create_file", file.path], {
|
||||
cwd: CLAWWORK_DIR,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
proc.stdin?.write(JSON.stringify(file.content));
|
||||
proc.stdin?.end();
|
||||
|
||||
proc.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
resolve(file.path);
|
||||
} else {
|
||||
reject(new Error("File creation failed"));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute code
|
||||
*/
|
||||
async executeCode(code: string): Promise<CodeResult> {
|
||||
console.log("[ClawWork] Executing code...");
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const proc = spawn("python", ["-m", "clawwork.execute_code"], {
|
||||
cwd: CLAWWORK_DIR,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
let output = "";
|
||||
proc.stdout?.on("data", (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
|
||||
proc.stdin?.write(code);
|
||||
proc.stdin?.end();
|
||||
|
||||
proc.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
resolve({ output, success: true });
|
||||
} else {
|
||||
resolve({ output, success: false });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interface AgentStatus {
|
||||
balance: number;
|
||||
tokenCosts: number;
|
||||
survivalTier: SurvivalTier;
|
||||
tasksCompleted: number;
|
||||
}
|
||||
|
||||
type SurvivalTier = 'thriving' | 'stable' | 'surviving' | 'at_risk' | 'starting';
|
||||
|
||||
interface ActivityDecision {
|
||||
activity: 'work' | 'learn';
|
||||
reason: string;
|
||||
}
|
||||
|
||||
interface TaskOptions {
|
||||
minDifficulty?: 'easy' | 'medium' | 'hard';
|
||||
}
|
||||
|
||||
interface Task {
|
||||
id: string;
|
||||
sector: string;
|
||||
description: string;
|
||||
estimatedHours: number;
|
||||
difficulty: 'easy' | 'medium' | 'hard';
|
||||
value: number;
|
||||
}
|
||||
|
||||
interface WorkResult {
|
||||
taskId: string;
|
||||
artifact: string;
|
||||
description: string;
|
||||
qualityEstimate: number;
|
||||
}
|
||||
|
||||
interface SearchResult {
|
||||
title: string;
|
||||
url: string;
|
||||
snippet: string;
|
||||
}
|
||||
|
||||
interface FileSpec {
|
||||
type: 'txt' | 'xlsx' | 'docx' | 'pdf';
|
||||
path: string;
|
||||
content: any;
|
||||
}
|
||||
|
||||
interface CodeResult {
|
||||
output: string;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize ClawWork
|
||||
*/
|
||||
export async function initClawWork(): Promise<ClawWorkClient> {
|
||||
const client = new ClawWorkClient();
|
||||
|
||||
if (client['config'].autoStart) {
|
||||
await client.start();
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* CLI command for ClawWork
|
||||
*/
|
||||
export async function clawworkCommand(args: string[]): Promise<void> {
|
||||
console.log("💼 ClawWork - Economic AI Agent Platform\n");
|
||||
|
||||
if (args.length === 0) {
|
||||
console.log("Usage: qwenclaw clawwork <command> [options]");
|
||||
console.log("");
|
||||
console.log("Commands:");
|
||||
console.log(" start - Start ClawWork server");
|
||||
console.log(" dashboard - Open React dashboard");
|
||||
console.log(" status - Check agent balance/status");
|
||||
console.log(" work - Start working on tasks");
|
||||
console.log(" learn <topic> - Learn new skill");
|
||||
console.log(" submit - Submit completed work");
|
||||
console.log("");
|
||||
console.log("Examples:");
|
||||
console.log(" qwenclaw clawwork start");
|
||||
console.log(" qwenclaw clawwork status");
|
||||
console.log(" qwenclaw clawwork learn 'Financial analysis'");
|
||||
return;
|
||||
}
|
||||
|
||||
const command = args[0];
|
||||
const client = new ClawWorkClient();
|
||||
|
||||
switch (command) {
|
||||
case "start":
|
||||
await client.start();
|
||||
break;
|
||||
|
||||
case "status":
|
||||
const status = await client.getStatus();
|
||||
console.log("ClawWork Agent Status:");
|
||||
console.log(` Balance: $${status.balance.toFixed(2)}`);
|
||||
console.log(` Token Costs: $${status.tokenCosts.toFixed(2)}`);
|
||||
console.log(` Survival Tier: ${status.survivalTier}`);
|
||||
console.log(` Tasks Completed: ${status.tasksCompleted}`);
|
||||
break;
|
||||
|
||||
case "learn":
|
||||
if (!args[1]) {
|
||||
console.log("[ERROR] Knowledge topic required");
|
||||
return;
|
||||
}
|
||||
await client.learn(args.slice(1).join(" "));
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log(`[ERROR] Unknown command: ${command}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user