Claude 24f816b6a3
Consolidate 22 sibling repos into layered organism structure
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
2026-06-10 06:53:01 +00:00

615 lines
19 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 fetch from 'node-fetch';
import { VerbosityType } from '../momoa_core/types.js';
import { GeminiClient } from './geminiClient.js';
import { DEFAULT_GEMINI_LITE_MODEL } from '../config/models.js';
import { removeBacktickFences } from '../utils/markdownUtils.js';
import { getAssetString, replaceRuntimePlaceholders } from './promptManager.js';
export enum JulesSessionState {
STATE_UNSPECIFIED = 'STATE_UNSPECIFIED',
QUEUED = 'QUEUED',
PLANNING = 'PLANNING',
AWAITING_PLAN_APPROVAL = 'AWAITING_PLAN_APPROVAL',
AWAITING_USER_FEEDBACK = 'AWAITING_USER_FEEDBACK',
IN_PROGRESS = 'IN_PROGRESS',
PAUSED = 'PAUSED',
FAILED = 'FAILED',
COMPLETED = 'COMPLETED',
}
export interface SourceContext {
source: string;
githubRepoContext: GitHubRepoContext;
}
export interface JulesSession {
name: string;
id: string;
prompt: string;
title?: string;
createTime: string;
updateTime: string;
state: JulesSessionState;
url: string
sourceContext: SourceContext;
outputs: SessionOutput[]
}
export interface SessionOutput {
pullRequest: PullRequest;
}
export interface PullRequest {
url: string;
title: string;
description: string;
}
export interface ListSessionsResponse {
sessions: JulesSession[];
}
export interface GitHubRepoContext {
startingBranch: string;
}
export interface GitHubBranch {
displayName: string;
}
export interface GitHubRepo {
owner: string;
repo: string;
isPrivate: boolean;
defaultBranch: GitHubBranch;
branches: GitHubBranch[];
}
export interface JulesSource {
name: string;
githubRepo: GitHubRepo;
}
export interface ListSourcesResponse {
sources: JulesSource[];
nextPageToken: string;
}
export interface AgentMessaged {
agentMessage: string;
}
export interface UserMessaged {
userMessage: string;
}
export interface PlanStep {
id: string;
title: string;
description: string;
index: number;
}
export interface Plan {
id: string;
steps: PlanStep[];
createTime: string;
}
export interface PlanGenerated {
plan: Plan;
}
export interface PlanApproved {
planId: string;
}
// -- Progress & Status Interfaces --
export interface ProgressUpdated {
title: string;
description: string;
}
export interface SessionCompleted {
}
export interface SessionFailed {
reason: string;
}
export interface GitPatch {
unidiffPatch: string;
baseCommitId: string;
suggestedCommitMessage: string;
}
export interface ChangeSet {
source: string;
gitPatch: GitPatch;
}
export interface BashOutput {
command: string;
output: string;
exitCode: number;
}
export interface Media {
data: string;
mimeType: string;
}
export interface Artifact {
changeSet?: ChangeSet;
bashOutput?: BashOutput;
media?: Media;
}
export interface Activity {
name: string;
id: string;
description: string;
createTime: string;
originator: string;
artifacts?: Artifact[];
agentMessaged?: AgentMessaged;
userMessaged?: UserMessaged;
planGenerated?: PlanGenerated;
planApproved?: PlanApproved;
progressUpdated?: ProgressUpdated;
sessionCompleted?: SessionCompleted;
sessionFailed?: SessionFailed;
}
export interface ListActivitiesResponse {
activities: Activity[];
}
export const JULES_API_BASE_URL = 'https://jules.googleapis.com';
export class JulesAPIService {
private apiKey: string;
constructor(apiKey: string) {
this.apiKey = apiKey;
}
async getSessions(): Promise<JulesSession[] | { error: string }> {
if (!this.apiKey) {
console.error('API Key not loaded');
return { error: 'API Key not loaded' };
}
try {
// Calls the list method for sessions
const response = await fetch(`${JULES_API_BASE_URL}/v1alpha/sessions`, {
headers: { 'x-goog-api-key': this.apiKey }
});
if (!response.ok) {
throw new Error(`API Error: ${response.statusText}`);
}
const data = await response.json() as ListSessionsResponse;
return data.sessions || [];
} catch (error: any) {
console.error('Error fetching Jules tasks:', error);
return { error: error.message };
}
}
/**
* Fetches a complete list of Jules projects (sources), handling pagination automatically.
* Can optionally filter the results by name.
* @param sourceNames - Optional. An array of full source names to filter by (e.g., ['sources/project-one']).
* @returns A promise that resolves to a complete array of JulesSource objects or an error object.
*/
async getProjects(sourceNames?: string[]): Promise<JulesSource[] | { error: string }> {
if (!this.apiKey) {
console.error('API Key not loaded');
return { error: 'API Key not loaded' };
}
// This array will accumulate sources from all pages.
const allSources: JulesSource[] = [];
let pageToken: string | undefined = undefined;
try {
do {
const url = new URL(`${JULES_API_BASE_URL}/v1alpha/sources`);
// Apply the filter, if provided.
if (sourceNames && sourceNames.length > 0) {
const filterString = sourceNames
.map(name => `name=${name}`)
.join(' OR ');
url.searchParams.append('filter', filterString);
}
// Set page size to the maximum allowed value to reduce API calls.
url.searchParams.append('pageSize', '100');
// If we have a pageToken from a previous request, add it to the URL.
if (pageToken) {
url.searchParams.append('pageToken', pageToken);
}
const response = await fetch(url.toString(), {
headers: { 'x-goog-api-key': this.apiKey }
});
if (!response.ok) {
const errorBody = await response.text();
console.error(`API Error: ${response.status} ${response.statusText}`, errorBody);
throw new Error(`API Error: ${response.status} ${response.statusText}`);
}
const data = await response.json() as ListSourcesResponse;
// Add the sources from the current page to our master list.
if (data.sources && data.sources.length > 0) {
allSources.push(...data.sources);
}
// Get the token for the next page. If it's undefined, the loop will end.
pageToken = data.nextPageToken;
} while (pageToken); // Continue looping as long as there is a next page.
//console.log(`Successfully fetched a total of ${allSources.length} sources.`);
return allSources;
} catch (error: any) {
console.error('Error fetching Jules projects:', error);
return { error: error.message };
}
}
async createSession(prompt: string, sourceName: string, branchName: string): Promise<JulesSession | { error: string }> {
if (!this.apiKey) {
console.error('API Key not loaded');
return { error: 'API Key not loaded' };
}
const sessionBody = {
prompt,
requirePlanApproval: false,
sourceContext: {
source: sourceName,
githubRepoContext: {
startingBranch: branchName
}
}
};
try {
// Calls the create method for sessions
const response = await fetch(`${JULES_API_BASE_URL}/v1alpha/sessions`, {
method: 'POST',
headers: {
'x-goog-api-key': this.apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify(sessionBody)
});
if (!response.ok) {
const errText = await response.text();
throw new Error(`API Error: ${response.statusText} - ${errText}`);
}
const data = await response.json() as JulesSession;
return data; // Return the newly created session
} catch (error: any) {
console.error('Error creating Jules task:', error);
return { error: error.message };
}
}
async getSession(sessionName: string): Promise<JulesSession | { error: string }> {
if (!this.apiKey) {
return { error: 'API Key not loaded' };
}
try {
const response = await fetch(`${JULES_API_BASE_URL}/v1alpha/${sessionName}`, {
headers: { 'x-goog-api-key': this.apiKey },
});
if (!response.ok) {
throw new Error(`API Error: ${response.statusText}`);
}
return await response.json() as JulesSession;
} catch (error: any) {
//console.error(`Error fetching session ${sessionName}:`, error);
return { error: error.message };
}
}
async listActivities(sessionName: string): Promise<Activity[] | { error: string }> {
if (!this.apiKey) {
return { error: 'API Key not loaded' };
}
try {
sessionName = sessionName.replace("sessions/", "");
const fetchString = `${JULES_API_BASE_URL}/v1alpha/sessions/${sessionName}/activities`;
//console.log("Fetching: " + fetchString);
const response = await fetch(fetchString, {
headers: { 'x-goog-api-key': this.apiKey },
});
if (!response.ok) {
throw new Error(`API Error: ${response.statusText}`);
}
const data = await response.json() as ListActivitiesResponse;
return data.activities || [];
} catch (error: any) {
//console.error(`Error listing activities for session ${sessionName}:`, error);
return [];
}
}
async postUserMessage(sessionName: string, message: string): Promise<{ success: boolean } | { error: string }> {
if (!this.apiKey) {
return { error: 'API Key not loaded' };
}
// The API expects a simple "prompt" in the body.
const messageBody = {
prompt: message
};
try {
// The URL must use the full sessionName and the ":sendMessage" custom method.
// DO NOT strip "sessions/" from the sessionName.
const url = `${JULES_API_BASE_URL}/v1alpha/${sessionName}:sendMessage`;
const response = await fetch(url, {
method: 'POST',
headers: {
'x-goog-api-key': this.apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify(messageBody)
});
if (!response.ok) {
const errText = await response.text();
throw new Error(`API Error: ${response.statusText} - ${errText}`);
}
// According to the documentation, the response body is empty on success.
// We will return a simple success object instead of an Activity.
return { success: true };
} catch (error: any) {
console.error(`Error posting user message to session ${sessionName}:`, error);
return { error: error.message };
}
}
}
/**
* Compares the data for each Jules session from the 'list' API endpoint
* against the 'get' API endpoint and logs only the differences found.
*/
export async function compareJulesSessions(julesService: JulesAPIService) {
if (!julesService) {
const errorMessage = 'Jules API Service not initialized. API Key may be missing.';
console.error(errorMessage);
//dialog.showErrorBox('API Key Not Set', 'Please set your API key before comparing Jules sessions.');
return;
}
console.log('🚀 Starting Jules session comparison...');
const sessionsListResult = await julesService.getSessions();
if ('error' in sessionsListResult) {
console.error('Failed to fetch session list:', sessionsListResult.error);
return;
}
if (sessionsListResult.length === 0) {
console.log('No Jules sessions found to compare.');
return;
}
let differencesFound = false;
for (const sessionFromList of sessionsListResult) {
const detailedSessionResult = await julesService.getSession(sessionFromList.name);
if ('error' in detailedSessionResult) {
console.error(`Failed to fetch details for session ${sessionFromList.name}:`, detailedSessionResult.error);
continue;
}
console.log(`Session State List ${sessionFromList.state}`);
console.log(`Session State Get ${detailedSessionResult.state}`);
const differences = findDifferences(sessionFromList, detailedSessionResult);
if (differences.length > 0) {
differencesFound = true;
console.log(`\n❌ Differences found for session: ${sessionFromList.name}`);
differences.forEach(diff => console.log(` ${diff}`));
}
}
if (!differencesFound) {
console.log('✅ All session data is IDENTICAL between list and get APIs.');
}
console.log('\n--- Jules session comparison finished. ---');
}
/**
* Performs a deep comparison between two objects and returns an array of strings
* describing the differences.
* @param obj1 The first object (from the LIST call).
* @param obj2 The second object (from the GET call).
* @param path The current path for nested objects (used for recursion).
* @returns An array of strings detailing the differences.
*/
function findDifferences(obj1: any, obj2: any, path: string = ''): string[] {
const differences: string[] = [];
if (obj1 === null || obj2 === null) {
if (obj1 !== obj2) {
differences.push(`- Path '${path}': '${JSON.stringify(obj1)}' (LIST) vs '${JSON.stringify(obj2)}' (GET)`);
}
return differences;
}
const allKeys = new Set([...Object.keys(obj1), ...Object.keys(obj2)]);
for (const key of allKeys) {
const newPath = path ? `${path}.${key}` : key;
const val1 = obj1[key];
const val2 = obj2[key];
if (typeof val1 === 'object' && val1 !== null && typeof val2 === 'object' && val2 !== null && !Array.isArray(val1) && !Array.isArray(val2)) {
differences.push(...findDifferences(val1, val2, newPath));
} else if (JSON.stringify(val1) !== JSON.stringify(val2)) {
let diffString = `- Key '${newPath}': `;
if (val1 === undefined) {
diffString += `not in LIST vs '${JSON.stringify(val2)}' (GET)`;
} else if (val2 === undefined) {
diffString += `'${JSON.stringify(val1)}' (LIST) vs not in GET`;
} else {
diffString += `'${JSON.stringify(val1)}' (LIST) vs '${JSON.stringify(val2)}' (GET)`;
}
differences.push(diffString);
}
}
return differences;
}
/**
* Formats a Plan object into a readable string listing all steps.
*/
export function formatPlan(plan: Plan): string {
if (!plan || !plan.steps || plan.steps.length === 0) {
return ' No steps in plan.';
}
const header = ` Plan ID: ${plan.id} (Created: ${plan.createTime})`;
const steps = plan.steps
.sort((a, b) => a.index - b.index) // Ensure steps are in order
.map(step => {
return ` [Step ${step.index + 1}] ${step.title}\n Details: ${step.description}`;
})
.join('\n');
return `${header}\n${steps}`;
}
/**
* Formats a BashOutput artifact into a readable block.
*/
export function formatBashOutput(bash: BashOutput): string {
if (!bash) return ' No bash output data.';
let result = `$> ${bash.command}`;
if (bash.exitCode)
result += `\n[Exit Code: ${bash.exitCode}]`;
result += `Output:
${bash.output.trim().replace(/^/gm, ' ')}
`; // Indents the output for readability
return result;
}
/**
* Helper to log an Activity's specific content based on its type.
*/
export async function formatActivityContent(activity: Activity, verbosity: VerbosityType = VerbosityType.AISummarize, geminiClient?: GeminiClient, assignedTask?: string): Promise<string> {
const parts: string[] = [];
if ((verbosity == VerbosityType.AISummarize) && (!geminiClient)) {
console.warn("AISummarize verbosity selected without a Gemini Client. Defaulting to Quiet.");
verbosity = VerbosityType.Quiet
}
if (verbosity == VerbosityType.Verbose)
parts.push(`Activity: ${activity.name} (${activity.createTime})`);
if (activity.agentMessaged) {
parts.push(`Message from Agent:\n${activity.agentMessaged.agentMessage}`);
}
if (activity.userMessaged) {
parts.push(`Message from User: "${activity.userMessaged.userMessage}"`);
}
if (activity.planGenerated) {
parts.push(`Jules Plan Generated`);
if (verbosity == VerbosityType.Verbose) {
parts.push(`:\n${formatPlan(activity.planGenerated.plan)}`);
}
}
if (activity.planApproved) {
parts.push(`Jules Plan Approved`);
if (verbosity == VerbosityType.Verbose) {
parts.push(`: ${activity.planApproved.planId}`);
}
}
if (activity.progressUpdated) {
if (activity.progressUpdated.title || activity.progressUpdated.description) {
const { title, description } = activity.progressUpdated;
// 1. Remove trailing ellipses/whitespace from title for comparison
const cleanTitle = title ? title.replace(/(\.{3}|…)$/, "").trim() : "";
// 2. Check if the description contains the cleaned title
const isTitleInDescription = description && cleanTitle && description.includes(cleanTitle);
// 3. Only add title if it is NOT found in the description
if (title && !isTitleInDescription) {
parts.push(`${title}`);
}
// 4. Always add description if it exists
if (description) {
parts.push(`${description}`);
}
}
}
if (activity.sessionFailed) {
parts.push(`Session Failed: ${activity.sessionFailed.reason}`);
}
// Handle Artifacts (BashOutput, etc.)
if (activity.artifacts && activity.artifacts.length > 0) {
// parts.push(` Artifacts:`);
for (const artifact of activity.artifacts) {
if (artifact.bashOutput && (verbosity !== VerbosityType.Quiet)) {
parts.push(`[Bash Command Run]`);
if (verbosity == VerbosityType.Verbose) {
parts.push(`${formatBashOutput(artifact.bashOutput)}`);
} else if (verbosity == VerbosityType.AISummarize && geminiClient) {
const formattedBashOutput = formatBashOutput(artifact.bashOutput);
const prompt = await replaceRuntimePlaceholders(await getAssetString("bash-summarizer"), {
BashOutput: formattedBashOutput,
AssignedTask: assignedTask || '---No Task Specified---',
})
const summarizedBash = (await geminiClient.sendOneShotMessage(
prompt,
{ model: DEFAULT_GEMINI_LITE_MODEL }
))?.text || formattedBashOutput;
const cleansummarizedBash = removeBacktickFences(summarizedBash);
parts.push(cleansummarizedBash);
}
}
}
}
return parts.join('\n');
}