23 mandateId: string;
24 taskId: string;
25 log: LogFunction;
26 config?: Record<string, any>;
27}
28
29type LogLevel = "DEBUG" | "INFO" | "WARN" | "ERROR" | "SUCCESS";
30type LogFunction = (level: LogLevel, component: string, message: string, details?: any) => void;
31
32interface LogEntry {
62 }
63
64 createLogFunction(mandateId: string, baseComponent?: string): LogFunction {
65 return (level, component, message, details) => {
66 const fullComponent = baseComponent ? `${baseComponent}:${component}` : component;
94
95// --- Tool Definitions & Registry ---
96type ToolType = "http_fetch" | "openai_call" | "string_template" | "custom_js_function";
97
98interface BaseToolConfig {}
108 template: string;
109}
110interface CustomJsFunctionToolConfig extends BaseToolConfig {
111 functionName: string;
112}
113
114type ToolFunction<DynamicParams = any, OutputPayload = any, ToolConfig extends BaseToolConfig = BaseToolConfig> = (
115 params: DynamicParams,
116 staticConfig: ToolConfig,
118) => Promise<AgentOutput<OutputPayload>>;
119
120type CustomJsFunction = (input: AgentInput, context: AgentContext) => Promise<AgentOutput>;
121
122class ToolRegistry {
123 private toolHandlers = new Map<ToolType, ToolFunction<any, any, any>>();
124 private customJsFunctions = new Map<string, CustomJsFunction>();
125
126 registerTool<DynamicParams, OutputPayload, C extends BaseToolConfig>(
127 toolType: ToolType,
128 handlerFn: ToolFunction<DynamicParams, OutputPayload, C>,
129 ): void {
130 if (this.toolHandlers.has(toolType)) console.warn(`[ToolRegistry] Overwriting tool handler for "${toolType}".`);
134 getToolHandler = (toolType: ToolType) => this.toolHandlers.get(toolType);
135
136 registerCustomJsFunction(name: string, fn: CustomJsFunction): void {
137 if (this.customJsFunctions.has(name)) console.warn(`[ToolRegistry] Overwriting custom JS function "${name}".`);
138 this.customJsFunctions.set(name, fn);
139 }
140
141 getCustomJsFunction = (name: string) => this.customJsFunctions.get(name);
142}
143
212 }
213
214 private validateInitialInput(definition: WorkflowDefinition, initialPayload: any, log: LogFunction): string | null {
215 if (!definition.initialInputSchema) return null;
216 log("DEBUG", `Workflow:${definition.id}`, "Validating initial input...");
234 }
235
236 private createTaskLogger(mandateId: string, taskId: string, stepId: string): LogFunction {
237 return (level, component, message, details) =>
238 this.logger.record({
246 }
247
248 private createToolLogger(mandateId: string, taskId: string, toolType: ToolType, stepId: string): LogFunction {
249 return (level, component, message, details) =>
250 this.logger.record({
263 ): Promise<WorkflowResult<FinalPayload>> {
264 const mandateId = `M-${Date.now()}-${definition.id}`;
265 const workflowLog = this.logger.createLogFunction(mandateId, "WorkflowEngine");
266 workflowLog("INFO", `Workflow:${definition.id}`, "Execution starting", { initialPayload });
267
348
349 let output: AgentOutput<any>;
350 if (step.toolType === "custom_js_function") {
351 const cfg = step.toolConfig as CustomJsFunctionToolConfig;
352 if (!cfg?.functionName) throw new Error(`Custom JS func name missing for step "${step.id}"`);
353 const customFn = this.toolRegistry.getCustomJsFunction(cfg.functionName);
354 if (!customFn) throw new Error(`Custom JS func "${cfg.functionName}" not found for step "${step.id}"`);
355 output = await customFn({ mandateId, taskId, payload: dynamicToolParams }, toolContext);
356 } else {
434
435// --- Concrete Tool Handlers ---
436const httpFetchToolHandler: ToolFunction<
437 {
438 url?: string;
530}
531
532const openAiCallToolHandler: ToolFunction<OpenAiDynamicParams, { result: any }, OpenAiCallToolConfig> = async (
533 params,
534 staticConfig,
589};
590
591const stringTemplateToolHandler: ToolFunction<Record<string, any>, { result: string }, StringTemplateToolConfig> =
592 async (params, staticConfig, context) => {
593 const { mandateId, taskId, log } = context;
624 };
625
626// --- Custom JS Functions (Agents) ---
627async function legacySummarizerAgent(
628 input: AgentInput<{ textToSummarize: string }>,
629 context: AgentContext,
653const WEBSITE_VISIT_DELAY_MS = 1000; // Reduced for faster demo, adjust for real use
654
655async function simulate_GoogleSearch(
656 input: AgentInput<{ query: string }>,
657 context: AgentContext,
695}
696
697async function scrape_emails_from_websites(
698 input: AgentInput<{ businesses: { name: string; website: string }[] }>,
699 context: AgentContext,
757}
758
759async function draft_emails_for_leads(
760 input: AgentInput<
761 { leads: { name: string; website: string; email: string }[]; companyType?: string; yourServiceName?: string }
842 {
843 id: "step2b_summarize_legacy",
844 toolType: "custom_js_function",
845 toolConfig: { functionName: "legacySummarizer" } as CustomJsFunctionToolConfig,
846 parameters: { textToSummarize: { source: "initial", field: "userText" } },
847 condition: { source: "initial", field: "useLegacySummarizer" },
877 {
878 id: "step_search",
879 toolType: "custom_js_function",
880 toolConfig: { functionName: "simulate_Google Search" } as CustomJsFunctionToolConfig,
881 parameters: { query: { source: "initial", field: "searchQuery" } },
882 },
883 {
884 id: "step_scrape_emails",
885 toolType: "custom_js_function",
886 toolConfig: { functionName: "scrape_emails_from_websites" } as CustomJsFunctionToolConfig,
887 parameters: { businesses: { source: "step_search", field: "businesses" } },
888 dependencies: ["step_search"],
890 {
891 id: "step_draft_emails",
892 toolType: "custom_js_function",
893 toolConfig: { functionName: "draft_emails_for_leads" } as CustomJsFunctionToolConfig,
894 parameters: {
895 leads: { source: "step_scrape_emails", field: "leads" },
931toolRegistry.registerTool("openai_call", openAiCallToolHandler);
932toolRegistry.registerTool("string_template", stringTemplateToolHandler);
933toolRegistry.registerTool("custom_js_function", async (params, staticConfig: CustomJsFunctionToolConfig, context) => {
934 const fnName = staticConfig.functionName;
935 if (!fnName) throw new Error("Custom JS function name missing in toolConfig.");
936 const customFn = toolRegistry.getCustomJsFunction(fnName);
937 if (!customFn) throw new Error(`Custom JS function "${fnName}" not found.`);
938 return customFn({ mandateId: context.mandateId, taskId: context.taskId, payload: params }, context);
939});
940
941toolRegistry.registerCustomJsFunction("legacySummarizer", legacySummarizerAgent);
942toolRegistry.registerCustomJsFunction("simulate_Google Search", simulate_GoogleSearch);
943toolRegistry.registerCustomJsFunction("scrape_emails_from_websites", scrape_emails_from_websites);
944toolRegistry.registerCustomJsFunction("draft_emails_for_leads", draft_emails_for_leads);
945
946// --- HTML Shells & Client-Side JS ---
986 const REPLAY_DELAY_MS = 100; // Faster replay
987
988 function renderInitialStepStatuses(workflowDef, statusBox) {
989 statusBox.innerHTML = '';
990 const overallDiv = document.createElement('div');
1003 }
1004
1005 function updateStepStatus(workflowDef, stepId, statusClass, statusText, statusBox) {
1006 const targetId = stepId === 'overall' ? 'status-overall' : \`status-\${stepId}\`;
1007 const div = document.getElementById(targetId);
1017 }
1018
1019 function appendLogEntry(logEntry, logBox) {
1020 const entryDiv = document.createElement('div');
1021 entryDiv.className = \`log-entry log-\${logEntry.level}\`;
1029 }
1030
1031 async function replayLogsAndUpdateUI(workflowDef, debugLog, logBox, statusBox) {
1032 logBox.textContent = '';
1033 if (!debugLog || !debugLog.length) { logBox.textContent = 'No execution log.'; return; }
1050`;
1051
1052function generateHtmlShellV3() {
1053 return `<!DOCTYPE html><html><head><title>Superpowered Agent Platform Demo V3</title><style>${COMMON_STYLES}</style></head>
1054<body><h1>Superpowered Agent Platform Demo V3</h1>
1113}
1114
1115function generateLeadGenHtmlShell() {
1116 return `<!DOCTYPE html><html><head><title>Superpowered Agent Platform - Lead Gen Demo</title><style>${COMMON_STYLES}</style></head>
1117<body><h1>Superpowered Agent Platform - Lead Gen Demo</h1>
1138 ]};
1139 ${CLIENT_SIDE_UI_SCRIPT_CONTENT}
1140 function displayLeadResults(payload, resultBoxElem) {
1141 resultBoxElem.innerHTML = '';
1142 if (payload && payload.finalLeads && Array.isArray(payload.finalLeads)) {
1185
1186// --- Val Town Entry Point ---
1187export default async function(req: Request): Promise<Response> {
1188 globalLogger.clear();
1189 const requestStartTime = Date.now();
1191 const pathname = rawPathname.replace(/\/$/, "") || "/"; // Normalize: remove trailing slash, default to '/'
1192
1193 const requestLog = globalLogger.createLogFunction(`REQ-${requestStartTime}`, "RequestHandler");
1194 requestLog("INFO", "RequestReceived", `${req.method} ${pathname} (raw: ${rawPathname})`);
1195