diff options
| author | Agando <roduccomm@gmail.com> | 2026-05-29 18:01:19 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-05-29 18:01:19 +0200 |
| commit | d570f86177c514ae888fb63fc0bb0da7dd4d5097 (patch) | |
| tree | bb7b8ebc43a967b40e23add551f676a67a5b493c /modules/core/module-aiassist.js | |
| parent | 09f14c97ba87bf16348d025d8cbbbe21cafb8097 (diff) | |
auto: 2026-05-29_18:01 changes
Diffstat (limited to 'modules/core/module-aiassist.js')
| -rw-r--r-- | modules/core/module-aiassist.js | 106 |
1 files changed, 103 insertions, 3 deletions
diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js index 70882a9..602ae3e 100644 --- a/modules/core/module-aiassist.js +++ b/modules/core/module-aiassist.js @@ -35,7 +35,10 @@ const now = new Date(); const dateStr = now.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); const timeStr = now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); - return `Current date: ${dateStr}\nCurrent time: ${timeStr}\n\nYou are WebX11AI, the AI assistant embedded in the WebX11 browser terminal. You have direct access to read/write files, search the web, install packages, edit modules, and run terminal commands — all via tool calls. Use tools proactively. Keep responses concise.\n\n== TOOL REFERENCE == + const optimizationActive = this.terminal?.ollamaManager?.optimizationEnabled !== false; + const CR = String.fromCharCode(10); + const optNote = CR + CR + '== TOOL EXECUTION RULES (STRICT) ==' + CR + 'STRICT RULE 1: NEVER describe what the filesystem looks like from your training data. ALWAYS call list_files() or read_file() first, then report the ACTUAL result.' + CR + 'STRICT RULE 2: If you return text without calling any tool, you have failed. Use tools for every factual question.' + CR + 'STRICT RULE 3: A response like "/bin/, /etc/, /home/" without calling list_files is ILLEGAL. Call the tool.' + CR + 'STRICT RULE 4: When calling a tool, base your answer ENTIRELY on the tool result. If result is empty, say "empty".' + CR + CR; + return `Current date: ${dateStr}\nCurrent time: ${timeStr}\n\nYou are WebX11AI, the AI assistant embedded in the WebX11 browser terminal. You have direct access to read/write files, search the web, install packages, edit modules, and run terminal commands — all via tool calls. Use tools proactively. Keep responses concise.${optNote}== TOOL REFERENCE == You have these tools available. Use them WITHOUT asking permission unless the tool description says (DESTRUCTIVE): FILESYSTEM: @@ -436,7 +439,8 @@ RULES: } // Auto-load skills relevant to the user's query (awaited, not fire-and-forget) // Skip skill loading for trivial queries (< 10 chars, no actionable keywords) - const isTrivial = userMessage.length < 10 && !/\b(search|write|install|edit|create|make|find|delete|remove|change|update|improve|add|fix|build|set|load|save|help)\b/i.test(userMessage); + const optEnabled = this.terminal?.ollamaManager?.optimizationEnabled !== false; + const isTrivial = optEnabled && userMessage.length < 10 && !/\b(search|write|install|edit|create|make|find|delete|remove|change|update|improve|add|fix|build|set|load|save|help)\b/i.test(userMessage); if (!isTrivial) { try { const skillRes = await this.runTool('load_skills', { query: userMessage }); @@ -539,6 +543,42 @@ RULES: } return null; } + _parseToolBlocksFromText(text) { + if (!text) return []; + const toolCalls = []; + // Match ```tool name(args) or ```tool\nname(args) blocks + const blockRegex = /```tool(?:\s*\n)?\s*(\w+)\s*({[^}]*})?\s*```/gi; + let match; + while ((match = blockRegex.exec(text)) !== null) { + const name = match[1]; + let args = {}; + try { + if (match[2]) args = JSON.parse(match[2]); + } catch (e) { + // If JSON parse fails, treat as raw string + args = match[2] ? { path: match[2].replace(/[{}]/g,'').trim() } : {}; + } + toolCalls.push({ + id: 'text_' + Date.now() + '_' + toolCalls.length, + type: 'function', + function: { name, arguments: args } + }); + } + // Also handle single-line format: ```tool get_status{} + const simpleRegex = /```tool\s+(\w+)\s*\{([^}]*)\}\s*```/gi; + while ((match = simpleRegex.exec(text)) !== null) { + // Avoid duplicates — skip if already caught by blockRegex + const name = match[1]; + if (!toolCalls.find(tc => tc.function.name === name)) { + toolCalls.push({ + id: 'text_' + Date.now() + '_' + toolCalls.length, + type: 'function', + function: { name, arguments: {} } + }); + } + } + return toolCalls; + } _trackToolFailure(toolName, error) { try { const key = 'webx11_tool_failures'; @@ -571,7 +611,67 @@ RULES: 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) { - const text = res.message.content || ''; + let text = res.message.content || ''; + // Fallback: parse ```tool blocks from text when model writes them instead of native tool_calls + let parsedToolCalls = this._parseToolBlocksFromText(text); + if (parsedToolCalls.length > 0) { + this.terminal.print(`🔧 Parsed ${parsedToolCalls.length} tool call(s) from text block — turn ${toolTurns}/${this.MAX_TOOL_TURNS} (${thinkTime}s)`, 'info'); + // Extract any non-tool text as pending assistant content + const cleanText = text.replace(/```tool[\s\S]*?```/g, '').trim(); + if (cleanText) messages.push({ role: 'assistant', content: cleanText }); + messages.push({ role: 'assistant', content: '(calling tool via text block)' }); + let pendingText = cleanText || null; + const destructiveTools = parsedToolCalls.filter(tc => this.isDestructive(tc.function.name)); + const safeTools = parsedToolCalls.filter(tc => !this.isDestructive(tc.function.name)); + const safeResults = await Promise.all(safeTools.map(async (tc) => { + const fn = tc.function; + const t0 = Date.now(); + const toolResult = await this.runTool(fn.name, fn.arguments); + const t1 = Math.round((Date.now() - t0) / 1000); + return { fn, toolResult, t1 }; + })); + for (const { fn, toolResult, t1 } of safeResults) { + this.terminal.print(`🔧 Tool: ${fn.name}(${typeof fn.arguments === 'string' ? fn.arguments : JSON.stringify(fn.arguments)})`, 'info'); + const obs = this._summarizeObservation(toolResult, 150); + const err = this._isToolError(toolResult); + if (err) { + this.terminal.log('WARN', 'AI', `Tool ${fn.name} failed: ${err} (${t1}s)`); + this._trackToolFailure(fn.name, err); + this.terminal.print(`📊 Result (${t1}s): ⚠️ Error: ${err}`, 'warning'); + } else { + this.terminal.log('INFO', 'AI', `Tool ${fn.name} ok (${t1}s)`); + this.terminal.print(`📊 Result (${t1}s): ${obs}`, 'info'); + } + messages.push({ role: 'tool', content: toolResult }); + } + if (destructiveTools.length > 0) { + const approved = await this.confirmActionMulti(destructiveTools); + if (approved) { + for (const tc of destructiveTools) { + const fn = tc.function; + const t0 = Date.now(); + this.terminal.print(`🔧 Tool: ${fn.name}(${typeof fn.arguments === 'string' ? fn.arguments : JSON.stringify(fn.arguments)})`, 'info'); + const toolResult = await this.runTool(fn.name, fn.arguments); + const t1 = Math.round((Date.now() - t0) / 1000); + const dObs = this._summarizeObservation(toolResult, 150); + const dErr = this._isToolError(toolResult); + if (dErr) { + this.terminal.log('WARN', 'AI', `Tool ${fn.name} failed: ${dErr} (${t1}s)`); + this._trackToolFailure(fn.name, dErr); + this.terminal.print(`📊 Result (${t1}s): ⚠️ Error: ${dErr}`, 'warning'); + } else { + this.terminal.log('INFO', 'AI', `Tool ${fn.name} ok (${t1}s)`); + this.terminal.print(`📊 Result (${t1}s): ${dObs}`, 'info'); + } + messages.push({ role: 'tool', content: toolResult }); + } + } else { + messages.push({ role: 'tool', content: `Destructive actions (${destructiveTools.map(tc => tc.function.name).join(', ')}) denied.` }); + } + } + if (pendingText) messages.push({ role: 'assistant', content: pendingText }); + continue; + } this.terminal.printAIResponse(text || '(no response)'); if (text) this.terminal.ollamaManager.fireResponseHooks(text); this.terminal.print(`💬 AI answered (turn ${toolTurns}/${this.MAX_TOOL_TURNS}, ${thinkTime}s)`, 'info'); |