From ea4d09fb5ccba12ed774fcf7c913edfa1e6a851a Mon Sep 17 00:00:00 2001 From: Agando Date: Thu, 2 Jul 2026 06:00:40 +0200 Subject: auto: 2026-07-02_06:00 changes --- modules/core/module-aiassist.js | 333 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 318 insertions(+), 15 deletions(-) (limited to 'modules/core/module-aiassist.js') diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js index 6359f17..8a0a7c8 100644 --- a/modules/core/module-aiassist.js +++ b/modules/core/module-aiassist.js @@ -22,6 +22,8 @@ this._toolDefs = null; this._toolCallLog = []; this._debugPanelEnabled = false; + this._lastRawResponse = null; + this._lastRawMessages = null; // 🔒 Guardrail Layer this._guardrailPolicy = null; this._shadowSnapshot = null; @@ -431,7 +433,7 @@ buildSystemPrompt() { type: 'function', function: { name: 'smart_context_list', - description: 'List available AI context profiles (e.g. coding, debug, creative, technical). Returns profile names and system prompts.', + description: 'List available AI context profiles (e.g. coding, debug, creative, technical, chat, system-admin, research, security). Each profile sets the AI system prompt, temperature, and behavior for a specific task type. Also switches the agent loop profile (max turns, tool set) to match. Returns profile names, descriptions, and system prompts. Use this when the user task has a clear domain (coding, debugging, research, etc.) to optimize AI behavior.', parameters: { type: 'object', properties: {}, @@ -442,11 +444,11 @@ buildSystemPrompt() { type: 'function', function: { name: 'smart_context_set', - description: "Set the active AI context profile. Changes the AI's behavior (temperature, style) for subsequent responses.", + description: "Set the active AI context profile. Changes the AI system prompt, temperature, and behavior for subsequent responses. Also switches the agent loop profile (max turns, tool set) to match. Available profiles: coding (code editing), debug (bug fixing), creative (brainstorming), technical (documentation), chat (casual), system-admin (maintenance), research (information gathering), security (threat assessment). Use this when the user task changes domain.", parameters: { type: 'object', properties: { - name: { type: 'string', description: 'Profile name (e.g. "coding", "debug", "creative", "technical", "chat")' } + name: { type: 'string', description: 'Profile name (e.g. "coding", "debug", "creative", "technical", "chat", "system-admin", "research", "security")' } }, required: ['name'] } @@ -632,6 +634,11 @@ buildSystemPrompt() { this.commands['bench-compare'] = (args) => this.benchCompareCmd(args); this.commands['debug'] = () => this.toggleDebugPanel(); this.commands['debug-ai'] = () => this.toggleDebug(); + this.commands['debug-ai-last'] = () => this._showLastRawResponse(); + this.commands['debug-ai-send'] = () => this._showLastRawRequest(); + this.commands['debug-ai-log'] = () => this._showToolCallLog(); + this.commands['archive-list'] = () => this._archiveList(); + this.commands['archive-read'] = (args) => this._archiveRead(args); this.commands['guardrail'] = (args) => this.guardrailCmd(args); this.commands['guardrail-add'] = (args) => this.guardrailCmd(['add', ...args]); this.commands['guardrail-remove'] = (args) => this.guardrailCmd(['remove', ...args]); @@ -773,9 +780,14 @@ buildSystemPrompt() { // Log the AI interaction this.terminal.log('INFO', 'AI', 'Query: ' + userMessage.substring(0, 100)); if (loopResult && loopResult.toolTurns) { - this.terminal.log('INFO', 'AI', 'Used ' + loopResult.toolTurns + ' tool(s)'); + const toolNames = this._toolCallLog.slice(-(loopResult.toolTurns || 0)).map(function(e) { return e.name; }).join(', '); + this.terminal.log('INFO', 'AI', 'Used ' + loopResult.toolTurns + ' tool(s): ' + toolNames); } } + // Auto-archive every 10 messages + if (this._messageCount > 0 && this._messageCount % 10 === 0) { + this._archiveSession('Auto-archive at message ' + this._messageCount); + } // Register session save on page unload (runs once per session) if (!this._sessionSaved) { this._sessionSaved = true; @@ -807,12 +819,13 @@ buildSystemPrompt() { if (!this._debugPanelEnabled) return; const panel = document.getElementById('debug-ai-panel'); if (!panel) return; + const activeTab = panel.getAttribute('data-tab') || 'tools'; const recent = this._toolCallLog.slice(-20); - let html = ''; + let toolHtml = ''; for (const entry of recent) { const statusClass = entry.error ? 'debug-entry-error' : 'debug-entry-ok'; const argsStr = typeof entry.args === 'string' ? entry.args.substring(0, 60) : JSON.stringify(entry.args).substring(0, 60); - html += '
' + + toolHtml += '
' + '#' + entry.turn + ' ' + '' + this._escapeHtml(entry.name) + ' ' + '' + this._escapeHtml(argsStr) + ' ' + @@ -820,14 +833,41 @@ buildSystemPrompt() { (entry.error ? ' ⚠️' : '') + '
'; } + // Raw I/O tab content + let rawHtml = ''; + if (this._lastRawResponse) { + const r = this._lastRawResponse; + const content = (r.message && r.message.content) || ''; + const hasTools = r.tool_calls && r.tool_calls.length > 0; + rawHtml += '
'; + if (r.error) rawHtml += '
⚠️ ' + this._escapeHtml(r.error) + '
'; + if (content) { + const safe = this._escapeHtml(content.length > 300 ? content.substring(0, 300) + '...' : content); + rawHtml += '
Content: ' + safe + '
'; + } + if (hasTools) { + rawHtml += '
Tools: ' + r.tool_calls.map(function(tc) { return tc.function.name; }).join(', ') + '
'; + } + if (this._lastRawMessages) { + rawHtml += '
Messages: ' + this._lastRawMessages.length + '
'; + } + rawHtml += '
'; + } else { + rawHtml = '
No AI response yet. Send a message first.
'; + } + const tabBar = '
' + + '🔧 Tools' + + '📡 Raw I/O' + + '
'; + const bodyHtml = activeTab === 'raw' ? rawHtml : toolHtml; panel.innerHTML = '
' + - '🔧 Tool Log ' + this._toolCallLog.length + ' calls' + + '🔧 AI Debug ' + this._toolCallLog.length + ' calls' + '' + '' + '' + '' + '✕' + - '
' + html; + '
' + tabBar + bodyHtml; } _isToolError(result) { if (!result || typeof result !== 'string') return null; @@ -878,6 +918,28 @@ buildSystemPrompt() { var loopDetect = loopCfg.loopDetection || { enabled: true, threshold: 6, windowMs: 30000 }; var confEsc = loopCfg.confidenceEscalation || { enabled: true, threshold: 90 }; var finalPrompt = loopCfg.finalTurnPrompt || 'This is your last tool turn. You MUST provide a final answer after this tool call. Synthesize what you know.'; + // Memory-augmented planning: inject prior loop lessons as negative constraints + try { + var fs = this.terminal.fs; + if (fs) { + var index = fs.fsReadJSON('/skills/index.json') || {}; + var lessonKeys = Object.keys(index).filter(function(k) { return k.indexOf('loop-lesson-') === 0; }); + if (lessonKeys.length > 0) { + var recentLessons = lessonKeys.sort().slice(-3); + var lessonTexts = []; + for (var li = 0; li < recentLessons.length; li++) { + var lc = fs.readFile('/skills/' + recentLessons[li] + '.md'); + if (lc) { + var excerpt = lc.substring(0, 300); + lessonTexts.push('[Lesson: ' + recentLessons[li] + '] ' + excerpt); + } + } + if (lessonTexts.length > 0) { + messages.push({ role: 'system', content: 'Prior loop lessons (read before planning):\n' + lessonTexts.join('\n---\n') + '\n\nUse these to avoid repeating past mistakes. If lessons mention tool failures, verify those tools before calling them.' }); + } + } + } + } catch (e) { /* VFS not available — skip memory-augmented planning */ } let toolTurns = 0; const _seenCalls = new Map(); const _loopWindow = loopDetect.windowMs || 30000; @@ -892,6 +954,9 @@ buildSystemPrompt() { const thinkStart = Date.now(); const res = await this.terminal.ollamaManager.chatWithTools(messages, this.getToolDefinitions()); const thinkTime = Math.round((Date.now() - thinkStart) / 1000); + // Store raw response for debug-ai-last + this._lastRawResponse = res; + this._lastRawMessages = messages.slice(); if (this._cancelled) return { finalAnswer: null, toolTurns }; if (res.error) { this.terminal.print(`⚠️ AI model error (${thinkTime}s): ${res.error}`, 'error'); return { finalAnswer: null, toolTurns }; } if (res.message && !res.tool_calls) { @@ -2068,10 +2133,26 @@ buildSystemPrompt() { return JSON.stringify({ error: `Profile '${name}' not found`, available: Object.keys(scm.contextProfiles) }); } scm.activeProfile = name; + scm.saveActiveProfile(); const profile = scm.contextProfiles[name]; // Rebuild system prompt to reflect new context profile this.systemPrompt = this.buildSystemPrompt(); - return JSON.stringify({ success: true, name, displayName: profile.name, systemPrompt: profile.systemPrompt, temperature: profile.temperature }); + // Also switch agent loop profile to match + const PROFILE_MAP = { + 'coding': 'coder', + 'debug': 'minimal', + 'creative': 'creative', + 'technical': 'research', + 'chat': 'default', + 'system-admin': 'safe', + 'research': 'research', + 'security': 'safe' + }; + const targetLoop = PROFILE_MAP[name]; + if (targetLoop && this._switchLoopProfile) { + this._switchLoopProfile(targetLoop); + } + return JSON.stringify({ success: true, name, displayName: profile.name, systemPrompt: profile.systemPrompt, temperature: profile.temperature, agentLoop: targetLoop || null }); } case 'create_mindmap': { const mindmap = this.terminal.moduleManager.modules['MindmapModule']; @@ -2796,6 +2877,121 @@ buildSystemPrompt() { } return total; } + _showToolCallLog() { + if (!this._toolCallLog || this._toolCallLog.length === 0) { + this.terminal.print('No tool calls recorded this session.', 'warning'); + return; + } + let out = '── Tool Call Log (' + this._toolCallLog.length + ' calls) ──\n'; + for (var i = 0; i < this._toolCallLog.length; i++) { + var e = this._toolCallLog[i]; + var err = e.error ? ' ⚠️' : ''; + var argsStr = typeof e.args === 'string' ? e.args.substring(0, 80) : JSON.stringify(e.args).substring(0, 80); + out += ' #' + e.turn + ' ' + e.name + '(' + argsStr + ') ' + e.duration + 's' + err + '\n'; + } + out += '── End ──'; + this.terminal.print(out, 'info'); + } + _archiveList() { + try { + const fs = this.terminal.fs; + const files = fs.ls('/memory/archive') || []; + const jsonFiles = files.filter(function(f) { return f.name && f.name.endsWith('.json'); }); + if (jsonFiles.length === 0) { + this.terminal.print('No archives found. Run ai-compress to create one.', 'warning'); + return; + } + jsonFiles.sort(function(a, b) { return (a.name || '') < (b.name || '') ? 1 : -1; }); + let out = '── Archives (' + jsonFiles.length + ') ──\n'; + for (var i = 0; i < Math.min(jsonFiles.length, 30); i++) { + var f = jsonFiles[i]; + var size = f.size ? '(' + f.size + 'b)' : ''; + out += ' ' + f.name + ' ' + size + '\n'; + } + out += '── End ──\nUse: archive-read '; + this.terminal.print(out, 'info'); + } catch (e) { + this.terminal.print('Error listing archives: ' + e.message, 'error'); + } + } + _archiveRead(args) { + var name = (args && args[0]) || ''; + if (!name) { + this.terminal.print('Usage: archive-read (e.g. 2026-06-25-14-30.json)', 'warning'); + return; + } + try { + const fs = this.terminal.fs; + var data = fs.fsReadJSON('/memory/archive/' + name); + if (!data) { + this.terminal.print('Archive not found: ' + name, 'error'); + return; + } + var out = '── Archive: ' + name + ' ──\n'; + out += 'Archived: ' + (data.archivedAt || '?') + '\n'; + out += 'Summary: ' + (data.summary || '(none)') + '\n'; + out += 'User messages: ' + (data.user?.messageCount || 0) + '\n'; + out += 'System: ' + (data.system?.modules || 0) + ' modules, ' + (data.system?.model || '?') + '\n'; + out += 'Context: ' + (data.context?.messageCount || 0) + ' msgs, ~' + (data.context?.estimatedTokens || 0) + ' tokens\n'; + out += '── End ──'; + this.terminal.print(out, 'info'); + } catch (e) { + this.terminal.print('Error reading archive: ' + e.message, 'error'); + } + } + _showLastRawResponse() { + if (!this._lastRawResponse) { + this.terminal.print('No AI response recorded yet. Send a message first.', 'warning'); + return; + } + const r = this._lastRawResponse; + var content = (r.message && r.message.content) || ''; + var hasTools = r.tool_calls && r.tool_calls.length > 0; + var toolStr = hasTools ? r.tool_calls.map(function(tc) { return tc.function.name + '(' + (tc.function.arguments || '{}') + ')'; }).join(', ') : ''; + // Render as HTML card for better readability + var html = '
' + + '
' + + '🤖 Last AI Response' + + '' + + '' + + '
' + + '
'; + if (r.error) { + html += '
⚠️ Error: ' + this._escapeHtml(r.error) + '
'; + } + if (content) { + var safe = this._escapeHtml(content.length > 1000 ? content.substring(0, 1000) + '...(' + content.length + ' chars)' : content); + html += '
Content:
' + safe + '
'; + } else if (!hasTools) { + html += '
⚠️ Empty response (no content, no tool calls)
'; + } + if (hasTools) { + html += '
Tool calls: ' + this._escapeHtml(toolStr) + '
'; + } + html += '
'; + this.terminal.printHTML(html); + } + _showLastRawRequest() { + if (!this._lastRawMessages) { + this.terminal.print('No AI request recorded yet. Send a message first.', 'warning'); + return; + } + const msgs = this._lastRawMessages; + let out = '── Last AI Request (' + msgs.length + ' messages) ──\n'; + for (let i = 0; i < msgs.length; i++) { + const m = msgs[i]; + const role = m.role || '?'; + const content = (m.content || '').substring(0, 200); + const suffix = (m.content || '').length > 200 ? '...' : ''; + out += ' [' + i + '] ' + role + ': ' + content + suffix + '\n'; + if (i > 8 && i < msgs.length - 2) { + out += ' ... (' + (msgs.length - i - 2) + ' more messages) ...\n'; + i = msgs.length - 3; + } + } + out += '── End ──'; + this.terminal.print(out, 'info'); + } async _compressContext() { if (this.context.length <= 6) return; const totalTokens = this._estimateContextTokens(); @@ -2821,10 +3017,58 @@ buildSystemPrompt() { } catch (e) { /* fallback to truncation */ } this.context = [{ role: 'system', content: compressed }, ...recentMsgs]; this.terminal.print('📦 Context compressed (' + oldMsgs.length + ' messages → 1 summary, ~' + totalTokens + ' tokens)', 'info'); + // Also archive session snapshot + this._archiveSession(compressed); + } + _archiveSession(summary) { + try { + const fs = this.terminal.fs; + fs.mkdirRecursive('/memory/archive'); + const now = new Date(); + const pad = function(n) { return String(n).padStart(2, '0'); }; + const ts = now.getFullYear() + '-' + pad(now.getMonth()+1) + '-' + pad(now.getDate()) + '-' + pad(now.getHours()) + '-' + pad(now.getMinutes()); + const archive = { + archivedAt: now.toISOString(), + summary: summary || '', + user: { + profile: this._userProfile || {}, + topics: [], + messageCount: this._messageCount + }, + system: { + modules: this.terminal?.moduleManager?.modules ? Object.keys(this.terminal.moduleManager.modules).length : 0, + model: this.terminal?.ollamaManager?.currentModel || 'unknown', + provider: this.terminal?.ollamaManager?.currentProvider?.name || 'unknown' + }, + context: { + messageCount: this.context.length, + estimatedTokens: this._estimateContextTokens() + } + }; + fs.fsWriteJSON('/memory/archive/' + ts + '.json', archive); + // Rotation: keep last 30 + try { + const files = fs.ls('/memory/archive') || []; + const jsonFiles = files.filter(function(f) { return f.name && f.name.endsWith('.json'); }); + if (jsonFiles.length > 30) { + jsonFiles.sort(function(a, b) { return (a.name || '') < (b.name || '') ? -1 : 1; }); + const toDelete = jsonFiles.slice(0, jsonFiles.length - 30); + for (var i = 0; i < toDelete.length; i++) { + fs.deleteFile('/memory/archive/' + toDelete[i].name); + } + } + } catch (e) { /* rotation best-effort */ } + } catch (e) { /* archive best-effort */ } } _manualCompress() { this._compressContext().then(() => { - this.terminal.print('Context: ' + this.context.length + ' message(s), ~' + this._estimateContextTokens() + ' estimated tokens', 'info'); + const fs = this.terminal.fs; + let archiveCount = 0; + try { + const files = fs.ls('/memory/archive') || []; + archiveCount = files.filter(function(f) { return f.name && f.name.endsWith('.json'); }).length; + } catch (e) {} + this.terminal.print('Context: ' + this.context.length + ' message(s), ~' + this._estimateContextTokens() + ' estimated tokens, ' + archiveCount + ' archive(s)', 'info'); }).catch(e => { this.terminal.print('Compression error: ' + e.message, 'error'); }); @@ -3150,11 +3394,38 @@ buildSystemPrompt() { } if (anomalies.length === 0) return; var suggestions = []; + var profileAdjusted = false; for (var ai = 0; ai < anomalies.length; ai++) { var a = anomalies[ai]; - if (a.type === 'max_turns_near_limit') suggestions.push('This task used ' + result.toolTurns + '/' + maxT + ' turns — consider increasing max-turns or splitting the task'); + if (a.type === 'max_turns_near_limit') { + suggestions.push('This task used ' + result.toolTurns + '/' + maxT + ' turns — consider increasing max-turns or splitting the task'); + // Auto-correct: bump maxTurns by +2 + if (this._activeLoopProfile && this._activeLoopProfile.loop) { + var newMax = (this._activeLoopProfile.loop.maxTurns || this.MAX_TOOL_TURNS) + 2; + this._activeLoopProfile.loop.maxTurns = newMax; + this._saveLoopProfile(this._activeLoopProfile); + suggestions.push(' → Auto-adjusted maxTurns to ' + newMax); + profileAdjusted = true; + } + } else if (a.type === 'high_token_usage') suggestions.push('High token usage (' + (a.value/1000).toFixed(0) + 'K) — consider a minimal-tools profile for simpler tasks'); - else if (a.type === 'tool_failures') suggestions.push(a.count + ' tool failures (' + a.tools.join(', ') + ') — check if modules are loaded or paths correct'); + else if (a.type === 'tool_failures') { + suggestions.push(a.count + ' tool failures (' + a.tools.join(', ') + ') — check if modules are loaded or paths correct'); + // Auto-correct: inject negative constraint into profile append + if (this._activeLoopProfile && this._activeLoopProfile.systemPrompt) { + var constraint = '[Constraint] Avoid tools that failed: ' + a.tools.join(', ') + '. Verify module state before calling these tools.'; + if (this._activeLoopProfile.systemPrompt.append) { + if (this._activeLoopProfile.systemPrompt.append.indexOf(constraint) === -1) { + this._activeLoopProfile.systemPrompt.append += '\n' + constraint; + } + } else { + this._activeLoopProfile.systemPrompt.append = constraint; + } + this._saveLoopProfile(this._activeLoopProfile); + suggestions.push(' → Added constraint to profile: avoid ' + a.tools.join(', ')); + profileAdjusted = true; + } + } else if (a.type === 'above_benchmark') suggestions.push('This run used ' + a.ratio + 'x more tokens than benchmark avg (' + a.benchmark + ') — consider profile optimization'); } var suggestion = suggestions.join('\n'); @@ -3162,15 +3433,47 @@ buildSystemPrompt() { var fs = this.terminal.fs; var index = fs.fsReadJSON('/skills/index.json') || {}; var autoSkills = Object.entries(index).filter(function(e) { return (e[1].tags || []).indexOf('auto-improvement') !== -1; }); - if (autoSkills.length < 10) { + // One-time cleanup: remove all existing loop-lesson files on first run + if (!this._loopLessonCleaned) { + this._loopLessonCleaned = true; + autoSkills.forEach(function(e) { + try { fs.unlink('/skills/' + e[0] + '.md'); } catch (ex) {} + delete index[e[0]]; + }); + autoSkills = []; + } + // Age limit: remove lessons older than 24h + var now = Date.now(); + autoSkills = autoSkills.filter(function(e) { + var created = new Date(e[1].created).getTime(); + if (now - created > 86400000) { + try { fs.unlink('/skills/' + e[0] + '.md'); } catch (ex) {} + delete index[e[0]]; + return false; + } + return true; + }); + // Dedup: skip if latest lesson has same anomaly types + var anomalyTypes = anomalies.map(function(a) { return a.type; }).sort().join(','); + var isDuplicate = autoSkills.length > 0 && autoSkills[autoSkills.length - 1][1].anomalyTypes === anomalyTypes; + // FIFO rotation: if at cap, delete oldest before creating + if (!isDuplicate && autoSkills.length >= 3) { + var oldest = autoSkills[0]; + try { fs.unlink('/skills/' + oldest[0] + '.md'); } catch (ex) {} + delete index[oldest[0]]; + autoSkills.shift(); + } + if (!isDuplicate) { var skillName = 'loop-lesson-' + Date.now(); var content = '---\nname: ' + skillName + '\ndescription: Auto-saved from agent loop anomaly detection\ntags: [auto-improvement, agent-loop, auto]\ncategory: system\n---\n## Anomalies Detected\n\n' + JSON.stringify(anomalies, null, 2) + '\n\n## Suggestions\n\n' + suggestion + '\n\n## Context\n- Profile: ' + (this._activeLoopProfile ? this._activeLoopProfile.name : 'none') + '\n- Turns: ' + result.toolTurns + '\n- Tools used: ' + toolLog.map(function(t) { return t.name; }).join(', '); fs.mkdirRecursive('/skills'); fs.writeFile('/skills/' + skillName + '.md', content); - index[skillName] = { name: skillName, description: 'Auto-saved loop lesson', tags: ['auto-improvement', 'agent-loop', 'auto'], category: 'system', created: new Date().toISOString(), updated: new Date().toISOString() }; + index[skillName] = { name: skillName, description: 'Auto-saved loop lesson', tags: ['auto-improvement', 'agent-loop', 'auto'], category: 'system', created: new Date().toISOString(), updated: new Date().toISOString(), anomalyTypes: anomalyTypes }; fs.fsWriteJSON('/skills/index.json', index); } } catch (e) { /* VFS not available */ } - this.terminal.print('💡 Self-improvement: ' + suggestion, 'info'); + var msg = '💡 Self-improvement: ' + anomalies.length + ' anomaly(ies)'; + if (profileAdjusted) msg += ' — profile auto-adjusted'; + this.terminal.print(msg, 'info'); } } -- cgit v1.2.3