feat(phase2): complete P1 tasks - Channels, Triggers, Skills CRUD and UI enhancements

Phase 2 P1 Tasks Completed:

API Layer (gateway-client.ts, gatewayStore.ts):
- Add Channels CRUD: getChannel, createChannel, updateChannel, deleteChannel
- Add Triggers CRUD: getTrigger, createTrigger, updateTrigger, deleteTrigger
- Add Skills CRUD: getSkill, createSkill, updateSkill, deleteSkill
- Add Scheduled Tasks API: createScheduledTask, deleteScheduledTask, toggleScheduledTask
- Add loadModels action for dynamic model list

UI Components:
- ModelsAPI.tsx: Dynamic model loading from API with loading/error states
- SchedulerPanel.tsx: Full CreateJobModal with cron/interval/once scheduling
- SecurityStatus.tsx: Loading states, error handling, retry functionality
- WorkflowEditor.tsx: New workflow creation/editing modal (new file)
- WorkflowHistory.tsx: Workflow execution history viewer (new file)
- WorkflowList.tsx: Integrated editor and history access

Configuration:
- Add 4 Hands TOML configs: clip, collector, predictor, twitter

Documentation (SYSTEM_ANALYSIS.md):
- Update API coverage: 65% → 89% (53/62 endpoints)
- Update UI completion: 85% → 92%
- Mark Phase 2 P1 tasks as completed
- Update technical debt cleanup status

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-15 01:38:34 +08:00
parent 1f9b6553fc
commit 5599c1a4db
15 changed files with 3216 additions and 296 deletions

View File

