feat(agents): shared runAgentTurn turn-runner
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
48
lib/ai/agent/run_turn.js
Normal file
48
lib/ai/agent/run_turn.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { writeFile, unlink } from 'fs/promises';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { tmpdir } from 'os';
|
||||||
|
import { randomUUID } from 'crypto';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { runClaudeTurn } from '../claude_cli.js';
|
||||||
|
|
||||||
|
// Absolute path to the MCP stdio server the claude child spawns.
|
||||||
|
const STDIO_PATH = fileURLToPath(new URL('../../mcp/companion-stdio.js', import.meta.url));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared agent turn-runner: builds the per-turn MCP config (selecting the tool
|
||||||
|
* registry + injecting agent/space/view), runs one claude turn, cleans up.
|
||||||
|
* SSE/persistence stay in the route. Returns runClaudeTurn's result.
|
||||||
|
*/
|
||||||
|
export async function runAgentTurn({
|
||||||
|
agent, persona, registryName, toolNames, spaceId = null, view = null,
|
||||||
|
sessionId, resume = false, userText, claudeExe = 'claude', home, onEvent
|
||||||
|
}) {
|
||||||
|
const agentActor = { kind: 'agent', id: agent.id, capabilities: agent.capabilities, scopes: agent.scopes };
|
||||||
|
const mcpConfigPath = join(tmpdir(), `void-mcp-${randomUUID()}.json`);
|
||||||
|
const mcpConfig = {
|
||||||
|
mcpServers: {
|
||||||
|
void: {
|
||||||
|
command: process.execPath,
|
||||||
|
args: [STDIO_PATH],
|
||||||
|
env: {
|
||||||
|
VOID_TOOL_REGISTRY: registryName || '',
|
||||||
|
VOID_SPACE_ID: spaceId || '',
|
||||||
|
VOID_AGENT_JSON: JSON.stringify(agentActor),
|
||||||
|
VOID_VIEW_JSON: view ? JSON.stringify(view) : '',
|
||||||
|
DATABASE_URL: process.env.DATABASE_URL || '',
|
||||||
|
OLLAMA_URL: process.env.OLLAMA_URL || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await writeFile(mcpConfigPath, JSON.stringify(mcpConfig));
|
||||||
|
try {
|
||||||
|
return await runClaudeTurn({
|
||||||
|
sessionId, resume, systemPrompt: persona, userText,
|
||||||
|
mcpConfigPath, tools: toolNames, allowedTools: toolNames,
|
||||||
|
claudeExe, home, onEvent
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
unlink(mcpConfigPath).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
32
tests/ai/agent/run_turn.test.js
Normal file
32
tests/ai/agent/run_turn.test.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { readFile } from 'fs/promises';
|
||||||
|
|
||||||
|
// Capture what runAgentTurn hands to runClaudeTurn (incl. the on-disk MCP config).
|
||||||
|
const captured = {};
|
||||||
|
vi.mock('../../../lib/ai/claude_cli.js', () => ({
|
||||||
|
runClaudeTurn: vi.fn(async (opts) => {
|
||||||
|
captured.opts = opts;
|
||||||
|
captured.cfg = JSON.parse(await readFile(opts.mcpConfigPath, 'utf8'));
|
||||||
|
return { text: 'ok', toolTrace: [], usage: null };
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
import { runAgentTurn } from '../../../lib/ai/agent/run_turn.js';
|
||||||
|
|
||||||
|
describe('runAgentTurn', () => {
|
||||||
|
it('builds the MCP config (registry + tools + agent + space) and forwards to runClaudeTurn', async () => {
|
||||||
|
const out = await runAgentTurn({
|
||||||
|
agent: { id: 'a1', slug: 'yerin', capabilities: { read: true }, scopes: {} },
|
||||||
|
persona: 'YERIN', registryName: 'security',
|
||||||
|
toolNames: ['mcp__void__audit_log'], spaceId: null,
|
||||||
|
sessionId: 'c1', userText: 'check', claudeExe: 'claude'
|
||||||
|
});
|
||||||
|
expect(out.text).toBe('ok');
|
||||||
|
expect(captured.opts.systemPrompt).toBe('YERIN');
|
||||||
|
expect(captured.opts.tools).toEqual(['mcp__void__audit_log']);
|
||||||
|
expect(captured.opts.allowedTools).toEqual(['mcp__void__audit_log']);
|
||||||
|
const env = captured.cfg.mcpServers.void.env;
|
||||||
|
expect(env.VOID_TOOL_REGISTRY).toBe('security');
|
||||||
|
expect(env.VOID_SPACE_ID).toBe('');
|
||||||
|
expect(JSON.parse(env.VOID_AGENT_JSON).id).toBe('a1');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user