@iaforged/context-code 1.1.7 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -0
- package/dist/src/commands/agent/agent.js +62 -0
- package/dist/src/commands/agent/index.js +1 -1
- package/dist/src/commands/model/model.js +9 -5
- package/dist/src/commands/orchestrate/index.js +2 -2
- package/dist/src/commands/orchestrate/orchestrate.js +12 -627
- package/dist/src/commands/tasks/index.js +1 -1
- package/dist/src/commands/tasks/tasks.js +8 -3
- package/dist/src/commands/team/index.js +2 -2
- package/dist/src/commands/team/team.js +12 -589
- package/dist/src/commands/timeline/index.js +8 -0
- package/dist/src/commands/timeline/timeline.js +194 -0
- package/dist/src/commands/workspace/workspace.js +3 -3
- package/dist/src/commands.js +2 -6
- package/dist/src/components/AgentActivitySidebar.js +50 -0
- package/dist/src/components/AgentProgressLine.js +5 -5
- package/dist/src/components/ModelPicker.js +252 -441
- package/dist/src/components/PromptInput/Notifications.js +1 -1
- package/dist/src/components/PromptInput/PromptInput.js +7 -3
- package/dist/src/components/PromptInput/PromptInputFooter.js +10 -29
- package/dist/src/components/Spinner/TeammateSpinnerLine.js +20 -62
- package/dist/src/components/Spinner/TeammateSpinnerTree.js +16 -258
- package/dist/src/components/Spinner/teammateSelectHint.js +1 -1
- package/dist/src/components/Spinner/utils.js +3 -6
- package/dist/src/components/ThemeBrowser.js +120 -0
- package/dist/src/components/ThemePicker.js +113 -321
- package/dist/src/components/design-system/ThemeProvider.js +3 -0
- package/dist/src/components/mcp/MCPListPanel.js +138 -444
- package/dist/src/components/permissions/SandboxPermissionRequest.js +5 -5
- package/dist/src/components/teams/TeamStatus.js +7 -71
- package/dist/src/constants/spinnerVerbs.js +80 -180
- package/dist/src/context/modalStackContext.js +12 -0
- package/dist/src/hooks/useTextInput.js +28 -18
- package/dist/src/main.js +12 -0
- package/dist/src/screens/REPL.js +386 -320
- package/dist/src/services/api/errors.js +1 -1
- package/dist/src/services/api/openai.js +70 -22
- package/dist/src/services/api/withRetry.js +3 -2
- package/dist/src/skills/loadSkillsDir.js +1 -0
- package/dist/src/tools/AgentTool/UI.js +8 -8
- package/dist/src/tools/AgentTool/loadAgentsDir.js +9 -4
- package/dist/src/tools/AgentTool/providerAgents.js +71 -0
- package/dist/src/tools/BashTool/bashSecurity.js +1 -1
- package/dist/src/utils/handlePromptSubmit.js +12 -2
- package/dist/src/utils/processUserInput/processSlashCommand.js +9 -5
- package/dist/src/utils/sembleMcp/common.js +5 -0
- package/dist/src/utils/sembleMcp/setup.js +119 -0
- package/dist/src/utils/theme.js +24 -3
- package/dist/src/utils/themes/bootstrap.js +109 -0
- package/dist/src/utils/themes/builtin/opencode/_index.json +41 -0
- package/dist/src/utils/themes/builtin/opencode/amoled.json +49 -0
- package/dist/src/utils/themes/builtin/opencode/aura.json +51 -0
- package/dist/src/utils/themes/builtin/opencode/ayu.json +51 -0
- package/dist/src/utils/themes/builtin/opencode/carbonfox.json +53 -0
- package/dist/src/utils/themes/builtin/opencode/catppuccin-frappe.json +85 -0
- package/dist/src/utils/themes/builtin/opencode/catppuccin-macchiato.json +85 -0
- package/dist/src/utils/themes/builtin/opencode/catppuccin.json +45 -0
- package/dist/src/utils/themes/builtin/opencode/cobalt2.json +87 -0
- package/dist/src/utils/themes/builtin/opencode/cursor.json +91 -0
- package/dist/src/utils/themes/builtin/opencode/dracula.json +49 -0
- package/dist/src/utils/themes/builtin/opencode/everforest.json +89 -0
- package/dist/src/utils/themes/builtin/opencode/flexoki.json +86 -0
- package/dist/src/utils/themes/builtin/opencode/github.json +85 -0
- package/dist/src/utils/themes/builtin/opencode/gruvbox.json +45 -0
- package/dist/src/utils/themes/builtin/opencode/kanagawa.json +89 -0
- package/dist/src/utils/themes/builtin/opencode/lucent-orng.json +87 -0
- package/dist/src/utils/themes/builtin/opencode/material.json +87 -0
- package/dist/src/utils/themes/builtin/opencode/matrix.json +91 -0
- package/dist/src/utils/themes/builtin/opencode/mercury.json +86 -0
- package/dist/src/utils/themes/builtin/opencode/monokai.json +49 -0
- package/dist/src/utils/themes/builtin/opencode/nightowl.json +46 -0
- package/dist/src/utils/themes/builtin/opencode/nord.json +46 -0
- package/dist/src/utils/themes/builtin/opencode/oc-2.json +88 -0
- package/dist/src/utils/themes/builtin/opencode/one-dark.json +89 -0
- package/dist/src/utils/themes/builtin/opencode/onedarkpro.json +45 -0
- package/dist/src/utils/themes/builtin/opencode/opencode.json +89 -0
- package/dist/src/utils/themes/builtin/opencode/orng.json +87 -0
- package/dist/src/utils/themes/builtin/opencode/osaka-jade.json +88 -0
- package/dist/src/utils/themes/builtin/opencode/palenight.json +85 -0
- package/dist/src/utils/themes/builtin/opencode/rosepine.json +85 -0
- package/dist/src/utils/themes/builtin/opencode/shadesofpurple.json +51 -0
- package/dist/src/utils/themes/builtin/opencode/solarized.json +49 -0
- package/dist/src/utils/themes/builtin/opencode/synthwave84.json +87 -0
- package/dist/src/utils/themes/builtin/opencode/tokyonight.json +47 -0
- package/dist/src/utils/themes/builtin/opencode/vercel.json +90 -0
- package/dist/src/utils/themes/builtin/opencode/vesper.json +51 -0
- package/dist/src/utils/themes/builtin/opencode/zenburn.json +87 -0
- package/dist/src/utils/themes/index.js +4 -0
- package/dist/src/utils/themes/loader.js +147 -0
- package/dist/src/utils/themes/opencodeMapper.js +124 -0
- package/dist/src/utils/themes/resolver.js +66 -0
- package/dist/src/utils/themes/types.js +1 -0
- package/docs/MCP_SERVERS.md +27 -1
- package/docs/comandos.md +16 -4
- package/package.json +1 -1
|
@@ -93,7 +93,7 @@ export function isMediaSizeErrorMessage(msg) {
|
|
|
93
93
|
isMediaSizeError(msg.errorDetails));
|
|
94
94
|
}
|
|
95
95
|
export const CREDIT_BALANCE_TOO_LOW_ERROR_MESSAGE = 'Credit balance is too low';
|
|
96
|
-
export const INVALID_API_KEY_ERROR_MESSAGE = '
|
|
96
|
+
export const INVALID_API_KEY_ERROR_MESSAGE = 'No has iniciado sesion · Ejecuta /login';
|
|
97
97
|
export const INVALID_API_KEY_ERROR_MESSAGE_EXTERNAL = 'Invalid API key · Fix external API key';
|
|
98
98
|
export const ORG_DISABLED_ERROR_MESSAGE_ENV_KEY_WITH_OAUTH = 'Your ANTHROPIC_API_KEY belongs to a disabled organization · Unset the environment variable to use your subscription instead';
|
|
99
99
|
export const ORG_DISABLED_ERROR_MESSAGE_ENV_KEY = 'Your ANTHROPIC_API_KEY belongs to a disabled organization · Update or unset the environment variable';
|
|
@@ -11,6 +11,17 @@ import { toolToAPISchema } from '../../utils/api.js';
|
|
|
11
11
|
import { jsonStringify } from '../../utils/slowOperations.js';
|
|
12
12
|
const OPENAI_EMPTY_CONTENT_MAX_ATTEMPTS = 2;
|
|
13
13
|
const OPENAI_EMPTY_CONTENT_RECOVERY_PROMPT = 'System reminder: your previous response for this turn was empty. Continue the current task without asking the user to repeat themselves. If a tool call is needed, emit the appropriate tool call now. Do not return an empty response.';
|
|
14
|
+
const OPENAI_DEFAULT_MAX_TIMEOUT_RETRIES = 10;
|
|
15
|
+
function getOpenAIMaxTimeoutRetries() {
|
|
16
|
+
const raw = process.env.CONTEXT_CODE_MAX_RETRIES;
|
|
17
|
+
if (!raw)
|
|
18
|
+
return OPENAI_DEFAULT_MAX_TIMEOUT_RETRIES;
|
|
19
|
+
const parsed = Number.parseInt(raw, 10);
|
|
20
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
21
|
+
return OPENAI_DEFAULT_MAX_TIMEOUT_RETRIES;
|
|
22
|
+
}
|
|
23
|
+
return parsed;
|
|
24
|
+
}
|
|
14
25
|
function isRecord(value) {
|
|
15
26
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
16
27
|
}
|
|
@@ -1252,6 +1263,7 @@ async function createChatCompletionResponse({ messages, systemPrompt, tools, sig
|
|
|
1252
1263
|
temperature: options.temperatureOverride,
|
|
1253
1264
|
});
|
|
1254
1265
|
}
|
|
1266
|
+
const maxTimeoutRetries = getOpenAIMaxTimeoutRetries();
|
|
1255
1267
|
const sendRequest = async (bearerToken = getOpenAICompatibleAccessToken(provider) ?? '', retryCount = 0) => {
|
|
1256
1268
|
const url = `${getOpenAIBaseUrl(provider)}/chat/completions`;
|
|
1257
1269
|
logForDebugging(`[${getOpenAICompatibleProviderLabel(provider)}] chat.completions request model=${options.model} messages=${chatMessages.length} tools=${chatTools.length} endpoint=${url}${retryCount > 0 ? ` (retry ${retryCount})` : ''}`);
|
|
@@ -1294,6 +1306,12 @@ async function createChatCompletionResponse({ messages, systemPrompt, tools, sig
|
|
|
1294
1306
|
if (err instanceof Error &&
|
|
1295
1307
|
err.name === 'AbortError' &&
|
|
1296
1308
|
!signal?.aborted) {
|
|
1309
|
+
if (retryCount < maxTimeoutRetries) {
|
|
1310
|
+
const backoffMs = 1000 * Math.pow(2, retryCount);
|
|
1311
|
+
logForDebugging(`[${getOpenAICompatibleProviderLabel(provider)}] timeout local tras ${timeoutMs}ms, reintentando en ${backoffMs}ms (intento ${retryCount + 1}/${maxTimeoutRetries})`, { level: 'warn' });
|
|
1312
|
+
await new Promise(resolve => setTimeout(resolve, backoffMs));
|
|
1313
|
+
return sendRequest(bearerToken, retryCount + 1);
|
|
1314
|
+
}
|
|
1297
1315
|
throw new Error(`La solicitud expiró tras ${timeoutMs / 1000}s. El servidor tardó demasiado en responder.`);
|
|
1298
1316
|
}
|
|
1299
1317
|
throw err;
|
|
@@ -1702,7 +1720,8 @@ async function createOpenAIResponse(params) {
|
|
|
1702
1720
|
if (options.effortValue) {
|
|
1703
1721
|
body.reasoning = { effort: options.effortValue };
|
|
1704
1722
|
}
|
|
1705
|
-
const
|
|
1723
|
+
const maxTimeoutRetries = getOpenAIMaxTimeoutRetries();
|
|
1724
|
+
const sendRequest = async (token, retryCount = 0) => {
|
|
1706
1725
|
const requestBody = isCodexOAuth ? { ...body, stream: true } : body;
|
|
1707
1726
|
let url;
|
|
1708
1727
|
let headers;
|
|
@@ -1725,30 +1744,59 @@ async function createOpenAIResponse(params) {
|
|
|
1725
1744
|
'Content-Type': 'application/json',
|
|
1726
1745
|
};
|
|
1727
1746
|
}
|
|
1728
|
-
const
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1747
|
+
const timeoutMs = 120000;
|
|
1748
|
+
const controller = new AbortController();
|
|
1749
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
1750
|
+
const onAbort = () => controller.abort();
|
|
1751
|
+
signal?.addEventListener('abort', onAbort);
|
|
1752
|
+
try {
|
|
1753
|
+
const response = await fetch(url, {
|
|
1754
|
+
method: 'POST',
|
|
1755
|
+
headers,
|
|
1756
|
+
body: jsonStringify(requestBody),
|
|
1757
|
+
signal: controller.signal,
|
|
1758
|
+
});
|
|
1759
|
+
const data = await readOpenAIResponseBody(response);
|
|
1760
|
+
if (response.status === 504 && retryCount < 1) {
|
|
1761
|
+
logForDebugging(`[${getOpenAICompatibleProviderLabel(provider)}] responses 504 Gateway Timeout, reintentando...`, { level: 'warn' });
|
|
1762
|
+
return sendRequest(token, retryCount + 1);
|
|
1763
|
+
}
|
|
1764
|
+
if (response.status === 401) {
|
|
1765
|
+
const refreshedToken = await maybeRecoverOpenAICompatible401(token, provider);
|
|
1766
|
+
if (refreshedToken) {
|
|
1767
|
+
return sendRequest(refreshedToken, retryCount);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
if (!response.ok) {
|
|
1771
|
+
logForDebugging(`[${getOpenAICompatibleProviderLabel(provider)}] responses error status=${response.status} model=${options.model} endpoint=${url} oauth=${isCodexOAuth} body=${(data.raw_error_text ?? '').replace(/\s+/g, ' ').slice(0, 500)}`, { level: 'error' });
|
|
1772
|
+
throw new Error(buildOpenAIErrorMessage({
|
|
1773
|
+
status: response.status,
|
|
1774
|
+
statusText: response.statusText,
|
|
1775
|
+
url,
|
|
1776
|
+
model: String(options.model),
|
|
1777
|
+
data,
|
|
1778
|
+
}));
|
|
1739
1779
|
}
|
|
1780
|
+
return data;
|
|
1740
1781
|
}
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1782
|
+
catch (err) {
|
|
1783
|
+
if (err instanceof Error &&
|
|
1784
|
+
err.name === 'AbortError' &&
|
|
1785
|
+
!signal?.aborted) {
|
|
1786
|
+
if (retryCount < maxTimeoutRetries) {
|
|
1787
|
+
const backoffMs = 1000 * Math.pow(2, retryCount);
|
|
1788
|
+
logForDebugging(`[${getOpenAICompatibleProviderLabel(provider)}] responses timeout local tras ${timeoutMs}ms, reintentando en ${backoffMs}ms (intento ${retryCount + 1}/${maxTimeoutRetries})`, { level: 'warn' });
|
|
1789
|
+
await new Promise(resolve => setTimeout(resolve, backoffMs));
|
|
1790
|
+
return sendRequest(token, retryCount + 1);
|
|
1791
|
+
}
|
|
1792
|
+
throw new Error(`La solicitud expiró tras ${timeoutMs / 1000}s. El servidor tardó demasiado en responder.`);
|
|
1793
|
+
}
|
|
1794
|
+
throw err;
|
|
1795
|
+
}
|
|
1796
|
+
finally {
|
|
1797
|
+
clearTimeout(timeoutId);
|
|
1798
|
+
signal?.removeEventListener('abort', onAbort);
|
|
1750
1799
|
}
|
|
1751
|
-
return data;
|
|
1752
1800
|
};
|
|
1753
1801
|
return sendRequest(bearerToken);
|
|
1754
1802
|
}
|
|
@@ -564,8 +564,9 @@ function shouldRetry(error) {
|
|
|
564
564
|
return false;
|
|
565
565
|
}
|
|
566
566
|
export function getDefaultMaxRetries() {
|
|
567
|
-
|
|
568
|
-
|
|
567
|
+
const maxRetriesEnv = process.env.CONTEXT_CODE_MAX_RETRIES;
|
|
568
|
+
if (maxRetriesEnv) {
|
|
569
|
+
return parseInt(maxRetriesEnv, 10);
|
|
569
570
|
}
|
|
570
571
|
return DEFAULT_MAX_RETRIES;
|
|
571
572
|
}
|
|
@@ -407,6 +407,7 @@ export const getSkillDirCommands = memoize(async (cwd) => {
|
|
|
407
407
|
const userSkillsDir = join(getClaudeConfigHomeDir(), 'skills');
|
|
408
408
|
const managedSkillsDir = join(getManagedFilePath(), '.context', 'skills');
|
|
409
409
|
const projectSkillsDirs = getProjectDirsUpToHome('skills', cwd, '.context');
|
|
410
|
+
const legacyProjectSkillsDirs = getProjectDirsUpToHome('skills', cwd, '.claude');
|
|
410
411
|
logForDebugging(`Loading skills from: managed=${managedSkillsDir}, user=${userSkillsDir}, project=[${projectSkillsDirs.join(', ')}]`);
|
|
411
412
|
// Load from additional directories (--add-dir)
|
|
412
413
|
const additionalDirs = getAdditionalDirectoriesForClaudeMd();
|
|
@@ -276,18 +276,18 @@ export function renderToolResultMessage(data, progressMessagesForMessage, { tool
|
|
|
276
276
|
// public schema. Narrow via the internal discriminant.
|
|
277
277
|
const internal = data;
|
|
278
278
|
if (internal.status === 'remote_launched') {
|
|
279
|
-
return _jsx(Box, { flexDirection: "column", children: _jsx(MessageResponse, { height: 1, children: _jsxs(Text, { children: ["
|
|
279
|
+
return _jsx(Box, { flexDirection: "column", children: _jsx(MessageResponse, { height: 1, children: _jsxs(Text, { children: ["Agente remoto iniciado", ' ', _jsxs(Text, { dimColor: true, children: ["\u00B7 ", internal.taskId, " \u00B7 ", internal.sessionUrl] })] }) }) });
|
|
280
280
|
}
|
|
281
281
|
if (data.status === 'async_launched') {
|
|
282
282
|
const { prompt } = data;
|
|
283
|
-
return _jsxs(Box, { flexDirection: "column", children: [_jsx(MessageResponse, { height: 1, children: _jsxs(Text, { children: ["
|
|
283
|
+
return _jsxs(Box, { flexDirection: "column", children: [_jsx(MessageResponse, { height: 1, children: _jsxs(Text, { children: ["Agente en segundo plano", !isTranscriptMode && _jsxs(Text, { dimColor: true, children: [' (', _jsxs(Byline, { children: [_jsx(KeyboardShortcutHint, { shortcut: "\u2193", action: "manage" }), prompt && _jsx(ConfigurableShortcutHint, { action: "app:toggleTranscript", context: "Global", fallback: "ctrl+o", description: "expand" })] }), ')'] })] }) }), isTranscriptMode && prompt && _jsx(MessageResponse, { children: _jsx(AgentPromptDisplay, { prompt: prompt, theme: theme }) })] });
|
|
284
284
|
}
|
|
285
285
|
if (data.status !== 'completed') {
|
|
286
286
|
return null;
|
|
287
287
|
}
|
|
288
288
|
const { agentId, totalDurationMs, totalToolUseCount, totalTokens, usage, content, prompt } = data;
|
|
289
|
-
const result = [totalToolUseCount === 1 ? '1
|
|
290
|
-
const completionMessage = `
|
|
289
|
+
const result = [totalToolUseCount === 1 ? '1 herramienta usada' : `${totalToolUseCount} herramientas usadas`, formatNumber(totalTokens) + ' tokens', formatDuration(totalDurationMs)];
|
|
290
|
+
const completionMessage = `Listo (${result.join(' · ')})`;
|
|
291
291
|
const finalAssistantMessage = createAssistantMessage({
|
|
292
292
|
content: completionMessage,
|
|
293
293
|
usage: {
|
|
@@ -319,7 +319,7 @@ export function renderToolUseTag(input) {
|
|
|
319
319
|
}
|
|
320
320
|
return _jsx(_Fragment, { children: tags });
|
|
321
321
|
}
|
|
322
|
-
const INITIALIZING_TEXT = '
|
|
322
|
+
const INITIALIZING_TEXT = 'Inicializando…';
|
|
323
323
|
export function renderToolUseProgressMessage(progressMessages, { tools, verbose, terminalSize, inProgressToolCallCount, isTranscriptMode = false }) {
|
|
324
324
|
if (!progressMessages.length) {
|
|
325
325
|
return _jsx(MessageResponse, { height: 1, children: _jsx(Text, { dimColor: true, children: INITIALIZING_TEXT }) });
|
|
@@ -349,7 +349,7 @@ export function renderToolUseProgressMessage(progressMessages, { tools, verbose,
|
|
|
349
349
|
};
|
|
350
350
|
if (shouldUseCondensedMode) {
|
|
351
351
|
const { toolUseCount, tokens } = getProgressStats();
|
|
352
|
-
return _jsx(MessageResponse, { height: 1, children: _jsxs(Text, { dimColor: true, children: ["
|
|
352
|
+
return _jsx(MessageResponse, { height: 1, children: _jsxs(Text, { dimColor: true, children: ["En progreso\u2026 \u00B7 ", _jsx(Text, { bold: true, children: toolUseCount }), " herramienta", ' ', toolUseCount === 1 ? 'usada' : 'usadas', tokens && ` · ${formatNumber(tokens)} tokens`, " \u00B7", ' ', _jsx(ConfigurableShortcutHint, { action: "app:toggleTranscript", context: "Global", fallback: "ctrl+o", description: "expand", parens: true })] }) });
|
|
353
353
|
}
|
|
354
354
|
// Process messages to group consecutive search/read operations into summaries (ants only)
|
|
355
355
|
// isAgentRunning=true since this is the progress view while the agent is still running
|
|
@@ -392,7 +392,7 @@ export function renderToolUseProgressMessage(progressMessages, { tools, verbose,
|
|
|
392
392
|
// doesn't leave a blank line. Tool call headers are single-line
|
|
393
393
|
// anyway so truncation isn't needed.
|
|
394
394
|
return _jsx(MessageComponent, { message: processed.message.data.message, lookups: subagentLookups, addMargin: false, tools: tools, commands: [], verbose: verbose, inProgressToolUseIDs: collapsedInProgressIDs, progressMessagesForMessage: [], shouldAnimate: false, shouldShowDot: false, style: "condensed", isTranscriptMode: false, isStatic: true }, processed.message.uuid);
|
|
395
|
-
})] }), hiddenToolUseCount > 0 && _jsxs(Text, { dimColor: true, children: ["+", hiddenToolUseCount, "
|
|
395
|
+
})] }), hiddenToolUseCount > 0 && _jsxs(Text, { dimColor: true, children: ["+", hiddenToolUseCount, " herramienta", hiddenToolUseCount === 1 ? '' : 's', " m\u00E1s ", _jsx(CtrlOToExpand, {})] })] }) });
|
|
396
396
|
}
|
|
397
397
|
export function renderToolUseRejectedMessage(_input, { progressMessagesForMessage, tools, verbose, isTranscriptMode }) {
|
|
398
398
|
// Get agentId from progress messages if available (agent was running before rejection)
|
|
@@ -490,7 +490,7 @@ export function renderGroupedAgentToolUse(toolUses, options) {
|
|
|
490
490
|
const commonType = allSameType && agentStats[0]?.agentType !== 'Agent' ? agentStats[0]?.agentType : null;
|
|
491
491
|
// Check if all resolved agents are async (background)
|
|
492
492
|
const allAsync = agentStats.every(stat => stat.isAsync);
|
|
493
|
-
return _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(ToolUseLoader, { shouldAnimate: shouldAnimate && anyUnresolved, isUnresolved: anyUnresolved, isError: anyError }), _jsxs(Text, { children: [allComplete ? allAsync ? _jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: toolUses.length }), "
|
|
493
|
+
return _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(ToolUseLoader, { shouldAnimate: shouldAnimate && anyUnresolved, isUnresolved: anyUnresolved, isError: anyError }), _jsxs(Text, { children: [allComplete ? allAsync ? _jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: toolUses.length }), " agentes en segundo plano iniciados", ' ', _jsx(Text, { dimColor: true, children: _jsx(KeyboardShortcutHint, { shortcut: "\u2193", action: "manage", parens: true }) })] }) : _jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: toolUses.length }), ' ', commonType ? `${commonType} agentes` : 'agentes', " finalizados"] }) : _jsxs(_Fragment, { children: ["Ejecutando ", _jsx(Text, { bold: true, children: toolUses.length }), ' ', commonType ? `${commonType} agentes` : 'agentes', "\u2026"] }), ' '] }), !allAsync && _jsx(CtrlOToExpand, {})] }), agentStats.map((stat, index) => _jsx(AgentProgressLine, { agentType: stat.agentType, description: stat.description, descriptionColor: stat.descriptionColor, taskDescription: stat.taskDescription, toolUseCount: stat.toolUseCount, tokens: stat.tokens, color: stat.color, isLast: index === agentStats.length - 1, isResolved: stat.isResolved, isError: stat.isError, isAsync: stat.isAsync, shouldAnimate: shouldAnimate, lastToolInfo: stat.lastToolInfo, hideType: allSameType, name: stat.name }, stat.id))] });
|
|
494
494
|
}
|
|
495
495
|
export function userFacingName(input) {
|
|
496
496
|
if (input?.subagent_type && input.subagent_type !== GENERAL_PURPOSE_AGENT.agentType) {
|
|
@@ -23,6 +23,7 @@ import { AGENT_COLORS, setAgentColor, } from './agentColorManager.js';
|
|
|
23
23
|
import { loadAgentMemoryPrompt } from './agentMemory.js';
|
|
24
24
|
import { checkAgentMemorySnapshot, initializeFromSnapshot, } from './agentMemorySnapshot.js';
|
|
25
25
|
import { getBuiltInAgents } from './builtInAgents.js';
|
|
26
|
+
import { loadProviderBackedAgentDefinitions } from './providerAgents.js';
|
|
26
27
|
// Zod schema for agent MCP server specs
|
|
27
28
|
const AgentMcpServerSpecSchema = lazySchema(() => z.union([
|
|
28
29
|
z.string(), // Reference by name
|
|
@@ -182,22 +183,26 @@ export const getAgentDefinitionsWithOverrides = memoize(async (cwd) => {
|
|
|
182
183
|
return agent;
|
|
183
184
|
})
|
|
184
185
|
.filter(agent => agent !== null);
|
|
185
|
-
// Kick off plugin agent loading concurrently with memory snapshot init
|
|
186
|
-
//
|
|
187
|
-
// Join both so neither becomes a floating promise if the other throws.
|
|
186
|
+
// Kick off plugin/provider agent loading concurrently with memory snapshot init.
|
|
187
|
+
// Both loaders are independent and we join to avoid floating promises.
|
|
188
188
|
let pluginAgentsPromise = loadPluginAgents();
|
|
189
|
+
let providerAgentsPromise = loadProviderBackedAgentDefinitions();
|
|
189
190
|
if (feature('AGENT_MEMORY_SNAPSHOT') && isAutoMemoryEnabled()) {
|
|
190
|
-
const [pluginAgents_] = await Promise.all([
|
|
191
|
+
const [pluginAgents_, providerAgents_] = await Promise.all([
|
|
191
192
|
pluginAgentsPromise,
|
|
193
|
+
providerAgentsPromise,
|
|
192
194
|
initializeAgentMemorySnapshots(customAgents),
|
|
193
195
|
]);
|
|
194
196
|
pluginAgentsPromise = Promise.resolve(pluginAgents_);
|
|
197
|
+
providerAgentsPromise = Promise.resolve(providerAgents_);
|
|
195
198
|
}
|
|
196
199
|
const pluginAgents = await pluginAgentsPromise;
|
|
200
|
+
const providerAgents = await providerAgentsPromise;
|
|
197
201
|
const builtInAgents = getBuiltInAgents();
|
|
198
202
|
const allAgentsList = [
|
|
199
203
|
...builtInAgents,
|
|
200
204
|
...pluginAgents,
|
|
205
|
+
...providerAgents,
|
|
201
206
|
...customAgents,
|
|
202
207
|
];
|
|
203
208
|
const activeAgents = getActiveAgentsFromList(allAgentsList);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { getProviderProfile } from '../../utils/model/providerProfiles.js';
|
|
2
|
+
import { getVisibleProvider } from '../../utils/model/providerCatalog.js';
|
|
3
|
+
import { getProviderWorkspaceById, listProviderAgents, } from '../../utils/orchestration/store/index.js';
|
|
4
|
+
const PROVIDER_AGENT_PREFIX = 'provider';
|
|
5
|
+
function sanitizeSegment(value) {
|
|
6
|
+
return value
|
|
7
|
+
.trim()
|
|
8
|
+
.toLowerCase()
|
|
9
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
10
|
+
.replace(/^-+|-+$/g, '');
|
|
11
|
+
}
|
|
12
|
+
function toAgentType(provider, name) {
|
|
13
|
+
const providerSegment = sanitizeSegment(provider) || 'provider';
|
|
14
|
+
const nameSegment = sanitizeSegment(name) || 'agent';
|
|
15
|
+
return `${PROVIDER_AGENT_PREFIX}-${providerSegment}-${nameSegment}`;
|
|
16
|
+
}
|
|
17
|
+
function toWhenToUse(params) {
|
|
18
|
+
const providerLabel = getVisibleProvider(params.provider).label;
|
|
19
|
+
const role = params.roleKind?.trim() || 'worker';
|
|
20
|
+
const orchestrator = params.isOrchestrator ? 'si' : 'no';
|
|
21
|
+
return `Agente del proveedor ${providerLabel}. Rol: ${role}. Orquestador: ${orchestrator}.`;
|
|
22
|
+
}
|
|
23
|
+
function toSystemPrompt(params) {
|
|
24
|
+
const providerLabel = getVisibleProvider(params.provider).label;
|
|
25
|
+
const role = params.roleKind?.trim() || 'worker';
|
|
26
|
+
const userPrompt = params.systemPrompt?.trim();
|
|
27
|
+
const base = [
|
|
28
|
+
`Eres un agente especializado para el proveedor ${providerLabel}.`,
|
|
29
|
+
`Rol operativo: ${role}.`,
|
|
30
|
+
'Responde de forma concreta, ejecuta tareas completas y reporta resultados verificables.',
|
|
31
|
+
].join('\n');
|
|
32
|
+
return userPrompt ? `${base}\n\n${userPrompt}` : base;
|
|
33
|
+
}
|
|
34
|
+
export async function loadProviderBackedAgentDefinitions() {
|
|
35
|
+
const records = await listProviderAgents({ isEnabled: true });
|
|
36
|
+
const providerAgents = [];
|
|
37
|
+
for (const record of records) {
|
|
38
|
+
const workspace = await getProviderWorkspaceById(record.workspaceId);
|
|
39
|
+
if (!workspace) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const provider = workspace.provider;
|
|
43
|
+
const profile = getProviderProfile(record.profileId);
|
|
44
|
+
const profileLabel = profile ? `${profile.provider}/${profile.name}` : null;
|
|
45
|
+
providerAgents.push({
|
|
46
|
+
agentType: toAgentType(provider, record.name),
|
|
47
|
+
filename: record.name,
|
|
48
|
+
baseDir: 'provider-agents',
|
|
49
|
+
source: 'projectSettings',
|
|
50
|
+
whenToUse: toWhenToUse({
|
|
51
|
+
provider,
|
|
52
|
+
roleKind: record.roleKind,
|
|
53
|
+
isOrchestrator: record.isOrchestrator,
|
|
54
|
+
}),
|
|
55
|
+
provider,
|
|
56
|
+
...(record.modelOverride ? { model: record.modelOverride } : {}),
|
|
57
|
+
getSystemPrompt: () => [
|
|
58
|
+
toSystemPrompt({
|
|
59
|
+
provider,
|
|
60
|
+
roleKind: record.roleKind,
|
|
61
|
+
systemPrompt: record.systemPrompt,
|
|
62
|
+
}),
|
|
63
|
+
profileLabel ? `Perfil asociado: ${profileLabel}.` : null,
|
|
64
|
+
`Nombre del agente: ${record.name}.`,
|
|
65
|
+
]
|
|
66
|
+
.filter(Boolean)
|
|
67
|
+
.join('\n'),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return providerAgents;
|
|
71
|
+
}
|
|
@@ -1796,7 +1796,7 @@ function validateCommentQuoteDesync(context) {
|
|
|
1796
1796
|
});
|
|
1797
1797
|
return {
|
|
1798
1798
|
behavior: 'ask',
|
|
1799
|
-
message: '
|
|
1799
|
+
message: 'El comando contiene comillas dentro de un comentario con #, lo que puede desincronizar el analisis de comillas',
|
|
1800
1800
|
};
|
|
1801
1801
|
}
|
|
1802
1802
|
// Skip to end of line (rest is comment)
|
|
@@ -16,7 +16,7 @@ function exit() {
|
|
|
16
16
|
gracefulShutdownSync(0);
|
|
17
17
|
}
|
|
18
18
|
export async function handlePromptSubmit(params) {
|
|
19
|
-
const { helpers, queryGuard, isExternalLoading = false, commands, onInputChange, setPastedContents, setToolJSX, getToolUseContext, messages, mainLoopModel, ideSelection, setUserInputOnProcessing, setAbortController, onQuery, setAppState, onBeforeQuery, canUseTool, queuedCommands, uuid, skipSlashCommands, } = params;
|
|
19
|
+
const { helpers, queryGuard, isExternalLoading = false, commands, onInputChange, setPastedContents, setToolJSX, getToolUseContext, messages, mainLoopModel, ideSelection, setUserInputOnProcessing, setAbortController, onQuery, setAppState, onBeforeQuery, canUseTool, queuedCommands, uuid, skipSlashCommands, onJumpToMessage, } = params;
|
|
20
20
|
const { setCursorOffset, clearBuffer, resetHistory } = helpers;
|
|
21
21
|
// Queue processor path: commands are pre-validated and ready to execute.
|
|
22
22
|
// Skip all input validation, reference parsing, and queuing logic.
|
|
@@ -40,6 +40,7 @@ export async function handlePromptSubmit(params) {
|
|
|
40
40
|
resetHistory,
|
|
41
41
|
canUseTool,
|
|
42
42
|
onInputChange,
|
|
43
|
+
onJumpToMessage: params.onJumpToMessage,
|
|
43
44
|
});
|
|
44
45
|
return;
|
|
45
46
|
}
|
|
@@ -131,6 +132,9 @@ export async function handlePromptSubmit(params) {
|
|
|
131
132
|
onInputChange(options.nextInput);
|
|
132
133
|
}
|
|
133
134
|
}
|
|
135
|
+
if (options?.jumpToMessageId) {
|
|
136
|
+
params.onJumpToMessage?.(options.jumpToMessageId);
|
|
137
|
+
}
|
|
134
138
|
};
|
|
135
139
|
const impl = await immediateCommand.load();
|
|
136
140
|
const jsx = await impl.call(onDone, context, commandArgs);
|
|
@@ -210,6 +214,7 @@ export async function handlePromptSubmit(params) {
|
|
|
210
214
|
resetHistory,
|
|
211
215
|
canUseTool,
|
|
212
216
|
onInputChange,
|
|
217
|
+
onJumpToMessage: params.onJumpToMessage,
|
|
213
218
|
});
|
|
214
219
|
}
|
|
215
220
|
/**
|
|
@@ -220,7 +225,7 @@ export async function handlePromptSubmit(params) {
|
|
|
220
225
|
* get `skipAttachments` to avoid duplicating turn-level context.
|
|
221
226
|
*/
|
|
222
227
|
async function executeUserInput(params) {
|
|
223
|
-
const { messages, mainLoopModel, ideSelection, querySource, queryGuard, setToolJSX, getToolUseContext, setUserInputOnProcessing, setAbortController, onQuery, setAppState, onBeforeQuery, resetHistory, canUseTool, queuedCommands, } = params;
|
|
228
|
+
const { messages, mainLoopModel, ideSelection, querySource, queryGuard, setToolJSX, getToolUseContext, setUserInputOnProcessing, setAbortController, onQuery, setAppState, onBeforeQuery, resetHistory, canUseTool, queuedCommands, onJumpToMessage, } = params;
|
|
224
229
|
// Note: paste references are already processed before calling this function
|
|
225
230
|
// (either in handlePromptSubmit before queuing, or before initial execution).
|
|
226
231
|
// Always create a fresh abort controller — queryGuard guarantees no concurrent
|
|
@@ -250,6 +255,7 @@ async function executeUserInput(params) {
|
|
|
250
255
|
let effort;
|
|
251
256
|
let nextInput;
|
|
252
257
|
let submitNextInput;
|
|
258
|
+
let jumpToMessageId;
|
|
253
259
|
// Iterate all commands uniformly. First command gets attachments +
|
|
254
260
|
// ideSelection + pastedContents, rest skip attachments to avoid
|
|
255
261
|
// duplicating turn-level context (IDE selection, todos, diffs).
|
|
@@ -320,6 +326,7 @@ async function executeUserInput(params) {
|
|
|
320
326
|
effort = result.effort;
|
|
321
327
|
nextInput = result.nextInput;
|
|
322
328
|
submitNextInput = result.submitNextInput;
|
|
329
|
+
jumpToMessageId = result.jumpToMessageId;
|
|
323
330
|
}
|
|
324
331
|
}
|
|
325
332
|
queryCheckpoint('query_process_user_input_end');
|
|
@@ -380,6 +387,9 @@ async function executeUserInput(params) {
|
|
|
380
387
|
params.onInputChange(nextInput);
|
|
381
388
|
}
|
|
382
389
|
}
|
|
390
|
+
if (jumpToMessageId) {
|
|
391
|
+
onJumpToMessage?.(jumpToMessageId);
|
|
392
|
+
}
|
|
383
393
|
}); // end runWithWorkload — ALS context naturally scoped, no finally needed
|
|
384
394
|
}
|
|
385
395
|
finally {
|
|
@@ -344,7 +344,7 @@ export async function processSlashCommand(inputString, precedingInputBlocks, ima
|
|
|
344
344
|
};
|
|
345
345
|
}
|
|
346
346
|
// Track slash command usage for feature discovery
|
|
347
|
-
const { messages: newMessages, shouldQuery: messageShouldQuery, allowedTools, model, effort, command: returnedCommand, resultText, nextInput, submitNextInput } = await getMessagesForSlashCommand(commandName, parsedArgs, setToolJSX, context, precedingInputBlocks, imageContentBlocks, isAlreadyProcessing, canUseTool, uuid);
|
|
347
|
+
const { messages: newMessages, shouldQuery: messageShouldQuery, allowedTools, model, effort, command: returnedCommand, resultText, nextInput, submitNextInput, jumpToMessageId } = await getMessagesForSlashCommand(commandName, parsedArgs, setToolJSX, context, precedingInputBlocks, imageContentBlocks, isAlreadyProcessing, canUseTool, uuid);
|
|
348
348
|
// Local slash commands that skip messages
|
|
349
349
|
if (newMessages.length === 0) {
|
|
350
350
|
const eventData = {
|
|
@@ -390,7 +390,8 @@ export async function processSlashCommand(inputString, precedingInputBlocks, ima
|
|
|
390
390
|
shouldQuery: false,
|
|
391
391
|
model,
|
|
392
392
|
nextInput,
|
|
393
|
-
submitNextInput
|
|
393
|
+
submitNextInput,
|
|
394
|
+
jumpToMessageId
|
|
394
395
|
};
|
|
395
396
|
}
|
|
396
397
|
// For invalid commands, preserve both the user message and error
|
|
@@ -455,7 +456,8 @@ export async function processSlashCommand(inputString, precedingInputBlocks, ima
|
|
|
455
456
|
effort,
|
|
456
457
|
resultText,
|
|
457
458
|
nextInput,
|
|
458
|
-
submitNextInput
|
|
459
|
+
submitNextInput,
|
|
460
|
+
jumpToMessageId
|
|
459
461
|
};
|
|
460
462
|
}
|
|
461
463
|
async function getMessagesForSlashCommand(commandName, args, setToolJSX, context, precedingInputBlocks, imageContentBlocks, _isAlreadyProcessing, canUseTool, uuid) {
|
|
@@ -507,7 +509,8 @@ async function getMessagesForSlashCommand(commandName, args, setToolJSX, context
|
|
|
507
509
|
shouldQuery: false,
|
|
508
510
|
command,
|
|
509
511
|
nextInput: options?.nextInput,
|
|
510
|
-
submitNextInput: options?.submitNextInput
|
|
512
|
+
submitNextInput: options?.submitNextInput,
|
|
513
|
+
jumpToMessageId: options?.jumpToMessageId
|
|
511
514
|
});
|
|
512
515
|
return;
|
|
513
516
|
}
|
|
@@ -541,7 +544,8 @@ async function getMessagesForSlashCommand(commandName, args, setToolJSX, context
|
|
|
541
544
|
shouldQuery: options?.shouldQuery ?? false,
|
|
542
545
|
command,
|
|
543
546
|
nextInput: options?.nextInput,
|
|
544
|
-
submitNextInput: options?.submitNextInput
|
|
547
|
+
submitNextInput: options?.submitNextInput,
|
|
548
|
+
jumpToMessageId: options?.jumpToMessageId
|
|
545
549
|
});
|
|
546
550
|
};
|
|
547
551
|
void command.load().then(mod => mod.call(onDone, {
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { spawnSync } from 'child_process';
|
|
2
|
+
import envPaths from 'env-paths';
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
4
|
+
import { dirname, join } from 'path';
|
|
5
|
+
import { buildMcpToolName } from '../../services/mcp/mcpStringUtils.js';
|
|
6
|
+
import { whichSync } from '../which.js';
|
|
7
|
+
import { SEMBLE_MCP_SERVER_NAME, SEMBLE_MCP_TOOLS } from './common.js';
|
|
8
|
+
const paths = envPaths('claude-cli');
|
|
9
|
+
const SEMBLE_PACKAGE_SPEC = 'semble[mcp]';
|
|
10
|
+
const SEMBLE_BOOTSTRAP_VERSION = 1;
|
|
11
|
+
const SEMBLE_BOOTSTRAP_MARKER = join(paths.cache, 'semble-bootstrap.json');
|
|
12
|
+
const MANAGED_UV_DIR = join(paths.data, 'uv');
|
|
13
|
+
const MANAGED_UV_PATH = join(MANAGED_UV_DIR, process.platform === 'win32' ? 'uv.exe' : 'uv');
|
|
14
|
+
function readBootstrapState() {
|
|
15
|
+
if (!existsSync(SEMBLE_BOOTSTRAP_MARKER)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(readFileSync(SEMBLE_BOOTSTRAP_MARKER, 'utf8'));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function writeBootstrapState() {
|
|
26
|
+
mkdirSync(dirname(SEMBLE_BOOTSTRAP_MARKER), { recursive: true });
|
|
27
|
+
writeFileSync(SEMBLE_BOOTSTRAP_MARKER, JSON.stringify({
|
|
28
|
+
version: SEMBLE_BOOTSTRAP_VERSION,
|
|
29
|
+
packageSpec: SEMBLE_PACKAGE_SPEC,
|
|
30
|
+
}, null, 2), 'utf8');
|
|
31
|
+
}
|
|
32
|
+
function runChecked(command, args, cwd) {
|
|
33
|
+
const result = spawnSync(command, args, {
|
|
34
|
+
cwd,
|
|
35
|
+
encoding: 'utf8',
|
|
36
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
37
|
+
});
|
|
38
|
+
if (result.status !== 0) {
|
|
39
|
+
const stderr = result.stderr?.trim() || result.stdout?.trim() || 'unknown error';
|
|
40
|
+
throw new Error(`${command} ${args.join(' ')} failed: ${stderr}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function installManagedUv() {
|
|
44
|
+
mkdirSync(MANAGED_UV_DIR, { recursive: true });
|
|
45
|
+
if (process.platform === 'win32') {
|
|
46
|
+
const powershellPath = whichSync('powershell') ?? whichSync('pwsh');
|
|
47
|
+
if (!powershellPath) {
|
|
48
|
+
throw new Error('PowerShell is required to install uv automatically on Windows');
|
|
49
|
+
}
|
|
50
|
+
runChecked(powershellPath, [
|
|
51
|
+
'-ExecutionPolicy',
|
|
52
|
+
'ByPass',
|
|
53
|
+
'-Command',
|
|
54
|
+
`$env:UV_INSTALL_DIR='${MANAGED_UV_DIR.replace(/'/g, "''")}'; $env:UV_NO_MODIFY_PATH='1'; irm https://astral.sh/uv/install.ps1 | iex`,
|
|
55
|
+
], process.cwd());
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
const hasCurl = whichSync('curl');
|
|
59
|
+
const hasWget = whichSync('wget');
|
|
60
|
+
if (!hasCurl && !hasWget) {
|
|
61
|
+
throw new Error('curl or wget is required to install uv automatically');
|
|
62
|
+
}
|
|
63
|
+
const installer = hasCurl
|
|
64
|
+
? `curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="${MANAGED_UV_DIR}" UV_NO_MODIFY_PATH=1 sh`
|
|
65
|
+
: `wget -qO- https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="${MANAGED_UV_DIR}" UV_NO_MODIFY_PATH=1 sh`;
|
|
66
|
+
runChecked('sh', ['-c', installer], process.cwd());
|
|
67
|
+
}
|
|
68
|
+
if (!existsSync(MANAGED_UV_PATH)) {
|
|
69
|
+
throw new Error(`uv installation finished but binary was not found at ${MANAGED_UV_PATH}`);
|
|
70
|
+
}
|
|
71
|
+
return MANAGED_UV_PATH;
|
|
72
|
+
}
|
|
73
|
+
function ensureUvAvailable() {
|
|
74
|
+
return (whichSync('uv') ??
|
|
75
|
+
(existsSync(MANAGED_UV_PATH) ? MANAGED_UV_PATH : installManagedUv()));
|
|
76
|
+
}
|
|
77
|
+
function ensureSembleBootstrap(uvPath, cwd) {
|
|
78
|
+
const current = readBootstrapState();
|
|
79
|
+
if (current?.version === SEMBLE_BOOTSTRAP_VERSION &&
|
|
80
|
+
current.packageSpec === SEMBLE_PACKAGE_SPEC) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Persist the tool environment so the MCP does not depend on a disposable cache.
|
|
84
|
+
runChecked(uvPath, ['tool', 'install', SEMBLE_PACKAGE_SPEC], cwd);
|
|
85
|
+
// Warm the cache once so Model2Vec downloads the local embedding model eagerly.
|
|
86
|
+
runChecked(uvPath, [
|
|
87
|
+
'tool',
|
|
88
|
+
'run',
|
|
89
|
+
'--from',
|
|
90
|
+
SEMBLE_PACKAGE_SPEC,
|
|
91
|
+
'semble',
|
|
92
|
+
'search',
|
|
93
|
+
'bootstrap',
|
|
94
|
+
cwd,
|
|
95
|
+
'--top-k',
|
|
96
|
+
'1',
|
|
97
|
+
], cwd);
|
|
98
|
+
writeBootstrapState();
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Setup the Semble MCP server as a dynamic stdio server.
|
|
102
|
+
* The server bootstraps its runtime dependencies automatically on first use.
|
|
103
|
+
*/
|
|
104
|
+
export function setupSembleMCP() {
|
|
105
|
+
const allowedTools = SEMBLE_MCP_TOOLS.map(tool => buildMcpToolName(SEMBLE_MCP_SERVER_NAME, tool));
|
|
106
|
+
const uvPath = ensureUvAvailable();
|
|
107
|
+
ensureSembleBootstrap(uvPath, process.cwd());
|
|
108
|
+
return {
|
|
109
|
+
mcpConfig: {
|
|
110
|
+
[SEMBLE_MCP_SERVER_NAME]: {
|
|
111
|
+
type: 'stdio',
|
|
112
|
+
command: uvPath,
|
|
113
|
+
args: ['tool', 'run', '--from', SEMBLE_PACKAGE_SPEC, 'semble'],
|
|
114
|
+
scope: 'dynamic',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
allowedTools,
|
|
118
|
+
};
|
|
119
|
+
}
|