@@ -825,6 +825,19 @@ export class GatewayClient {
return response.json();
}
private async restPatch<T>(path: string, body?: unknown): Promise<T> {
const baseUrl = this.getRestBaseUrl();
const response = await fetch(`${baseUrl}${path}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined,
});
if (!response.ok) {
throw new Error(`REST API error: ${response.status} ${response.statusText}`);
}
return response.json();
}
// === ZCLAW / Agent Methods (OpenFang REST API) ===
async listClones(): Promise<any> {
@@ -871,9 +884,54 @@ export class GatewayClient {
async listSkills(): Promise<any> {
return this.restGet('/api/skills');
}
async getSkill(id: string): Promise<any> {
return this.restGet(`/api/skills/${id}`);
}
async createSkill(skill: {
name: string;
description?: string;
triggers: Array<{ type: string; pattern?: string }>;
actions: Array<{ type: string; params?: Record<string, unknown> }>;
enabled?: boolean;
}): Promise<any> {
return this.restPost('/api/skills', skill);
}
async updateSkill(id: string, updates: {
name?: string;
description?: string;
triggers?: Array<{ type: string; pattern?: string }>;
actions?: Array<{ type: string; params?: Record<string, unknown> }>;
enabled?: boolean;
}): Promise<any> {
return this.restPut(`/api/skills/${id}`, updates);
}
async deleteSkill(id: string): Promise<any> {
return this.restDelete(`/api/skills/${id}`);
}
async listChannels(): Promise<any> {
return this.restGet('/api/channels');
}
async getChannel(id: string): Promise<any> {
return this.restGet(`/api/channels/${id}`);
}
async createChannel(channel: {
type: string;
name: string;
config: Record<string, unknown>;
enabled?: boolean;
}): Promise<any> {
return this.restPost('/api/channels', channel);
}
async updateChannel(id: string, updates: {
name?: string;
config?: Record<string, unknown>;
enabled?: boolean;
}): Promise<any> {
return this.restPut(`/api/channels/${id}`, updates);
}
async deleteChannel(id: string): Promise<any> {
return this.restDelete(`/api/channels/${id}`);
}
async getFeishuStatus(): Promise<any> {
return this.restGet('/api/channels/feishu/status');
}
@@ -881,6 +939,31 @@ export class GatewayClient {
return this.restGet('/api/scheduler/tasks');
}
/** Create a scheduled task */
async createScheduledTask(task: {
name: string;
schedule: string;
scheduleType: 'cron' | 'interval' | 'once';
target?: {
type: 'agent' | 'hand' | 'workflow';
id: string;
};
description?: string;
enabled?: boolean;
}): Promise<{ id: string; name: string; schedule: string; status: string }> {
return this.restPost('/api/scheduler/tasks', task);
}
/** Delete a scheduled task */
async deleteScheduledTask(id: string): Promise<void> {
return this.restDelete(`/api/scheduler/tasks/${id}`);
}
/** Toggle a scheduled task (enable/disable) */
async toggleScheduledTask(id: string, enabled: boolean): Promise<{ id: string; enabled: boolean }> {
return this.restPatch(`/api/scheduler/tasks/${id}`, { enabled });
}
// === OpenFang Hands API ===
/** List available Hands */
@@ -948,11 +1031,130 @@ export class GatewayClient {
return this.restGet(`/api/workflows/${workflowId}/runs/${runId}`);
}
/** List workflow execution runs */
async listWorkflowRuns(workflowId: string, opts?: { limit?: number; offset?: number }): Promise<{
runs: Array<{
runId: string;
status: string;
startedAt: string;
completedAt?: string;
step?: string;
result?: unknown;
error?: string;
}>;
}> {
const params = new URLSearchParams();
if (opts?.limit) params.set('limit', String(opts.limit));
if (opts?.offset) params.set('offset', String(opts.offset));
return this.restGet(`/api/workflows/${workflowId}/runs?${params}`);
}
/** Cancel a workflow execution */
async cancelWorkflow(workflowId: string, runId: string): Promise<{ status: string }> {
return this.restPost(`/api/workflows/${workflowId}/runs/${runId}/cancel`, {});
}
/** Create a new workflow */
async createWorkflow(workflow: {
name: string;
description?: string;
steps: Array<{
handName: string;
name?: string;
params?: Record<string, unknown>;
condition?: string;
}>;
}): Promise<{ id: string; name: string }> {
return this.restPost('/api/workflows', workflow);
}
/** Update a workflow */
async updateWorkflow(id: string, updates: {
name?: string;
description?: string;
steps?: Array<{
handName: string;
name?: string;
params?: Record<string, unknown>;
condition?: string;
}>;
}): Promise<{ id: string; name: string }> {
return this.restPut(`/api/workflows/${id}`, updates);
}
/** Delete a workflow */
async deleteWorkflow(id: string): Promise<{ status: string }> {
return this.restDelete(`/api/workflows/${id}`);
}
// === OpenFang Session API ===
/** List all sessions */
async listSessions(opts?: { limit?: number; offset?: number }): Promise<{
sessions: Array<{
id: string;
agent_id: string;
created_at: string;
updated_at?: string;
message_count?: number;
status?: string;
}>;
}> {
const params = new URLSearchParams();
if (opts?.limit) params.set('limit', String(opts.limit));
if (opts?.offset) params.set('offset', String(opts.offset));
return this.restGet(`/api/sessions?${params}`);
}
/** Get session details */
async getSession(sessionId: string): Promise<{
id: string;
agent_id: string;
created_at: string;
updated_at?: string;
message_count?: number;
status?: string;
metadata?: Record<string, unknown>;
}> {
return this.restGet(`/api/sessions/${sessionId}`);
}
/** Create a new session */
async createSession(opts: {
agent_id: string;
metadata?: Record<string, unknown>;
}): Promise<{
id: string;
agent_id: string;
created_at: string;
}> {
return this.restPost('/api/sessions', opts);
}
/** Delete a session */
async deleteSession(sessionId: string): Promise<{ status: string }> {
return this.restDelete(`/api/sessions/${sessionId}`);
}
/** Get session messages */
async getSessionMessages(sessionId: string, opts?: {
limit?: number;
offset?: number;
}): Promise<{
messages: Array<{
id: string;
role: 'user' | 'assistant' | 'system';
content: string;
created_at: string;
tokens?: { input?: number; output?: number };
}>;
}> {
const params = new URLSearchParams();
if (opts?.limit) params.set('limit', String(opts.limit));
if (opts?.offset) params.set('offset', String(opts.offset));
return this.restGet(`/api/sessions/${sessionId}/messages?${params}`);
}
// === OpenFang Triggers API ===
/** List triggers */
@@ -960,6 +1162,45 @@ export class GatewayClient {
return this.restGet('/api/triggers');
}
/** Get trigger details */
async getTrigger(id: string): Promise<{
id: string;
type: string;
name?: string;
enabled: boolean;
config?: Record<string, unknown>;
}> {
return this.restGet(`/api/triggers/${id}`);
}
/** Create a new trigger */
async createTrigger(trigger: {
type: string;
name?: string;
enabled?: boolean;
config?: Record<string, unknown>;
handName?: string;
workflowId?: string;
}): Promise<{ id: string }> {
return this.restPost('/api/triggers', trigger);
}
/** Update a trigger */
async updateTrigger(id: string, updates: {
name?: string;
enabled?: boolean;
config?: Record<string, unknown>;
handName?: string;
workflowId?: string;
}): Promise<{ id: string }> {
return this.restPut(`/api/triggers/${id}`, updates);
}
/** Delete a trigger */
async deleteTrigger(id: string): Promise<{ status: string }> {
return this.restDelete(`/api/triggers/${id}`);
}
// === OpenFang Audit API ===
/** Get audit logs */