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
251 lines
8.9 KiB
TypeScript
251 lines
8.9 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 { addFAQ } from '../../utils/faqs.js';
|
|
import { MultiAgentToolContext, MultiAgentToolResult, ToolParsingResult } from '../../momoa_core/types.js';
|
|
import { DEFAULT_GEMINI_LITE_MODEL, DEFAULT_GEMINI_PRO_MODEL } from '../../config/models.js';
|
|
import { getFilesAndContent, removeBacktickFences } from '../../utils/markdownUtils.js';
|
|
import { getAssetString, getToolPreamblePrompt, replaceRuntimePlaceholders } from '../../services/promptManager.js';
|
|
import { generateDiff } from '../../utils/diffGenerator.js';
|
|
import { getFormattedCacheContents } from './urlFetchTool.js';
|
|
import { Part } from '@google/genai';
|
|
|
|
/**
|
|
* Implements the File Reader Tool, providing functionality to read file content
|
|
* from an in-memory collection of files (Map). This tool does NOT read from disk.
|
|
*/
|
|
export const askExpertTool: MultiAgentTool = {
|
|
displayName: "Ask an Expert",
|
|
name: 'PHONEAFRIEND',
|
|
|
|
/**
|
|
* Executes the Ask an Expert tool.
|
|
* @param params The parameters for the tool's execution, expecting a 'filename' property.
|
|
* @param context The ToolContext object containing necessary runtime information.
|
|
* @returns A promise that resolves to the file's content or an error message.
|
|
*/
|
|
async execute(params: Record<string, string>, context: MultiAgentToolContext): Promise<MultiAgentToolResult> {
|
|
const updateLog = async (message: string, updateOverseerLog: boolean = true) => {
|
|
context.sendMessage(JSON.stringify({
|
|
status: 'WORK_LOG',
|
|
message: message,
|
|
}));
|
|
|
|
if (updateOverseerLog)
|
|
context.overseer?.addLog(message);
|
|
};
|
|
|
|
const question = params.question;
|
|
|
|
updateLog(`${this.displayName} Invoked`);
|
|
|
|
context.sendMessage({
|
|
type: 'PROGRESS_UPDATE',
|
|
message: `Getting additional guidance from an 'Expert' (Spoiler: It's a bigger LLM with a special prompt).`,
|
|
});
|
|
|
|
try {
|
|
let problemSummary = question;
|
|
let requestedFiles = [];
|
|
let fileNamesPlusContentString = "No files were provided.";
|
|
|
|
const relevantFilesMarker = 'RELEVANT_FILES:';
|
|
const fileMarkerIndex = question.indexOf(relevantFilesMarker);
|
|
|
|
if (fileMarkerIndex !== -1) {
|
|
problemSummary = question.substring(0, fileMarkerIndex).trim();
|
|
const jsonString = question.substring(fileMarkerIndex + relevantFilesMarker.length).trim();
|
|
|
|
try {
|
|
const parsedFiles = JSON.parse(jsonString);
|
|
if (Array.isArray(parsedFiles)) {
|
|
requestedFiles = parsedFiles;
|
|
} else {
|
|
updateLog('RELEVANT_FILES section is not a JSON array');
|
|
}
|
|
} catch (jsonError) {
|
|
updateLog(`Failed to parse RELEVANT_FILES JSON: ${jsonError}`);
|
|
updateLog(`Problematic JSON string: ${jsonString}`);
|
|
}
|
|
} else {
|
|
updateLog(`RELEVANT_FILES marker not found in question.`);
|
|
}
|
|
|
|
const urlContent = getFormattedCacheContents();
|
|
|
|
fileNamesPlusContentString = await getFilesAndContent(requestedFiles, context);
|
|
|
|
// Generate the unified diff
|
|
const diffBlock = generateDiff(
|
|
context.originalFileMap,
|
|
context.fileMap,
|
|
context.editedFilesSet,
|
|
new Set(context.originalBinaryFileMap.keys())
|
|
);
|
|
const projectDiffString = diffBlock ? `${diffBlock}` : "---No changes detected---";
|
|
|
|
let askExpertPrompt = await getToolPreamblePrompt('ask-expert-preamble');
|
|
askExpertPrompt = await replaceRuntimePlaceholders(
|
|
askExpertPrompt,
|
|
{
|
|
ProblemSummary: problemSummary,
|
|
RelevantFiles: fileNamesPlusContentString,
|
|
URLContent: urlContent,
|
|
ProjectDiff: projectDiffString,
|
|
ProjectTask: context.initialPrompt,
|
|
Assumptions: context.assumptions ?? "",
|
|
Spec: context.projectSpecification ?? "--No Specification Provided--"
|
|
});
|
|
|
|
const parts: Part[] = [];
|
|
|
|
// 1. Add Initial Context Image (if present)
|
|
if (context.initialImage && context.initialImageMimeType) {
|
|
parts.push({
|
|
inlineData: {
|
|
mimeType: context.initialImageMimeType,
|
|
data: context.initialImage,
|
|
}
|
|
});
|
|
}
|
|
|
|
// 2. Add Relevant Images from the File List
|
|
// We iterate through requested files to see if any are binary images.
|
|
for (const filename of requestedFiles) {
|
|
if (context.binaryFileMap.has(filename)) {
|
|
const mimeType = getImageMimeType(filename);
|
|
const base64Data = context.binaryFileMap.get(filename);
|
|
|
|
if (mimeType && base64Data) {
|
|
parts.push({
|
|
inlineData: {
|
|
mimeType: mimeType,
|
|
data: base64Data
|
|
}
|
|
});
|
|
updateLog(`Attaching relevant image file to expert prompt: ${filename}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. Add the Text Prompt
|
|
parts.push({ text: askExpertPrompt });
|
|
|
|
const expertResponse = (await context.multiAgentGeminiClient.sendOneShotMessage(
|
|
parts,
|
|
{
|
|
model: DEFAULT_GEMINI_PRO_MODEL,
|
|
enableThinking: true,
|
|
}
|
|
));
|
|
|
|
let expertOpinion = expertResponse?.text || "";
|
|
expertOpinion = removeBacktickFences(expertOpinion).trim();
|
|
|
|
if (!expertOpinion) {
|
|
// Check the candidate for specific failure reasons
|
|
const candidate = expertResponse.candidates?.[0];
|
|
if (candidate) {
|
|
if (candidate.finishReason !== 'STOP') {
|
|
updateLog(`**Expert Tool Warning:** Response stopped due to: ${candidate.finishReason}`);
|
|
}
|
|
|
|
// Attempt to extract text from parts (including thoughts)
|
|
expertOpinion = candidate.content?.parts
|
|
?.map(p => p.text)
|
|
.join('\n')
|
|
.trim() || "";
|
|
|
|
if (expertOpinion) {
|
|
updateLog("**Expert Tool:** Recovered text from raw parts (likely thought trace + partial answer).");
|
|
}
|
|
} else {
|
|
updateLog("**Expert Tool Error:** No candidates returned in response.");
|
|
}
|
|
}
|
|
|
|
if (!expertOpinion) {
|
|
const nullResult = '[The expert provided a non-text response]';
|
|
updateLog(nullResult);
|
|
return { result: nullResult };
|
|
}
|
|
|
|
const result = "You asked an expert and this is what they said:\n" + expertOpinion;
|
|
|
|
const completed_status_message_prompt = await replaceRuntimePlaceholders(await getAssetString("summarize-progress-start"), {
|
|
LastOrchestratorResponse: result
|
|
});
|
|
|
|
try {
|
|
const opinionSummaryPromise = (context.multiAgentGeminiClient.sendOneShotMessage(
|
|
completed_status_message_prompt,
|
|
{ model: DEFAULT_GEMINI_LITE_MODEL, signal: context.signal }
|
|
)).then(msg => msg.text || "Empty");
|
|
context.sendMessage({
|
|
type: 'PROGRESS_UPDATE',
|
|
message: opinionSummaryPromise,
|
|
});
|
|
} catch (_error) {}
|
|
|
|
addFAQ(problemSummary, expertOpinion, context);
|
|
|
|
return {result: result};
|
|
} catch (error) {
|
|
const errReult = `An error occurred while asking an expert: ${error}`;
|
|
updateLog(errReult);
|
|
return { result: errReult };
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Extract parameters from the tool invocation string.
|
|
* @param invocation The string used to invoke the tool.
|
|
* @returns The parameter names and corresponding values.
|
|
*/
|
|
async extractParameters(invocation: string, _context: MultiAgentToolContext): Promise<ToolParsingResult> {
|
|
if (invocation.trim()) {
|
|
const question = invocation.trim();
|
|
return {
|
|
success: true,
|
|
params: {
|
|
question
|
|
}
|
|
};
|
|
} else {
|
|
return {
|
|
success: false,
|
|
error: `Invalid syntax for ${this.displayName} Tool. No question was provided.`
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Helper function to determine MIME type from filename extension for supported image types.
|
|
*/
|
|
function getImageMimeType(filename: string): string | null {
|
|
const ext = filename.split('.').pop()?.toLowerCase();
|
|
switch (ext) {
|
|
case 'png': return 'image/png';
|
|
case 'jpg':
|
|
case 'jpeg': return 'image/jpeg';
|
|
case 'webp': return 'image/webp';
|
|
case 'heic': return 'image/heic';
|
|
case 'heif': return 'image/heif';
|
|
default: return null;
|
|
}
|
|
} |