Stop delete from creating disable overrides Delete only the selected agent scope Keep web and VS Code behavior aligned
487 lines
17 KiB
JavaScript
487 lines
17 KiB
JavaScript
export const registerConfigEntityRoutes = (app, dependencies) => {
|
|
const {
|
|
resolveProjectDirectory,
|
|
resolveOptionalProjectDirectory,
|
|
refreshOpenCodeAfterConfigChange,
|
|
clientReloadDelayMs,
|
|
getAgentSources,
|
|
getAgentConfig,
|
|
createAgent,
|
|
updateAgent,
|
|
deleteAgent,
|
|
getCommandSources,
|
|
createCommand,
|
|
updateCommand,
|
|
deleteCommand,
|
|
listMcpConfigs,
|
|
getMcpConfig,
|
|
createMcpConfig,
|
|
updateMcpConfig,
|
|
deleteMcpConfig,
|
|
listSnippets,
|
|
getSnippet,
|
|
createSnippet,
|
|
updateSnippet,
|
|
deleteSnippet,
|
|
expandSnippets,
|
|
} = dependencies;
|
|
|
|
const completeMcpMutation = async (res, action, name, applyChange) => {
|
|
applyChange();
|
|
|
|
try {
|
|
await refreshOpenCodeAfterConfigChange(`mcp ${action}`);
|
|
return res.json({
|
|
success: true,
|
|
requiresReload: true,
|
|
message: `MCP server "${name}" ${action}d. Reloading interface…`,
|
|
reloadDelayMs: clientReloadDelayMs,
|
|
});
|
|
} catch (error) {
|
|
console.error(`[API:MCP ${action}] Reload failed after config write:`, error);
|
|
return res.json({
|
|
success: true,
|
|
requiresReload: false,
|
|
reloadFailed: true,
|
|
message: `MCP server "${name}" ${action}d, but OpenCode reload failed.`,
|
|
warning: error.message || 'OpenCode reload failed after the MCP configuration changed',
|
|
});
|
|
}
|
|
};
|
|
|
|
app.get('/api/config/agents/:name', async (req, res) => {
|
|
try {
|
|
const agentName = req.params.name;
|
|
const { directory, error } = await resolveProjectDirectory(req);
|
|
if (!directory) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
const sources = getAgentSources(agentName, directory);
|
|
|
|
const scope = sources.md.exists
|
|
? sources.md.scope
|
|
: (sources.json.exists ? sources.json.scope : null);
|
|
|
|
res.json({
|
|
name: agentName,
|
|
sources: sources,
|
|
scope,
|
|
isBuiltIn: !sources.md.exists && !sources.json.exists
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to get agent sources:', error);
|
|
res.status(500).json({ error: 'Failed to get agent configuration metadata' });
|
|
}
|
|
});
|
|
|
|
app.get('/api/config/agents/:name/config', async (req, res) => {
|
|
try {
|
|
const agentName = req.params.name;
|
|
const { directory, error } = await resolveProjectDirectory(req);
|
|
if (!directory) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
|
|
const configInfo = getAgentConfig(agentName, directory);
|
|
res.json(configInfo);
|
|
} catch (error) {
|
|
console.error('Failed to get agent config:', error);
|
|
res.status(500).json({ error: 'Failed to get agent configuration' });
|
|
}
|
|
});
|
|
|
|
app.post('/api/config/agents/:name', async (req, res) => {
|
|
try {
|
|
const agentName = req.params.name;
|
|
const { scope, ...config } = req.body;
|
|
const { directory, error } = await resolveProjectDirectory(req);
|
|
if (!directory) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
|
|
console.log('[Server] Creating agent:', agentName);
|
|
console.log('[Server] Config received:', JSON.stringify(config, null, 2));
|
|
console.log('[Server] Scope:', scope, 'Working directory:', directory);
|
|
|
|
createAgent(agentName, config, directory, scope);
|
|
await refreshOpenCodeAfterConfigChange('agent creation', {
|
|
agentName
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
requiresReload: true,
|
|
message: `Agent ${agentName} created successfully. Reloading interface…`,
|
|
reloadDelayMs: clientReloadDelayMs,
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to create agent:', error);
|
|
res.status(500).json({ error: error.message || 'Failed to create agent' });
|
|
}
|
|
});
|
|
|
|
app.patch('/api/config/agents/:name', async (req, res) => {
|
|
try {
|
|
const agentName = req.params.name;
|
|
const updates = req.body;
|
|
const { directory, error } = await resolveProjectDirectory(req);
|
|
if (!directory) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
|
|
console.log(`[Server] Updating agent: ${agentName}`);
|
|
console.log('[Server] Updates:', JSON.stringify(updates, null, 2));
|
|
console.log('[Server] Working directory:', directory);
|
|
|
|
updateAgent(agentName, updates, directory);
|
|
await refreshOpenCodeAfterConfigChange('agent update');
|
|
|
|
console.log(`[Server] Agent ${agentName} updated successfully`);
|
|
|
|
res.json({
|
|
success: true,
|
|
requiresReload: true,
|
|
message: `Agent ${agentName} updated successfully. Reloading interface…`,
|
|
reloadDelayMs: clientReloadDelayMs,
|
|
});
|
|
} catch (error) {
|
|
console.error('[Server] Failed to update agent:', error);
|
|
console.error('[Server] Error stack:', error.stack);
|
|
res.status(500).json({ error: error.message || 'Failed to update agent' });
|
|
}
|
|
});
|
|
|
|
app.delete('/api/config/agents/:name', async (req, res) => {
|
|
try {
|
|
const agentName = req.params.name;
|
|
const { directory, error } = await resolveProjectDirectory(req);
|
|
if (!directory) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
|
|
const scope = req.body?.scope;
|
|
deleteAgent(agentName, directory, scope);
|
|
await refreshOpenCodeAfterConfigChange('agent deletion');
|
|
|
|
res.json({
|
|
success: true,
|
|
requiresReload: true,
|
|
message: `Agent ${agentName} deleted successfully. Reloading interface…`,
|
|
reloadDelayMs: clientReloadDelayMs,
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to delete agent:', error);
|
|
res.status(500).json({ error: error.message || 'Failed to delete agent' });
|
|
}
|
|
});
|
|
|
|
app.get('/api/config/mcp', async (req, res) => {
|
|
try {
|
|
const { directory, error } = await resolveOptionalProjectDirectory(req);
|
|
if (error) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
const configs = listMcpConfigs(directory);
|
|
res.json(configs);
|
|
} catch (error) {
|
|
console.error('[API:GET /api/config/mcp] Failed:', error);
|
|
res.status(500).json({ error: error.message || 'Failed to list MCP configs' });
|
|
}
|
|
});
|
|
|
|
app.get('/api/config/mcp/:name', async (req, res) => {
|
|
try {
|
|
const name = req.params.name;
|
|
const { directory, error } = await resolveOptionalProjectDirectory(req);
|
|
if (error) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
const config = getMcpConfig(name, directory);
|
|
if (!config) {
|
|
return res.status(404).json({ error: `MCP server "${name}" not found` });
|
|
}
|
|
res.json(config);
|
|
} catch (error) {
|
|
console.error('[API:GET /api/config/mcp/:name] Failed:', error);
|
|
res.status(500).json({ error: error.message || 'Failed to get MCP config' });
|
|
}
|
|
});
|
|
|
|
app.post('/api/config/mcp/:name', async (req, res) => {
|
|
try {
|
|
const name = req.params.name;
|
|
const { scope, ...config } = req.body || {};
|
|
const { directory, error } = await resolveOptionalProjectDirectory(req);
|
|
if (error) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
console.log(`[API:POST /api/config/mcp] Creating MCP server: ${name}`);
|
|
|
|
await completeMcpMutation(res, 'create', name, () => {
|
|
createMcpConfig(name, config, directory, scope);
|
|
});
|
|
} catch (error) {
|
|
console.error('[API:POST /api/config/mcp/:name] Failed:', error);
|
|
res.status(500).json({ error: error.message || 'Failed to create MCP server' });
|
|
}
|
|
});
|
|
|
|
app.patch('/api/config/mcp/:name', async (req, res) => {
|
|
try {
|
|
const name = req.params.name;
|
|
const updates = req.body;
|
|
const { directory, error } = await resolveOptionalProjectDirectory(req);
|
|
if (error) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
console.log(`[API:PATCH /api/config/mcp] Updating MCP server: ${name}`);
|
|
|
|
await completeMcpMutation(res, 'update', name, () => {
|
|
updateMcpConfig(name, updates, directory);
|
|
});
|
|
} catch (error) {
|
|
console.error('[API:PATCH /api/config/mcp/:name] Failed:', error);
|
|
if (error?.message === `MCP server "${req.params.name}" not found`) {
|
|
return res.status(404).json({ error: error.message });
|
|
}
|
|
res.status(500).json({ error: error.message || 'Failed to update MCP server' });
|
|
}
|
|
});
|
|
|
|
app.delete('/api/config/mcp/:name', async (req, res) => {
|
|
try {
|
|
const name = req.params.name;
|
|
const { directory, error } = await resolveOptionalProjectDirectory(req);
|
|
if (error) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
console.log(`[API:DELETE /api/config/mcp] Deleting MCP server: ${name}`);
|
|
|
|
await completeMcpMutation(res, 'delete', name, () => {
|
|
deleteMcpConfig(name, directory);
|
|
});
|
|
} catch (error) {
|
|
console.error('[API:DELETE /api/config/mcp/:name] Failed:', error);
|
|
res.status(500).json({ error: error.message || 'Failed to delete MCP server' });
|
|
}
|
|
});
|
|
|
|
app.get('/api/config/commands/:name', async (req, res) => {
|
|
try {
|
|
const commandName = req.params.name;
|
|
const { directory, error } = await resolveProjectDirectory(req);
|
|
if (!directory) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
const sources = getCommandSources(commandName, directory);
|
|
|
|
const scope = sources.md.exists
|
|
? sources.md.scope
|
|
: (sources.json.exists ? sources.json.scope : null);
|
|
|
|
res.json({
|
|
name: commandName,
|
|
sources: sources,
|
|
scope,
|
|
isBuiltIn: !sources.md.exists && !sources.json.exists
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to get command sources:', error);
|
|
res.status(500).json({ error: 'Failed to get command configuration metadata' });
|
|
}
|
|
});
|
|
|
|
app.post('/api/config/commands/:name', async (req, res) => {
|
|
try {
|
|
const commandName = req.params.name;
|
|
const { scope, ...config } = req.body;
|
|
const { directory, error } = await resolveProjectDirectory(req);
|
|
if (!directory) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
|
|
console.log('[Server] Creating command:', commandName);
|
|
console.log('[Server] Config received:', JSON.stringify(config, null, 2));
|
|
console.log('[Server] Scope:', scope, 'Working directory:', directory);
|
|
|
|
createCommand(commandName, config, directory, scope);
|
|
await refreshOpenCodeAfterConfigChange('command creation', {
|
|
commandName
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
requiresReload: true,
|
|
message: `Command ${commandName} created successfully. Reloading interface…`,
|
|
reloadDelayMs: clientReloadDelayMs,
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to create command:', error);
|
|
res.status(500).json({ error: error.message || 'Failed to create command' });
|
|
}
|
|
});
|
|
|
|
app.patch('/api/config/commands/:name', async (req, res) => {
|
|
try {
|
|
const commandName = req.params.name;
|
|
const updates = req.body;
|
|
const { directory, error } = await resolveProjectDirectory(req);
|
|
if (!directory) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
|
|
console.log(`[Server] Updating command: ${commandName}`);
|
|
console.log('[Server] Updates:', JSON.stringify(updates, null, 2));
|
|
console.log('[Server] Working directory:', directory);
|
|
|
|
updateCommand(commandName, updates, directory);
|
|
await refreshOpenCodeAfterConfigChange('command update');
|
|
|
|
console.log(`[Server] Command ${commandName} updated successfully`);
|
|
|
|
res.json({
|
|
success: true,
|
|
requiresReload: true,
|
|
message: `Command ${commandName} updated successfully. Reloading interface…`,
|
|
reloadDelayMs: clientReloadDelayMs,
|
|
});
|
|
} catch (error) {
|
|
console.error('[Server] Failed to update command:', error);
|
|
console.error('[Server] Error stack:', error.stack);
|
|
res.status(500).json({ error: error.message || 'Failed to update command' });
|
|
}
|
|
});
|
|
|
|
app.delete('/api/config/commands/:name', async (req, res) => {
|
|
try {
|
|
const commandName = req.params.name;
|
|
const { directory, error } = await resolveProjectDirectory(req);
|
|
if (!directory) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
|
|
deleteCommand(commandName, directory);
|
|
await refreshOpenCodeAfterConfigChange('command deletion');
|
|
|
|
res.json({
|
|
success: true,
|
|
requiresReload: true,
|
|
message: `Command ${commandName} deleted successfully. Reloading interface…`,
|
|
reloadDelayMs: clientReloadDelayMs,
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to delete command:', error);
|
|
res.status(500).json({ error: error.message || 'Failed to delete command' });
|
|
}
|
|
});
|
|
|
|
app.get('/api/config/snippets', async (req, res) => {
|
|
try {
|
|
const { directory, error } = await resolveOptionalProjectDirectory(req);
|
|
if (error) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
res.json(listSnippets(directory));
|
|
} catch (error) {
|
|
console.error('[API:GET /api/config/snippets] Failed:', error);
|
|
res.status(500).json({ error: error.message || 'Failed to list snippets' });
|
|
}
|
|
});
|
|
|
|
app.post('/api/config/snippets/expand', async (req, res) => {
|
|
try {
|
|
const { directory, error } = await resolveOptionalProjectDirectory(req);
|
|
if (error) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
res.json({ text: expandSnippets(req.body?.text ?? '', directory) });
|
|
} catch (error) {
|
|
console.error('[API:POST /api/config/snippets/expand] Failed:', error);
|
|
res.status(500).json({ error: error.message || 'Failed to expand snippets' });
|
|
}
|
|
});
|
|
|
|
app.get('/api/config/snippets/:name', async (req, res) => {
|
|
try {
|
|
const name = req.params.name;
|
|
const { directory, error } = await resolveOptionalProjectDirectory(req);
|
|
if (error) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
const snippet = getSnippet(name, directory);
|
|
if (!snippet) {
|
|
return res.status(404).json({ error: `Snippet "${name}" not found` });
|
|
}
|
|
res.json(snippet);
|
|
} catch (error) {
|
|
console.error('[API:GET /api/config/snippets/:name] Failed:', error);
|
|
if (error.message?.includes('Snippet name')) {
|
|
return res.status(400).json({ error: error.message });
|
|
}
|
|
res.status(500).json({ error: error.message || 'Failed to get snippet' });
|
|
}
|
|
});
|
|
|
|
app.post('/api/config/snippets/:name', async (req, res) => {
|
|
try {
|
|
const name = req.params.name;
|
|
const { directory, error } = await resolveOptionalProjectDirectory(req);
|
|
if (error) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
const snippet = createSnippet(name, req.body || {}, directory, req.body?.scope || 'global');
|
|
res.json({ success: true, snippet });
|
|
} catch (error) {
|
|
console.error('[API:POST /api/config/snippets/:name] Failed:', error);
|
|
if (error.message?.includes('already exists')) {
|
|
return res.status(409).json({ error: error.message });
|
|
}
|
|
if (error.message?.includes('Snippet name') || error.message?.includes('Project directory')) {
|
|
return res.status(400).json({ error: error.message });
|
|
}
|
|
res.status(500).json({ error: error.message || 'Failed to create snippet' });
|
|
}
|
|
});
|
|
|
|
app.patch('/api/config/snippets/:name', async (req, res) => {
|
|
try {
|
|
const name = req.params.name;
|
|
const { directory, error } = await resolveOptionalProjectDirectory(req);
|
|
if (error) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
res.json({ success: true, snippet: updateSnippet(name, req.body || {}, directory) });
|
|
} catch (error) {
|
|
console.error('[API:PATCH /api/config/snippets/:name] Failed:', error);
|
|
if (error.message?.includes('not found')) {
|
|
return res.status(404).json({ error: error.message });
|
|
}
|
|
if (error.message?.includes('Snippet name')) {
|
|
return res.status(400).json({ error: error.message });
|
|
}
|
|
res.status(500).json({ error: error.message || 'Failed to update snippet' });
|
|
}
|
|
});
|
|
|
|
app.delete('/api/config/snippets/:name', async (req, res) => {
|
|
try {
|
|
const name = req.params.name;
|
|
const { directory, error } = await resolveOptionalProjectDirectory(req);
|
|
if (error) {
|
|
return res.status(400).json({ error });
|
|
}
|
|
deleteSnippet(name, directory);
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
console.error('[API:DELETE /api/config/snippets/:name] Failed:', error);
|
|
if (error.message?.includes('not found')) {
|
|
return res.status(404).json({ error: error.message });
|
|
}
|
|
if (error.message?.includes('Snippet name')) {
|
|
return res.status(400).json({ error: error.message });
|
|
}
|
|
res.status(500).json({ error: error.message || 'Failed to delete snippet' });
|
|
}
|
|
});
|
|
};
|