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:
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user