Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
102 lines
4.5 KiB
JavaScript
102 lines
4.5 KiB
JavaScript
/**
|
|
* Claude Code event handler builder for agent execution.
|
|
* Handles terminal command tracking and step message saving.
|
|
*/
|
|
|
|
import { dbGet, dbRun } from '../../../database/connection.js';
|
|
import { apiLogger } from '../../../utils/logger.js';
|
|
import { saveStepMessage } from './shared.js';
|
|
|
|
/**
|
|
* Build a Claude Code onEvent handler that tracks terminal commands
|
|
* and saves step messages to the conversation.
|
|
*
|
|
* @param {Object} opts
|
|
* @param {number} opts.conversationId
|
|
* @param {number} opts.agentId
|
|
* @param {string} opts.model
|
|
* @param {number} opts.userId
|
|
* @returns {{ onEvent: Function|undefined, toolUseMap: Map, getTerminalSessionId: Function }}
|
|
*/
|
|
export async function buildClaudeCodeEventHandler({ conversationId, agentId, model, userId }) {
|
|
const toolUseMap = new Map();
|
|
let agentTerminalSessionId = null;
|
|
|
|
if (conversationId) {
|
|
try {
|
|
const agentSessionTitle = `Agent #${agentId}`;
|
|
let agentSession = await dbGet(
|
|
`SELECT * FROM terminal_sessions WHERE user_id = $1 AND title = $2 AND status = $3`,
|
|
[userId, agentSessionTitle, 'active']
|
|
);
|
|
if (!agentSession) {
|
|
const { createSession } = await import('../../../services/TerminalService.js');
|
|
agentSession = await createSession(userId, agentSessionTitle);
|
|
}
|
|
agentTerminalSessionId = agentSession.id;
|
|
} catch { /* terminal not critical */ }
|
|
}
|
|
|
|
const onEvent = conversationId ? (event) => {
|
|
if (event.type === 'assistant' && event.message?.content) {
|
|
for (const block of event.message.content) {
|
|
if (block.type === 'tool_use') {
|
|
saveStepMessage(conversationId, {
|
|
content: block.name, contentType: 'tool_call', role: 'assistant',
|
|
senderType: 'agent', agentId, modelUsed: model,
|
|
toolResults: { tool: block.name, args: block.input, terminal_session_id: agentTerminalSessionId }
|
|
}).catch(() => {});
|
|
let insertPromise = null;
|
|
if (block.name === 'Bash' && block.input?.command && agentTerminalSessionId) {
|
|
insertPromise = dbRun(
|
|
`INSERT INTO terminal_commands (session_id, command, risk_level, approval_status, source, agent_name)
|
|
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
[agentTerminalSessionId, block.input.command, 'safe', 'auto', 'agent', `Agent #${agentId}`]
|
|
).then(result => {
|
|
apiLogger.debug({ cmdId: result?.lastInsertRowid, command: block.input?.command?.substring(0, 50) }, 'Terminal command inserted');
|
|
return result;
|
|
}).catch(err => {
|
|
apiLogger.warn({ err }, 'Failed to insert terminal command');
|
|
return null;
|
|
});
|
|
}
|
|
toolUseMap.set(block.id, { name: block.name, command: block.input?.command, insertPromise });
|
|
} else if (block.type === 'text' && block.text) {
|
|
saveStepMessage(conversationId, {
|
|
content: block.text, contentType: 'thinking', role: 'assistant',
|
|
senderType: 'agent', agentId, modelUsed: model
|
|
}).catch(() => {});
|
|
}
|
|
}
|
|
} else if (event.type === 'user' && event.message?.content) {
|
|
for (const block of event.message.content) {
|
|
if (block.type === 'tool_result') {
|
|
const resultContent = typeof block.content === 'string'
|
|
? block.content : JSON.stringify(block.content);
|
|
const truncated = resultContent.length > 2000 ? resultContent.substring(0, 2000) + '...' : resultContent;
|
|
saveStepMessage(conversationId, {
|
|
content: truncated, contentType: 'tool_result', role: 'tool',
|
|
senderType: 'agent', agentId,
|
|
toolResults: { tool_use_id: block.tool_use_id, content: truncated }
|
|
}).catch(() => {});
|
|
const toolInfo = toolUseMap.get(block.tool_use_id);
|
|
if (toolInfo?.insertPromise) {
|
|
const output = resultContent;
|
|
const exitCode = block.is_error ? 1 : 0;
|
|
toolInfo.insertPromise.then(result => {
|
|
const cmdId = result?.lastInsertRowid;
|
|
if (cmdId) {
|
|
dbRun(
|
|
`UPDATE terminal_commands SET output = $1, exit_code = $2, completed_at = NOW() WHERE id = $3`,
|
|
[output.substring(0, 100 * 1024), exitCode, cmdId]
|
|
).catch(err => apiLogger.warn({ err, cmdId }, 'Failed to update terminal command'));
|
|
}
|
|
}).catch(() => {});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} : undefined;
|
|
|
|
return { onEvent, toolUseMap };
|
|
}
|