diff options
| author | Agando <roduccomm@gmail.com> | 2026-05-30 06:00:47 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-05-30 06:00:47 +0200 |
| commit | e3fb2d434c4f7317944b7c77781599528d766a39 (patch) | |
| tree | 8f9f9c94b19d46f42f8086adb6b68ee07dfce5c6 /modules/core/module-aiassist.js | |
| parent | d570f86177c514ae888fb63fc0bb0da7dd4d5097 (diff) | |
auto: 2026-05-30_06:00 changes
Diffstat (limited to 'modules/core/module-aiassist.js')
| -rw-r--r-- | modules/core/module-aiassist.js | 113 |
1 files changed, 16 insertions, 97 deletions
diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js index 602ae3e..f6fadb5 100644 --- a/modules/core/module-aiassist.js +++ b/modules/core/module-aiassist.js @@ -543,42 +543,6 @@ 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'; @@ -611,67 +575,7 @@ 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) { - 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; - } + const text = res.message.content || ''; 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'); @@ -734,6 +638,21 @@ RULES: }); } } +// Validator: check safe tool results for consistency + if (safeResults.length > 0 && pendingText) { + const om = this.terminal.ollamaManager; + if (om && om.validatorEnabled && om.validatorModel) { + let anyInvalid = false; + for (const { fn, toolResult } of safeResults) { + const valid = await om.validateToolResult(fn.name, fn.arguments, toolResult, pendingText || ''); + if (!valid && !this._cancelled) { + anyInvalid = true; + this.terminal.print('🔍 Validator: ' + fn.name + ' result — AI note may not match (logged)', 'info'); + // Just log it — don't force retry (cloud models get 400 on retry) + } + } + } + } if (pendingText) { messages.push({ role: 'assistant', content: pendingText }); } |