@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.
Files changed (95) hide show
  1. package/README.md +56 -0
  2. package/dist/src/commands/agent/agent.js +62 -0
  3. package/dist/src/commands/agent/index.js +1 -1
  4. package/dist/src/commands/model/model.js +9 -5
  5. package/dist/src/commands/orchestrate/index.js +2 -2
  6. package/dist/src/commands/orchestrate/orchestrate.js +12 -627
  7. package/dist/src/commands/tasks/index.js +1 -1
  8. package/dist/src/commands/tasks/tasks.js +8 -3
  9. package/dist/src/commands/team/index.js +2 -2
  10. package/dist/src/commands/team/team.js +12 -589
  11. package/dist/src/commands/timeline/index.js +8 -0
  12. package/dist/src/commands/timeline/timeline.js +194 -0
  13. package/dist/src/commands/workspace/workspace.js +3 -3
  14. package/dist/src/commands.js +2 -6
  15. package/dist/src/components/AgentActivitySidebar.js +50 -0
  16. package/dist/src/components/AgentProgressLine.js +5 -5
  17. package/dist/src/components/ModelPicker.js +252 -441
  18. package/dist/src/components/PromptInput/Notifications.js +1 -1
  19. package/dist/src/components/PromptInput/PromptInput.js +7 -3
  20. package/dist/src/components/PromptInput/PromptInputFooter.js +10 -29
  21. package/dist/src/components/Spinner/TeammateSpinnerLine.js +20 -62
  22. package/dist/src/components/Spinner/TeammateSpinnerTree.js +16 -258
  23. package/dist/src/components/Spinner/teammateSelectHint.js +1 -1
  24. package/dist/src/components/Spinner/utils.js +3 -6
  25. package/dist/src/components/ThemeBrowser.js +120 -0
  26. package/dist/src/components/ThemePicker.js +113 -321
  27. package/dist/src/components/design-system/ThemeProvider.js +3 -0
  28. package/dist/src/components/mcp/MCPListPanel.js +138 -444
  29. package/dist/src/components/permissions/SandboxPermissionRequest.js +5 -5
  30. package/dist/src/components/teams/TeamStatus.js +7 -71
  31. package/dist/src/constants/spinnerVerbs.js +80 -180
  32. package/dist/src/context/modalStackContext.js +12 -0
  33. package/dist/src/hooks/useTextInput.js +28 -18
  34. package/dist/src/main.js +12 -0
  35. package/dist/src/screens/REPL.js +386 -320
  36. package/dist/src/services/api/errors.js +1 -1
  37. package/dist/src/services/api/openai.js +70 -22
  38. package/dist/src/services/api/withRetry.js +3 -2
  39. package/dist/src/skills/loadSkillsDir.js +1 -0
  40. package/dist/src/tools/AgentTool/UI.js +8 -8
  41. package/dist/src/tools/AgentTool/loadAgentsDir.js +9 -4
  42. package/dist/src/tools/AgentTool/providerAgents.js +71 -0
  43. package/dist/src/tools/BashTool/bashSecurity.js +1 -1
  44. package/dist/src/utils/handlePromptSubmit.js +12 -2
  45. package/dist/src/utils/processUserInput/processSlashCommand.js +9 -5
  46. package/dist/src/utils/sembleMcp/common.js +5 -0
  47. package/dist/src/utils/sembleMcp/setup.js +119 -0
  48. package/dist/src/utils/theme.js +24 -3
  49. package/dist/src/utils/themes/bootstrap.js +109 -0
  50. package/dist/src/utils/themes/builtin/opencode/_index.json +41 -0
  51. package/dist/src/utils/themes/builtin/opencode/amoled.json +49 -0
  52. package/dist/src/utils/themes/builtin/opencode/aura.json +51 -0
  53. package/dist/src/utils/themes/builtin/opencode/ayu.json +51 -0
  54. package/dist/src/utils/themes/builtin/opencode/carbonfox.json +53 -0
  55. package/dist/src/utils/themes/builtin/opencode/catppuccin-frappe.json +85 -0
  56. package/dist/src/utils/themes/builtin/opencode/catppuccin-macchiato.json +85 -0
  57. package/dist/src/utils/themes/builtin/opencode/catppuccin.json +45 -0
  58. package/dist/src/utils/themes/builtin/opencode/cobalt2.json +87 -0
  59. package/dist/src/utils/themes/builtin/opencode/cursor.json +91 -0
  60. package/dist/src/utils/themes/builtin/opencode/dracula.json +49 -0
  61. package/dist/src/utils/themes/builtin/opencode/everforest.json +89 -0
  62. package/dist/src/utils/themes/builtin/opencode/flexoki.json +86 -0
  63. package/dist/src/utils/themes/builtin/opencode/github.json +85 -0
  64. package/dist/src/utils/themes/builtin/opencode/gruvbox.json +45 -0
  65. package/dist/src/utils/themes/builtin/opencode/kanagawa.json +89 -0
  66. package/dist/src/utils/themes/builtin/opencode/lucent-orng.json +87 -0
  67. package/dist/src/utils/themes/builtin/opencode/material.json +87 -0
  68. package/dist/src/utils/themes/builtin/opencode/matrix.json +91 -0
  69. package/dist/src/utils/themes/builtin/opencode/mercury.json +86 -0
  70. package/dist/src/utils/themes/builtin/opencode/monokai.json +49 -0
  71. package/dist/src/utils/themes/builtin/opencode/nightowl.json +46 -0
  72. package/dist/src/utils/themes/builtin/opencode/nord.json +46 -0
  73. package/dist/src/utils/themes/builtin/opencode/oc-2.json +88 -0
  74. package/dist/src/utils/themes/builtin/opencode/one-dark.json +89 -0
  75. package/dist/src/utils/themes/builtin/opencode/onedarkpro.json +45 -0
  76. package/dist/src/utils/themes/builtin/opencode/opencode.json +89 -0
  77. package/dist/src/utils/themes/builtin/opencode/orng.json +87 -0
  78. package/dist/src/utils/themes/builtin/opencode/osaka-jade.json +88 -0
  79. package/dist/src/utils/themes/builtin/opencode/palenight.json +85 -0
  80. package/dist/src/utils/themes/builtin/opencode/rosepine.json +85 -0
  81. package/dist/src/utils/themes/builtin/opencode/shadesofpurple.json +51 -0
  82. package/dist/src/utils/themes/builtin/opencode/solarized.json +49 -0
  83. package/dist/src/utils/themes/builtin/opencode/synthwave84.json +87 -0
  84. package/dist/src/utils/themes/builtin/opencode/tokyonight.json +47 -0
  85. package/dist/src/utils/themes/builtin/opencode/vercel.json +90 -0
  86. package/dist/src/utils/themes/builtin/opencode/vesper.json +51 -0
  87. package/dist/src/utils/themes/builtin/opencode/zenburn.json +87 -0
  88. package/dist/src/utils/themes/index.js +4 -0
  89. package/dist/src/utils/themes/loader.js +147 -0
  90. package/dist/src/utils/themes/opencodeMapper.js +124 -0
  91. package/dist/src/utils/themes/resolver.js +66 -0
  92. package/dist/src/utils/themes/types.js +1 -0
  93. package/docs/MCP_SERVERS.md +27 -1
  94. package/docs/comandos.md +16 -4
  95. 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 = 'Not logged in · Please run /login';
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 sendRequest = async (token) => {
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 response = await fetch(url, {
1729
- method: 'POST',
1730
- headers,
1731
- body: jsonStringify(requestBody),
1732
- signal,
1733
- });
1734
- const data = await readOpenAIResponseBody(response);
1735
- if (response.status === 401) {
1736
- const refreshedToken = await maybeRecoverOpenAICompatible401(token, provider);
1737
- if (refreshedToken) {
1738
- return sendRequest(refreshedToken);
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
- if (!response.ok) {
1742
- 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' });
1743
- throw new Error(buildOpenAIErrorMessage({
1744
- status: response.status,
1745
- statusText: response.statusText,
1746
- url,
1747
- model: String(options.model),
1748
- data,
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
- if (process.env.CLAUDE_CODE_MAX_RETRIES) {
568
- return parseInt(process.env.CLAUDE_CODE_MAX_RETRIES, 10);
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: ["Remote agent launched", ' ', _jsxs(Text, { dimColor: true, children: ["\u00B7 ", internal.taskId, " \u00B7 ", internal.sessionUrl] })] }) }) });
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: ["Backgrounded agent", !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 }) })] });
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 tool use' : `${totalToolUseCount} tool uses`, formatNumber(totalTokens) + ' tokens', formatDuration(totalDurationMs)];
290
- const completionMessage = `Done (${result.join(' · ')})`;
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 = 'Initializing…';
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: ["In progress\u2026 \u00B7 ", _jsx(Text, { bold: true, children: toolUseCount }), " tool", ' ', toolUseCount === 1 ? 'use' : 'uses', tokens && ` · ${formatNumber(tokens)} tokens`, " \u00B7", ' ', _jsx(ConfigurableShortcutHint, { action: "app:toggleTranscript", context: "Global", fallback: "ctrl+o", description: "expand", parens: true })] }) });
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, " more tool", ' ', hiddenToolUseCount === 1 ? 'use' : 'uses', " ", _jsx(CtrlOToExpand, {})] })] }) });
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 }), " background agents launched", ' ', _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} agents` : 'agents', " finished"] }) : _jsxs(_Fragment, { children: ["Running ", _jsx(Text, { bold: true, children: toolUses.length }), ' ', commonType ? `${commonType} agents` : 'agents', "\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))] });
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
- // loadPluginAgents is memoized and takes no args, so it's independent.
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: 'Command contains quote characters inside a # comment which can desync quote tracking',
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,5 @@
1
+ /**
2
+ * Constants for the Semble MCP server.
3
+ */
4
+ export const SEMBLE_MCP_SERVER_NAME = 'semble';
5
+ export const SEMBLE_MCP_TOOLS = ['search', 'find_related'];
@@ -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
+ }