From 1044b41835018b6fe6e2e12310ef61141fa4a5f6 Mon Sep 17 00:00:00 2001 From: cafechen Date: Wed, 8 Apr 2026 13:28:33 +0800 Subject: [PATCH 1/4] feat: Add parallel chain to extract and inject initial user prompt - Add readFileHead(), extractFirstUserMessage(), and getCurrentSessionInitialPrompt() functions - Extract first USER message from current session file (Cursor/OpenClaw) - Inject initial user prompt into GEP prompt context and metadata - Store initial_user_prompt in prompt artifact metadata and solidify state - Support both Cursor transcripts and OpenClaw session files as data sources - Maintain audit trail for original user intent throughout evolution cycle --- src/evolve.js | 92 +++++++++++++++++++++++++++++++++++++++++++++++ src/gep/prompt.js | 2 ++ 2 files changed, 94 insertions(+) diff --git a/src/evolve.js b/src/evolve.js index e0abc70..ef557e6 100644 --- a/src/evolve.js +++ b/src/evolve.js @@ -79,6 +79,89 @@ function shouldSkipHubCalls(signals) { return true; } +// New: Read file head (first maxBytes) to extract initial user prompt +function readFileHead(filePath, maxBytes = 8192) { + try { + if (!fs.existsSync(filePath)) return ''; + const fd = fs.openSync(filePath, 'r'); + const buffer = Buffer.alloc(maxBytes); + const bytesRead = fs.readSync(fd, buffer, 0, maxBytes, 0); + fs.closeSync(fd); + return buffer.slice(0, bytesRead).toString('utf8'); + } catch (e) { + console.warn(`[readFileHead] Failed to read ${filePath}: ${e.message}`); + return ''; + } +} + +// New: Extract first USER message from session content +function extractFirstUserMessage(content) { + if (!content) return null; + const lines = content.split('\n'); + for (const line of lines) { + if (!line.trim()) continue; + try { + const data = JSON.parse(line); + const msg = data.message || data; + if (msg.role === 'user' || msg.role === 'USER') { + const content = msg.content; + if (Array.isArray(content)) { + // Handle array content (e.g., from Claude/OpenAI) + const textParts = content.filter(c => c.type === 'text').map(c => c.text).join(''); + return textParts.trim(); + } else if (typeof content === 'string') { + return content.trim(); + } + } + } catch (e) { + // Not JSON, skip + } + } + return null; +} + +// New: Unified entry to get current session's initial user prompt +function getCurrentSessionInitialPrompt() { + const sessionSource = SESSION_SOURCE; + let sessionFile = null; + + if (sessionSource === 'cursor' && CURSOR_TRANSCRIPTS_DIR) { + // Use cursor transcripts + try { + const files = collectTranscriptFiles(CURSOR_TRANSCRIPTS_DIR, 3); + if (files.length > 0) { + // Sort by time descending, take most recent + files.sort((a, b) => b.time - a.time); + sessionFile = files[0].path; + } + } catch (e) { + console.warn(`[getCurrentSessionInitialPrompt] Failed to collect cursor transcripts: ${e.message}`); + } + } else { + // Use OpenClaw sessions + try { + const sessions = fs.readdirSync(AGENT_SESSIONS_DIR) + .filter(f => f.endsWith('.jsonl')) + .map(f => ({ + name: f, + path: path.join(AGENT_SESSIONS_DIR, f), + time: fs.statSync(path.join(AGENT_SESSIONS_DIR, f)).mtime.getTime() + })) + .sort((a, b) => b.time - a.time); + if (sessions.length > 0) { + sessionFile = sessions[0].path; + } + } catch (e) { + console.warn(`[getCurrentSessionInitialPrompt] Failed to read agent sessions: ${e.message}`); + } + } + + if (!sessionFile) return null; + + const headContent = readFileHead(sessionFile, 16384); // Read first 16KB + return extractFirstUserMessage(headContent); +} + // Load environment variables from repo root try { require('dotenv').config({ path: path.join(REPO_ROOT, '.env'), quiet: true }); @@ -1944,6 +2027,7 @@ async function run() { commitment_deadline: activeTask ? (activeTask._commitment_deadline || null) : null, applied_lessons: hubLessons.map(function(l) { return l.lesson_id; }).filter(Boolean), hub_lessons: hubLessons, + initial_user_prompt: initialUserPrompt, }; writeStateForSolidify(prevState); @@ -1977,6 +2061,9 @@ async function run() { const genesPreview = `\`\`\`json\n${JSON.stringify(genes.slice(0, 6), null, 2)}\n\`\`\``; const capsulesPreview = `\`\`\`json\n${JSON.stringify(capsules.slice(-3), null, 2)}\n\`\`\``; + // Get initial user prompt from current session + const initialUserPrompt = getCurrentSessionInitialPrompt(); + const reviewNote = IS_REVIEW_MODE ? 'Review mode: before significant edits, pause and ask the user for confirmation.' : 'Review mode: disabled.'; @@ -1996,6 +2083,9 @@ async function run() { })(); const context = ` +Initial User Prompt (Original Intent): +${initialUserPrompt ? `\`\`\`\n${initialUserPrompt}\n\`\`\`` : '(not available)'} + Runtime state: - System health: ${healthReport} - Agent state: ${moodStatus} @@ -2085,6 +2175,7 @@ ${mutationDirective} strategyPolicy, failedCapsules: recentFailedCapsules, hubLessons, + initialUserPrompt, }); // Optional: emit a compact thought process block for wrappers (noise-controlled). @@ -2130,6 +2221,7 @@ ${mutationDirective} dry_run: IS_DRY_RUN, mutation_id: mutation && mutation.id ? mutation.id : null, personality_key: personalitySelection && personalitySelection.personality_key ? personalitySelection.personality_key : null, + initial_user_prompt: initialUserPrompt, }, }); } catch (e) { diff --git a/src/gep/prompt.js b/src/gep/prompt.js index 6091e87..5037550 100644 --- a/src/gep/prompt.js +++ b/src/gep/prompt.js @@ -267,6 +267,7 @@ function buildGepPrompt({ failedCapsules, hubLessons, strategyPolicy, + initialUserPrompt, }) { const parentValue = parentEventId ? `"${parentEventId}"` : 'null'; const selectedGeneId = selectedGene && selectedGene.id ? selectedGene.id : 'gene_'; @@ -569,6 +570,7 @@ ${buildAntiPatternZone(failedCapsules, signals)}${buildLessonsBlock(hubLessons, ${historyBlock} ${buildNarrativeBlock()} ${buildPrinciplesBlock()} +${initialUserPrompt ? `Context [Initial User Prompt]:\n${initialUserPrompt}\n` : ''} Context [Execution]: ${executionContext} From a9c4cc68f27c08bf4dbf6328e8564151558ff217 Mon Sep 17 00:00:00 2001 From: cafechen Date: Wed, 8 Apr 2026 14:14:57 +0800 Subject: [PATCH 2/4] feat: Include initial_user_prompt in EvolutionEvent and Capsule objects - Add initial_user_prompt field to EvolutionEvent for source intent tracking - Add initial_user_prompt field to Capsule for context preservation - Add initial_user_prompt field to FailedCapsule for debugging - Extract from solidify_state.json (last_run.initial_user_prompt) - Automatically included in A2A publish payload for hub synchronization - Enables complete audit trail from user request to hub knowledge graph --- src/gep/solidify.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gep/solidify.js b/src/gep/solidify.js index 44f112d..9f5fcde 100644 --- a/src/gep/solidify.js +++ b/src/gep/solidify.js @@ -759,6 +759,7 @@ function solidify({ intent, summary, dryRun = false, rollbackOnFailure = true } capsule_id: capsuleId, source_type: sourceType, reused_asset_id: reusedAssetId, + initial_user_prompt: lastRun && lastRun.initial_user_prompt ? lastRun.initial_user_prompt : null, ...(appliedLessons.length > 0 ? { applied_lessons: appliedLessons } : {}), gene_library_version: geneLibVersion, env_fingerprint: envFp, @@ -858,6 +859,7 @@ function solidify({ intent, summary, dryRun = false, rollbackOnFailure = true } env_fingerprint: envFp, source_type: sourceType, reused_asset_id: reusedAssetId, + initial_user_prompt: lastRun && lastRun.initial_user_prompt ? lastRun.initial_user_prompt : null, a2a: { eligible_to_broadcast: false }, content: capsuleContent, diff: capsuleDiff || undefined, @@ -887,6 +889,7 @@ function solidify({ intent, summary, dryRun = false, rollbackOnFailure = true } constraint_violations: constraintCheck.violations || [], env_fingerprint: envFp, blast_radius: { files: blast.files, lines: blast.lines }, + initial_user_prompt: lastRun && lastRun.initial_user_prompt ? lastRun.initial_user_prompt : null, created_at: ts, }; failedCapsule.asset_id = computeAssetId(failedCapsule); From f099684f1e207a65c4d3fb4f0fd51332749f6fa0 Mon Sep 17 00:00:00 2001 From: cafechen Date: Wed, 8 Apr 2026 18:12:10 +0800 Subject: [PATCH 3/4] Revert "feat: Include initial_user_prompt in EvolutionEvent and Capsule objects" This reverts commit a9c4cc68f27c08bf4dbf6328e8564151558ff217. --- src/gep/solidify.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/gep/solidify.js b/src/gep/solidify.js index 9f5fcde..44f112d 100644 --- a/src/gep/solidify.js +++ b/src/gep/solidify.js @@ -759,7 +759,6 @@ function solidify({ intent, summary, dryRun = false, rollbackOnFailure = true } capsule_id: capsuleId, source_type: sourceType, reused_asset_id: reusedAssetId, - initial_user_prompt: lastRun && lastRun.initial_user_prompt ? lastRun.initial_user_prompt : null, ...(appliedLessons.length > 0 ? { applied_lessons: appliedLessons } : {}), gene_library_version: geneLibVersion, env_fingerprint: envFp, @@ -859,7 +858,6 @@ function solidify({ intent, summary, dryRun = false, rollbackOnFailure = true } env_fingerprint: envFp, source_type: sourceType, reused_asset_id: reusedAssetId, - initial_user_prompt: lastRun && lastRun.initial_user_prompt ? lastRun.initial_user_prompt : null, a2a: { eligible_to_broadcast: false }, content: capsuleContent, diff: capsuleDiff || undefined, @@ -889,7 +887,6 @@ function solidify({ intent, summary, dryRun = false, rollbackOnFailure = true } constraint_violations: constraintCheck.violations || [], env_fingerprint: envFp, blast_radius: { files: blast.files, lines: blast.lines }, - initial_user_prompt: lastRun && lastRun.initial_user_prompt ? lastRun.initial_user_prompt : null, created_at: ts, }; failedCapsule.asset_id = computeAssetId(failedCapsule); From 43a57d29e7d4cd9d973380d44f89bd5d959fdd1f Mon Sep 17 00:00:00 2001 From: cafechen Date: Wed, 8 Apr 2026 18:18:55 +0800 Subject: [PATCH 4/4] Translate hub event comments to English in src/evolve.js --- src/evolve.js | 90 ++++++++++++++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 41 deletions(-) diff --git a/src/evolve.js b/src/evolve.js index ef557e6..e4ca2de 100644 --- a/src/evolve.js +++ b/src/evolve.js @@ -79,7 +79,7 @@ function shouldSkipHubCalls(signals) { return true; } -// New: Read file head (first maxBytes) to extract initial user prompt +// Read file head (first maxBytes) to extract initial user prompt function readFileHead(filePath, maxBytes = 8192) { try { if (!fs.existsSync(filePath)) return ''; @@ -94,7 +94,7 @@ function readFileHead(filePath, maxBytes = 8192) { } } -// New: Extract first USER message from session content +// Extract first USER message from session content function extractFirstUserMessage(content) { if (!content) return null; const lines = content.split('\n'); @@ -104,13 +104,13 @@ function extractFirstUserMessage(content) { const data = JSON.parse(line); const msg = data.message || data; if (msg.role === 'user' || msg.role === 'USER') { - const content = msg.content; - if (Array.isArray(content)) { + const msgContent = msg.content; + if (Array.isArray(msgContent)) { // Handle array content (e.g., from Claude/OpenAI) - const textParts = content.filter(c => c.type === 'text').map(c => c.text).join(''); + const textParts = msgContent.filter(c => c.type === 'text').map(c => c.text).join(''); return textParts.trim(); - } else if (typeof content === 'string') { - return content.trim(); + } else if (typeof msgContent === 'string') { + return msgContent.trim(); } } } catch (e) { @@ -120,46 +120,56 @@ function extractFirstUserMessage(content) { return null; } -// New: Unified entry to get current session's initial user prompt +// Unified entry to get current session's initial user prompt function getCurrentSessionInitialPrompt() { const sessionSource = SESSION_SOURCE; - let sessionFile = null; - if (sessionSource === 'cursor' && CURSOR_TRANSCRIPTS_DIR) { - // Use cursor transcripts + function getCursorPrompt() { + if (!CURSOR_TRANSCRIPTS_DIR) return null; try { const files = collectTranscriptFiles(CURSOR_TRANSCRIPTS_DIR, 3); - if (files.length > 0) { - // Sort by time descending, take most recent - files.sort((a, b) => b.time - a.time); - sessionFile = files[0].path; - } + if (!files || files.length === 0) return null; + files.sort((a, b) => b.time - a.time); + const headContent = readFileHead(files[0].path, 16384); + return extractFirstUserMessage(headContent); } catch (e) { console.warn(`[getCurrentSessionInitialPrompt] Failed to collect cursor transcripts: ${e.message}`); + return null; } - } else { - // Use OpenClaw sessions + } + + function getOpenClawPrompt() { try { const sessions = fs.readdirSync(AGENT_SESSIONS_DIR) .filter(f => f.endsWith('.jsonl')) .map(f => ({ - name: f, path: path.join(AGENT_SESSIONS_DIR, f), - time: fs.statSync(path.join(AGENT_SESSIONS_DIR, f)).mtime.getTime() + time: fs.statSync(path.join(AGENT_SESSIONS_DIR, f)).mtime.getTime(), })) .sort((a, b) => b.time - a.time); - if (sessions.length > 0) { - sessionFile = sessions[0].path; - } + if (!sessions || sessions.length === 0) return null; + const headContent = readFileHead(sessions[0].path, 16384); + return extractFirstUserMessage(headContent); } catch (e) { console.warn(`[getCurrentSessionInitialPrompt] Failed to read agent sessions: ${e.message}`); + return null; } } - if (!sessionFile) return null; + if (sessionSource === 'cursor') { + return getCursorPrompt(); + } - const headContent = readFileHead(sessionFile, 16384); // Read first 16KB - return extractFirstUserMessage(headContent); + if (sessionSource === 'openclaw') { + return getOpenClawPrompt(); + } + + if (sessionSource === 'merge') { + return getOpenClawPrompt() || getCursorPrompt(); + } + + // 'auto' (default): OpenClaw primary, Cursor fallback + return getOpenClawPrompt() || getCursorPrompt(); } // Load environment variables from repo root @@ -1213,6 +1223,7 @@ async function run() { const cycleNum = getNextCycleId(); const cycleId = `Cycle #${cycleNum}`; + const initialUserPrompt = getCurrentSessionInitialPrompt(); // 2. Detect Workspace State & Local Overrides // Logic: Default to generic reporting (message) @@ -1562,10 +1573,10 @@ async function run() { const hubEvents = consumeHubEvents(); if (hubEvents.length > 0) { const HUB_EVENT_SIGNALS = { - // ── 对话 ────────────────────────────────────────────────────── + // ── Dialog ────────────────────────────────────────────────────── dialog_message: ['dialog', 'respond_required'], - // ── 议会 / 治理 ─────────────────────────────────────────────── + // ── Council / Governance ───────────────────────────────────────── council_invite: ['council', 'governance', 'respond_required'], council_second_request: ['council', 'governance', 'second_request', 'respond_required'], council_vote: ['council', 'vote', 'governance', 'respond_required'], @@ -1573,19 +1584,19 @@ async function run() { council_decision: ['council', 'decision', 'governance'], council_decision_notification: ['council', 'governance'], - // ── 审议 / 辩论 ─────────────────────────────────────────────── + // ── Deliberation / Debate ─────────────────────────────────────── deliberation_invite: ['deliberation', 'governance', 'respond_required'], deliberation_challenge: ['deliberation', 'challenge', 'respond_required'], deliberation_next_round: ['deliberation', 'next_round', 'respond_required'], deliberation_completed: ['deliberation', 'governance'], - // ── 协作 / 会话 ─────────────────────────────────────────────── + // ── Collaboration / Session ────────────────────────────────────── collaboration_invite: ['collaboration', 'respond_required'], session_message: ['collaboration', 'dialog', 'respond_required'], session_nudge: ['collaboration', 'idle_warning'], task_board_update: ['collaboration', 'task_update'], - // ── 任务 / 工作池 ───────────────────────────────────────────── + // ── Task / Work Pool ─────────────────────────────────────────── task_available: ['task', 'work_available'], work_assigned: ['task', 'work_assigned'], swarm_subtask_available: ['swarm', 'task', 'work_available'], @@ -1594,7 +1605,7 @@ async function run() { pipeline_step_assigned: ['pipeline', 'task', 'work_assigned'], organism_work: ['organism', 'task', 'work_assigned'], - // ── 蜂群 PDRI 角色事件 ────────────────────────────────────── + // ── Swarm PDRI Role Events ────────────────────────────────────── swarm_plan_available: ['swarm', 'planner', 'work_available'], swarm_build_available: ['swarm', 'builder', 'work_available'], swarm_review_available: ['swarm', 'reviewer', 'work_available', 'respond_required'], @@ -1604,22 +1615,22 @@ async function run() { team_formed: ['swarm', 'team', 'collaboration'], team_dissolved: ['swarm', 'team'], - // ── 隐私计算 ──────────────────────────────────────────────── + // ── Privacy Computation ───────────────────────────────────────── privacy_task_ready: ['privacy', 'sealed_tool', 'work_available'], privacy_result_available: ['privacy', 'result'], - // ── 评审 / 赏金 ─────────────────────────────────────────────── + // ── Review / Bounty ───────────────────────────────────────────── bounty_review_requested: ['review', 'bounty', 'respond_required'], peer_review_request: ['review', 'swarm', 'respond_required'], supplement_request: ['supplement', 'respond_required'], - // ── 成长 / 知识 ─────────────────────────────────────────────── + // ── Growth / Knowledge ────────────────────────────────────────── evolution_circle_formed: ['evolution_circle', 'collaboration'], knowledge_update: ['knowledge'], topic_notification: ['topic', 'knowledge'], reflection_prompt: ['reflection'], - // ── 系统 ────────────────────────────────────────────────────── + // ── System ────────────────────────────────────────────────────── task_overdue: ['overdue_task', 'urgent'], }; for (const ev of hubEvents) { @@ -1991,6 +2002,8 @@ async function run() { lines: Number.isFinite(maxFiles) && maxFiles > 0 ? Math.round(maxFiles * 80) : 0, }; + const initialUserPrompt = getCurrentSessionInitialPrompt(); + // Merge into existing state to preserve last_solidify (do not wipe it). const prevState = readStateForSolidify(); prevState.last_run = { @@ -2061,12 +2074,7 @@ async function run() { const genesPreview = `\`\`\`json\n${JSON.stringify(genes.slice(0, 6), null, 2)}\n\`\`\``; const capsulesPreview = `\`\`\`json\n${JSON.stringify(capsules.slice(-3), null, 2)}\n\`\`\``; - // Get initial user prompt from current session - const initialUserPrompt = getCurrentSessionInitialPrompt(); - const reviewNote = IS_REVIEW_MODE - ? 'Review mode: before significant edits, pause and ask the user for confirmation.' - : 'Review mode: disabled.'; // Build recent evolution history summary for context injection const recentHistorySummary = (() => {