mirror of
https://github.com/SHOGGOTH-SECTOR/sica-fondt.git
synced 2026-08-01 08:30:20 +00:00
Place useful parts of the surrounding repos into sica-fondt by layer, per the
body model (Ada = membrane; brain/endocrine/capabilities/knowledge non-Ada):
- brain/ LLM reasoning + providers (dapr, hermes, MoMoA)
- capabilities/ REPRAG sidecars: hermes tools/skills, dapr tools, parallel
dispatch, A51 channels, and the OSINT cluster
- knowledge/ LORAG corpus: 754 cyber-skills, agency personas, secure-coding,
MITRE ATT&CK data
- reference/ defensive threat-reference (C3, shhbruh doc) + AdaYaml parser
License handling: AGPL sources (worldosint, advanced_evolution, mercury,
Reticulum) and GPL DeTTECT are SPEC-only clean-room/port descriptions — no
copyleft code copied. MIT/Apache/data parts copied as working trees.
Safety: shhbruh escape/persistence material and C3 covert-C2 kept as reference
only, not wired into the running organism. See CONSOLIDATION.md.
https://claude.ai/code/session_01UehUqEXXJJCsHoA4voCU5c
315 lines
12 KiB
TypeScript
315 lines
12 KiB
TypeScript
/**
|
|
* Copyright 2026 Reto Meier
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import { MultiAgentTool } from '../multiAgentTool.js';
|
|
import {
|
|
DesignResponseItem,
|
|
ExtractedData,
|
|
MultiAgentToolContext,
|
|
MultiAgentToolResult,
|
|
ToolParsingResult } from '../../momoa_core/types.js';
|
|
import { DEFAULT_GEMINI_FLASH_MODEL } from '../../config/models.js';
|
|
import { getToolPreamblePrompt, replaceRuntimePlaceholders } from '../../services/promptManager.js';
|
|
import { removeBacktickFences } from '../../utils/markdownUtils.js';
|
|
import { StitchToolClient, StitchError } from "@google/stitch-sdk";
|
|
import { TranscriptManager } from '../../services/transcriptManager.js';
|
|
|
|
/**
|
|
* Implements the Stitch Tool (UI Designer) using the @google/stitch-sdk.
|
|
*/
|
|
export const stitchTool: MultiAgentTool = {
|
|
displayName: "Stitch UI Designer",
|
|
name: 'STITCH',
|
|
endToken: '}STITCH',
|
|
|
|
async execute(params: Record<string, string>, context: MultiAgentToolContext): Promise<MultiAgentToolResult> {
|
|
const updateProgress = async (message: string, tellOverseer: boolean = false) => {
|
|
context.sendMessage({
|
|
type: 'PROGRESS_UPDATE',
|
|
message: message,
|
|
});
|
|
if (tellOverseer)
|
|
context.overseer?.addLog(message);
|
|
};
|
|
|
|
const startTime = Date.now();
|
|
const TEN_MINUTES_MS = 10 * 60 * 1000;
|
|
const question = params.question;
|
|
const deviceType = params.deviceType;
|
|
|
|
if (!context.secrets.stitchApiKey)
|
|
return { result: "No Stitch API key was provided."}
|
|
|
|
try {
|
|
const client = new StitchToolClient({
|
|
apiKey: `${context.secrets.stitchApiKey}`
|
|
});
|
|
|
|
// Create a new Stitch Project
|
|
updateProgress(`Creating a new Stitch project targeting ${deviceType}.`);
|
|
const createResult = await client.callTool<any>("create_project", {
|
|
title: context.sessionTitle ?? 'Untitled MoMoA Project',
|
|
});
|
|
|
|
// Extract Project ID
|
|
let projectId: string | undefined;
|
|
if (createResult && typeof createResult === 'object') {
|
|
projectId = createResult.id || createResult.projectId || createResult.name?.split('/').pop();
|
|
}
|
|
|
|
if (!projectId) {
|
|
throw new Error("Failed to retrieve a valid Project ID from Stitch.");
|
|
}
|
|
|
|
const projectUrl = `https://stitch.withgoogle.com/projects/${projectId}`;
|
|
updateProgress(`New Stitch project created at: [${projectUrl}](${projectUrl})`);
|
|
|
|
let uiDesignPrompt = await getToolPreamblePrompt('ui-design-preamble');
|
|
uiDesignPrompt = await replaceRuntimePlaceholders(
|
|
uiDesignPrompt,
|
|
{
|
|
ProblemSummary: question,
|
|
});
|
|
|
|
let currentPrompt = uiDesignPrompt;
|
|
let totalAttempts = 0;
|
|
let retryDelay = 2000;
|
|
|
|
// --- Setup Q&A Transcript for Clarifications ---
|
|
const stitchQnATranscript = new TranscriptManager({
|
|
context: context.infrastructureContext
|
|
});
|
|
const chatHistoryString = context.transcriptForContext.getTranscriptAsString(true, context.experts) || "--No Chat History Available--";
|
|
const initialPreamble = `You are an expert UI/UX designer helping an AI design tool named Stitch.
|
|
Stitch is generating a UI screen and may ask clarifying questions.
|
|
Your responsibility is to provide clear, direct, and helpful responses based on the full context of the provided Project Conversation History.
|
|
Do not be conversational; provide only the necessary information to answer the questions. Answer the question as "full sentence answers" that incorporate the question into the response to ensure clarity.
|
|
|
|
You **MUST NOT** attempt to invoke tools. You **must** provide your response to Stitch in a single response.
|
|
|
|
---PROJECT CONVERSATION HISTORY---
|
|
${chatHistoryString}
|
|
------------------------------------
|
|
|
|
You will now receive questions from Stitch.`;
|
|
|
|
stitchQnATranscript.addEntry('user', initialPreamble);
|
|
|
|
// Loop with Backoff and Timeout
|
|
while (true) {
|
|
const elapsed = Date.now() - startTime;
|
|
if (elapsed > TEN_MINUTES_MS) {
|
|
throw new Error("Stitch UI generation timed out after 10 minutes of attempts.");
|
|
}
|
|
|
|
totalAttempts++;
|
|
|
|
try {
|
|
const rawResponse = await client.callTool<any>('generate_screen_from_text', {
|
|
projectId: projectId,
|
|
prompt: currentPrompt,
|
|
deviceType: deviceType
|
|
});
|
|
|
|
const stitchDesignResult = parseStichDesign(rawResponse.outputComponents);
|
|
const stitchMessage: string = stitchDesignResult.combinedText;
|
|
updateProgress(`Final message from Stitch:\n${stitchMessage}`);
|
|
|
|
if (stitchDesignResult.hasPreviewUrls) {
|
|
updateProgress(`Downloading ${stitchDesignResult.previewUrls.length + stitchDesignResult.htmlUrls.length} design assets.`);
|
|
|
|
let successMessage = `The preview images and HTML for this design have been saved to:`;
|
|
|
|
let downloadedCount = 0;
|
|
for (const imageUrl of stitchDesignResult.previewUrls) {
|
|
// Download Image
|
|
const imageResponse = await fetch(imageUrl);
|
|
if (!imageResponse.ok) continue;
|
|
const base64Image = Buffer.from(await imageResponse.arrayBuffer()).toString('base64');
|
|
downloadedCount++;
|
|
|
|
const timestamp = Date.now();
|
|
const imgFilename = `stitch_images/ui_${timestamp}.png`;
|
|
|
|
context.binaryFileMap.set(imgFilename, base64Image);
|
|
context.editedFilesSet.add(imgFilename);
|
|
|
|
successMessage += `\n* ${imgFilename}`;
|
|
}
|
|
|
|
for (const htmlUrl of stitchDesignResult.htmlUrls) {
|
|
// Download HTML
|
|
const htmlResponse = await fetch(htmlUrl);
|
|
if (!htmlResponse.ok) throw new Error(`Failed HTML download: ${htmlResponse.status}`);
|
|
const htmlContent = await htmlResponse.text();
|
|
downloadedCount++;
|
|
|
|
const timestamp = Date.now();
|
|
const htmlFilename = `stitch_code/ui_${timestamp}.html`;
|
|
|
|
context.fileMap.set(htmlFilename, htmlContent);
|
|
context.editedFilesSet.add(htmlFilename);
|
|
successMessage += `\n* ${htmlFilename}`;
|
|
}
|
|
|
|
if (stitchDesignResult.designMarkdown) {
|
|
const timestamp = Date.now();
|
|
const designFilename = `stitch_design/design_${timestamp}.md`;
|
|
|
|
context.fileMap.set(designFilename, stitchDesignResult.designMarkdown);
|
|
context.editedFilesSet.add(designFilename);
|
|
successMessage += `\n* ${designFilename}`;
|
|
}
|
|
|
|
if (downloadedCount > 0)
|
|
successMessage += "\nUse the File Reader tool to view the contents of these files and images.";
|
|
else
|
|
successMessage = "I was unable to save preview image or HTML for this design."
|
|
|
|
updateProgress(successMessage);
|
|
|
|
if (stitchMessage)
|
|
successMessage = `${stitchMessage}\n\n${successMessage}`;
|
|
|
|
return { result: successMessage };
|
|
}
|
|
|
|
if (stitchMessage) {
|
|
const combinedText = stitchMessage;
|
|
|
|
if (combinedText.trim() !== "") {
|
|
updateProgress(`Stitch is asking for clarification:\n${combinedText}`);
|
|
|
|
stitchQnATranscript.addEntry('user', `Message from Stitch:\n"${combinedText}"`);
|
|
|
|
const llmResponse = await context.multiAgentGeminiClient.sendTranscriptMessage(
|
|
stitchQnATranscript,
|
|
{ model: DEFAULT_GEMINI_FLASH_MODEL }
|
|
);
|
|
|
|
const llmResponseText = llmResponse?.text || "Proceed with standard UI/UX best practices.";
|
|
stitchQnATranscript.addEntry('model', llmResponseText);
|
|
|
|
const answer = removeBacktickFences(llmResponseText);
|
|
updateProgress(`I told Stitch:\n${answer}`);
|
|
|
|
currentPrompt = answer;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
throw new Error("Stitch returned no screens and no clarifying questions.");
|
|
|
|
} catch (innerError: any) {
|
|
// Check for recoverable errors via the SDK
|
|
const isRecoverable = innerError instanceof StitchError && innerError.recoverable;
|
|
const remainingTime = TEN_MINUTES_MS - (Date.now() - startTime);
|
|
|
|
if (isRecoverable && remainingTime > retryDelay) {
|
|
updateProgress(`Recoverable error [${innerError.code}]. Retrying in ${retryDelay / 1000}s...`);
|
|
await new Promise(res => setTimeout(res, retryDelay));
|
|
retryDelay = Math.min(retryDelay * 2, 30000);
|
|
continue;
|
|
}
|
|
throw innerError;
|
|
}
|
|
}
|
|
} catch (error: any) {
|
|
let errorMessage = error.message;
|
|
if (error instanceof StitchError) {
|
|
errorMessage = `[${error.code}] ${error.message}`;
|
|
}
|
|
|
|
const errResult = `An error occurred while asking for a UI Design: ${errorMessage}`;
|
|
updateProgress(errResult);
|
|
return { result: errResult };
|
|
}
|
|
},
|
|
|
|
async extractParameters(invocation: string, _context: MultiAgentToolContext): Promise<ToolParsingResult> {
|
|
const trimmed = invocation.trim();
|
|
if (!trimmed) {
|
|
return { success: false, error: "No prompt provided." };
|
|
}
|
|
|
|
let question = trimmed;
|
|
let deviceType = 'DESKTOP'; // Default fallback
|
|
|
|
// 1. Extract the deviceType if it's placed at the end after a comma.
|
|
const match = trimmed.match(/^(.*),\s*(MOBILE|DESKTOP|TABLET|AGNOSTIC)\s*$/i);
|
|
|
|
if (match) {
|
|
question = match[1].trim();
|
|
deviceType = match[2].toUpperCase();
|
|
}
|
|
|
|
// 2. Strip surrounding quotes from the question if they exist
|
|
if (
|
|
(question.startsWith('"') && question.endsWith('"')) ||
|
|
(question.startsWith("'") && question.endsWith("'"))
|
|
) {
|
|
question = question.slice(1, -1).trim();
|
|
}
|
|
|
|
return question
|
|
? { success: true, params: { question, deviceType } }
|
|
: { success: false, error: "No prompt provided after parsing." };
|
|
}
|
|
};
|
|
|
|
function parseStichDesign(payload: DesignResponseItem[]): ExtractedData {
|
|
const htmlUrls: string[] = [];
|
|
const previewUrls: string[] = [];
|
|
const textElements: string[] = [];
|
|
let designMarkdown = "";
|
|
let hasPreviewUrls = false;
|
|
|
|
for (const item of payload) {
|
|
// 1. Extract Design Markdown
|
|
if (item.designSystem?.designSystem?.theme?.designMd) {
|
|
designMarkdown = item.designSystem.designSystem.theme.designMd;
|
|
}
|
|
|
|
// 2. Extract URLs from Screens
|
|
if (item.design?.screens) {
|
|
for (const screen of item.design.screens) {
|
|
if (screen.htmlCode?.downloadUrl) {
|
|
htmlUrls.push(screen.htmlCode.downloadUrl);
|
|
}
|
|
if (screen.screenshot?.downloadUrl) {
|
|
previewUrls.push(screen.screenshot.downloadUrl);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. Extract Text Elements
|
|
if (item.text) {
|
|
textElements.push(item.text);
|
|
}
|
|
}
|
|
|
|
const combinedText = textElements.join(String.fromCharCode(10));
|
|
|
|
hasPreviewUrls = (htmlUrls.length + previewUrls.length) > 0;
|
|
|
|
return {
|
|
htmlUrls,
|
|
previewUrls,
|
|
combinedText,
|
|
designMarkdown,
|
|
hasPreviewUrls
|
|
};
|
|
} |