godcrm/backend/services/agent-tools/misc-tools.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

224 lines
7.9 KiB
JavaScript

/**
* Miscellaneous Tool Handlers
*
* Handles: view_conversation_steps, view_step_detail, run_code, validate_code,
* run_code_loop, get_api_documentation, manage_plan, save_conversation_summary
*/
import { dbGet, dbRun, dbAll, isPostgres, safeJsonParse } from '../../database/connection.js';
import { aiLogger } from '../../utils/logger.js';
import { executeCodeTool } from '../CodeToolsService.js';
import { handleManagePlan } from '../chat/agent-execution-shared.js';
/**
* Miscellaneous tool handlers
*/
export const miscToolHandlers = {
// === CONVERSATION INTROSPECTION ===
async view_conversation_steps({ conversation_id }) {
const steps = await dbAll(
`SELECT id, content_type, role, content, tool_results, agent_id, created_at
FROM messages
WHERE conversation_id = ? AND content_type IN ('thinking', 'tool_call', 'tool_result')
ORDER BY created_at ASC`,
[conversation_id]
);
return {
success: true,
conversation_id,
total_steps: steps.length,
steps: steps.map((s, i) => {
const toolData = s.tool_results ? safeJsonParse(s.tool_results) : null;
const toolName = toolData?.tool || (s.content_type === 'tool_call' ? s.content : null);
return {
index: i + 1,
message_id: s.id,
type: s.content_type,
tool: toolName || null,
preview: s.content?.substring(0, 150) || '',
timestamp: s.created_at
};
})
};
},
async view_step_detail({ message_id }) {
const msg = await dbGet(
`SELECT id, conversation_id, content_type, role, content, tool_results,
agent_id, model_used, tokens_in, tokens_out, created_at
FROM messages WHERE id = ?`,
[message_id]
);
if (!msg) return { error: `Message ${message_id} not found` };
const toolData = msg.tool_results ? safeJsonParse(msg.tool_results) : null;
return {
success: true,
message_id: msg.id,
conversation_id: msg.conversation_id,
type: msg.content_type,
role: msg.role,
tool: toolData?.tool || null,
args: toolData?.args || null,
content: msg.content,
result: toolData?.result || null,
model: msg.model_used,
tokens: msg.tokens_in || msg.tokens_out ? { in: msg.tokens_in, out: msg.tokens_out } : null,
timestamp: msg.created_at
};
},
// === CODE EXECUTION (ADR-032) ===
async run_code(args, context) {
return executeCodeTool('run_code', args, context);
},
async validate_code(args, context) {
return executeCodeTool('validate_code', args, context);
},
async run_code_loop(args, context) {
return executeCodeTool('run_code_loop', args, context);
},
// === API DOCUMENTATION (ADR-036) ===
async get_api_documentation({ include_tools = true, include_endpoints = true, filter_tag } = {}) {
try {
// Lazy import to avoid circular dependency
const { swaggerOptions } = await import('../../swagger.config.js');
const swaggerJsdoc = (await import('swagger-jsdoc')).default;
const swaggerSpec = swaggerJsdoc(swaggerOptions);
const result = {
api_version: swaggerSpec.info?.version || '0.003.001',
base_url: '/api/v3',
auth_type: 'Bearer JWT in Authorization header'
};
// Include endpoints
if (include_endpoints) {
let paths = swaggerSpec.paths || {};
// Filter by tag if specified
if (filter_tag) {
const filteredPaths = {};
for (const [path, methods] of Object.entries(paths)) {
const filteredMethods = {};
for (const [method, def] of Object.entries(methods)) {
if (def.tags && def.tags.includes(filter_tag)) {
filteredMethods[method] = def;
}
}
if (Object.keys(filteredMethods).length > 0) {
filteredPaths[path] = filteredMethods;
}
}
paths = filteredPaths;
}
result.endpoints = paths;
result.endpoint_count = Object.keys(paths).length;
result.available_tags = (swaggerSpec.tags || []).map(t => ({ name: t.name, description: t.description }));
}
// Include agent tools — need lazy reference to AGENT_TOOLS
if (include_tools) {
const { AGENT_TOOLS } = await import('./tool-definitions.js');
result.agent_tools = AGENT_TOOLS.filter(t => t.function?.name !== 'get_api_documentation').map(tool => ({
name: tool.function?.name,
description: tool.function?.description,
parameters: tool.function?.parameters
}));
result.tool_count = result.agent_tools.length;
}
// Usage instructions
result.usage = {
response_format: '{ success: boolean, data: any, timestamp: string }',
common_operations: {
'List resources': 'GET /resource',
'Get single': 'GET /resource/:id',
'Create': 'POST /resource',
'Update': 'PUT /resource/:id',
'Delete': 'DELETE /resource/:id'
}
};
return result;
} catch (error) {
aiLogger.error({ err: error }, 'Error getting API documentation');
return { error: 'Failed to load API documentation: ' + error.message };
}
},
// === PLANNING TOOL (ADR-113) ===
// manage_plan is handled via the shared handleManagePlan() from
// agent-execution-shared.js. This toolHandlers entry delegates to it
// in validation-only mode (no conversationId).
async manage_plan(args) {
return handleManagePlan(args, null, 'unknown');
},
// === SUMMARY TOOL ===
async save_conversation_summary({ conversation_id, summary_text }, userId) {
if (!conversation_id || !summary_text) {
return { error: 'conversation_id and summary_text are required' };
}
try {
const pg = isPostgres();
// 1. Verify conversation exists
const conv = await dbGet(
pg ? 'SELECT id, agent_id FROM conversations WHERE id = $1'
: 'SELECT id, agent_id FROM conversations WHERE id = ?',
[conversation_id]
);
if (!conv) {
return { error: `Conversation ${conversation_id} not found` };
}
// 2. Save summary as a message from the Summary Agent (SUMMARY_AGENT_ID = 85572)
const SUMMARY_AGENT_ID = 85572;
const metadata = JSON.stringify({
is_summary: true,
agent_row_id: SUMMARY_AGENT_ID,
generated_at: new Date().toISOString(),
});
const messageResult = await dbRun(
pg ? `INSERT INTO messages (conversation_id, role, content, content_type, sender_type, agent_id, metadata, created_at)
VALUES ($1, 'assistant', $2, 'text', 'agent', $3, $4, NOW())
RETURNING id`
: `INSERT INTO messages (conversation_id, role, content, content_type, sender_type, agent_id, metadata, created_at)
VALUES (?, 'assistant', ?, 'text', 'agent', ?, ?, datetime('now'))`,
[conversation_id, summary_text, SUMMARY_AGENT_ID, metadata]
);
const messageId = pg ? messageResult?.rows?.[0]?.id : messageResult?.lastID;
// 3. Update conversations table — overwrite summary, save message link
await dbRun(
pg ? `UPDATE conversations SET summary = $1, summary_message_id = $2, updated_at = NOW() WHERE id = $3`
: `UPDATE conversations SET summary = ?, summary_message_id = ?, updated_at = datetime('now') WHERE id = ?`,
[summary_text, messageId, conversation_id]
);
aiLogger.info({ conversation_id, messageId }, 'Conversation summary saved');
return {
success: true,
message_id: messageId,
conversation_id,
summary_length: summary_text.length,
note: 'Summary saved to conversation. Previous summary overwritten. Old summary accessible via message history.'
};
} catch (err) {
aiLogger.error({ err, conversation_id }, 'Failed to save conversation summary');
return { error: `Failed to save summary: ${err.message}` };
}
}
};