claude-flow-novice
Version:
Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes Local RuVector Accelerator and all CFN skills for complete functionality.
253 lines (252 loc) • 9.53 kB
JavaScript
/**
* TriggerDevClient - Type-safe wrapper for trigger.dev SDK
* Handles CFN Loop orchestration via trigger.dev API
*
* Configuration via environment variables:
* - TRIGGER_API_URL: Base URL for trigger.dev API
* - TRIGGER_API_KEY: Authentication token
* - TRIGGER_ENVIRONMENT_ID: Environment ID for deployments
*/ /**
* Typed error class for trigger.dev operations
*/ export class TriggerDevError extends Error {
code;
statusCode;
details;
constructor(message, code, statusCode, details){
super(message), this.code = code, this.statusCode = statusCode, this.details = details;
this.name = 'TriggerDevError';
}
}
/**
* TriggerDevClient - Type-safe integration with trigger.dev
*
* Provides methods for:
* - Triggering CFN Loop runs
* - Monitoring run status
* - Canceling runs
* - Querying run history
*/ export class TriggerDevClient {
config;
baseUrl;
headers;
constructor(config){
const apiUrl = config?.apiUrl || process.env.TRIGGER_API_URL;
const apiKey = config?.apiKey || process.env.TRIGGER_API_KEY;
const environmentId = config?.environmentId || process.env.TRIGGER_ENVIRONMENT_ID;
if (!apiUrl || !apiKey || !environmentId) {
throw new TriggerDevError('Missing trigger.dev configuration', 'CONFIGURATION_ERROR', undefined, {
apiUrl: Boolean(apiUrl),
apiKey: Boolean(apiKey),
environmentId: Boolean(environmentId)
});
}
this.config = {
apiUrl,
apiKey,
environmentId,
timeoutMs: config?.timeoutMs || 30000,
retryAttempts: config?.retryAttempts || 3
};
this.baseUrl = `${apiUrl}/v1`;
this.headers = {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'X-Environment-Id': environmentId
};
}
/**
* Trigger a CFN Loop run via trigger.dev
*
* TODO: RUNTIME_TEST: Verify webhook signature validation in trigger.dev
* TODO: RUNTIME_TEST: Test retry logic with transient API failures
*
* @param payload CFN Loop trigger payload
* @returns Promise resolving to run ID
* @throws TriggerDevError on API failure
*/ async triggerCFNLoop(payload) {
const endpoint = `${this.baseUrl}/tasks/cfn-loop/runs`;
try {
const response = await this.makeRequest('POST', endpoint, {
payload: {
taskId: payload.taskId,
description: payload.description,
mode: payload.mode,
successCriteria: payload.successCriteria,
context: payload.context || {},
webhookUrl: payload.webhookUrl
}
});
if (!response.id) {
throw new TriggerDevError('Invalid response format from trigger.dev', 'INVALID_RESPONSE', undefined, {
response
});
}
return response.id;
} catch (error) {
if (error instanceof TriggerDevError) {
throw error;
}
throw new TriggerDevError(`Failed to trigger CFN Loop: ${error instanceof Error ? error.message : String(error)}`, 'TRIGGER_FAILED', undefined, {
originalError: error
});
}
}
/**
* Get run status from trigger.dev
*
* TODO: RUNTIME_TEST: Verify polling behavior matches trigger.dev API
*
* @param runId Run ID from trigger.dev
* @returns Promise resolving to run status
* @throws TriggerDevError if run not found
*/ async getRunStatus(runId) {
if (!runId || typeof runId !== 'string') {
throw new TriggerDevError('Invalid run ID', 'INVALID_RUN_ID', undefined, {
runId
});
}
const endpoint = `${this.baseUrl}/runs/${runId}`;
try {
const response = await this.makeRequest('GET', endpoint);
if (!response.status) {
throw new TriggerDevError('Invalid run response', 'INVALID_RESPONSE', undefined, {
response
});
}
return response;
} catch (error) {
if (error instanceof TriggerDevError) {
throw error;
}
throw new TriggerDevError(`Failed to get run status: ${error instanceof Error ? error.message : String(error)}`, 'STATUS_FETCH_FAILED', undefined, {
runId,
originalError: error
});
}
}
/**
* Cancel a trigger.dev run
*
* TODO: RUNTIME_TEST: Verify cancellation propagates to agent processes
*
* @param runId Run ID to cancel
* @throws TriggerDevError on cancellation failure
*/ async cancelRun(runId) {
if (!runId || typeof runId !== 'string') {
throw new TriggerDevError('Invalid run ID', 'INVALID_RUN_ID', undefined, {
runId
});
}
const endpoint = `${this.baseUrl}/runs/${runId}/cancel`;
try {
await this.makeRequest('POST', endpoint);
} catch (error) {
if (error instanceof TriggerDevError) {
throw error;
}
throw new TriggerDevError(`Failed to cancel run: ${error instanceof Error ? error.message : String(error)}`, 'CANCEL_FAILED', undefined, {
runId,
originalError: error
});
}
}
/**
* List runs with optional filtering
*
* TODO: RUNTIME_TEST: Test pagination with large result sets
*
* @param filters Optional filter criteria
* @returns Promise resolving to array of runs
* @throws TriggerDevError on query failure
*/ async listRuns(filters) {
const endpoint = `${this.baseUrl}/runs`;
const params = new URLSearchParams();
if (filters?.taskId) {
params.append('taskId', filters.taskId);
}
if (filters?.status) {
params.append('status', filters.status);
}
if (filters?.agentType) {
params.append('agentType', filters.agentType);
}
if (filters?.limit) {
params.append('limit', String(filters.limit));
}
if (filters?.offset) {
params.append('offset', String(filters.offset));
}
const queryString = params.toString();
const url = queryString ? `${endpoint}?${queryString}` : endpoint;
try {
const response = await this.makeRequest('GET', url);
if (!Array.isArray(response)) {
throw new TriggerDevError('Invalid response format', 'INVALID_RESPONSE', undefined, {
response
});
}
return response;
} catch (error) {
if (error instanceof TriggerDevError) {
throw error;
}
throw new TriggerDevError(`Failed to list runs: ${error instanceof Error ? error.message : String(error)}`, 'LIST_FAILED', undefined, {
filters,
originalError: error
});
}
}
/**
* Internal HTTP request handler with retry logic
*
* @private
* @param method HTTP method
* @param url Full URL
* @param body Optional request body
* @returns Promise resolving to response data
* @throws TriggerDevError on all retries exhausted
*/ async makeRequest(method, url, body) {
let lastError = null;
for(let attempt = 0; attempt <= this.config.retryAttempts; attempt++){
try {
const response = await fetch(url, {
method,
headers: this.headers,
body: body ? JSON.stringify(body) : undefined,
signal: AbortSignal.timeout(this.config.timeoutMs)
});
if (!response.ok) {
const errorBody = await response.text();
throw new TriggerDevError(`HTTP ${response.status}: ${response.statusText}`, 'HTTP_ERROR', response.status, {
url,
method,
body: errorBody
});
}
return await response.json();
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
// Don't retry on validation errors
if (error instanceof TriggerDevError && error.code === 'INVALID_RUN_ID') {
throw error;
}
// Exponential backoff: 100ms * 2^attempt
if (attempt < this.config.retryAttempts) {
const delayMs = 100 * Math.pow(2, attempt);
await new Promise((resolve)=>setTimeout(resolve, delayMs));
}
}
}
throw new TriggerDevError(`Request failed after ${this.config.retryAttempts + 1} attempts`, 'REQUEST_FAILED', undefined, {
lastError: lastError?.message
});
}
}
/**
* Create and export singleton instance
* Uses environment variable configuration
*/ export const createTriggerDevClient = ()=>{
return new TriggerDevClient();
};
export default TriggerDevClient;
//# sourceMappingURL=trigger-dev-client.js.map