@iaforged/context-code 1.1.5 → 1.1.9

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.
@@ -121,11 +121,15 @@ function PromptInput({ debug, ideSelection, toolPermissionContext, setToolPermis
121
121
  // Track the last input value set via internal handlers so we can detect
122
122
  // external input changes (e.g. speech-to-text injection) and move cursor to end.
123
123
  const lastInternalInputRef = React.useRef(input);
124
- if (input !== lastInternalInputRef.current) {
125
- // Input changed externally (not through any internal handler) — move cursor to end
124
+ useEffect(() => {
125
+ if (input === lastInternalInputRef.current) {
126
+ return;
127
+ }
128
+ // Input changed externally (not through any internal handler) — move cursor to end.
129
+ // Keep this in an effect to avoid state updates during render.
126
130
  setCursorOffset(input.length);
127
131
  lastInternalInputRef.current = input;
128
- }
132
+ }, [input]);
129
133
  // Wrap onInputChange to track internal changes before they trigger re-render
130
134
  const trackAndSetInput = React.useCallback((value) => {
131
135
  lastInternalInputRef.current = value;
@@ -34,7 +34,12 @@ const LANGUAGE_NAME_TO_CODE = {
34
34
  export function normalizeLanguageForSTT(language) {
35
35
  if (!language)
36
36
  return { code: DEFAULT_STT_LANGUAGE };
37
- const lower = language.toLowerCase().trim();
37
+ // Normaliza eliminando tildes/diacriticos para que "Español" -> "espanol".
38
+ const lower = language
39
+ .toLowerCase()
40
+ .normalize('NFD')
41
+ .replace(/[\u0300-\u036f]/g, '')
42
+ .trim();
38
43
  if (!lower)
39
44
  return { code: DEFAULT_STT_LANGUAGE };
40
45
  if (lower === 'auto')
@@ -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
  }
@@ -34,7 +34,11 @@ function getConfiguredExecutable() {
34
34
  if (stored?.trim()) {
35
35
  return stored.trim();
36
36
  }
37
- return process.platform === 'win32' ? 'whisper-cli.exe' : 'whisper-cli';
37
+ // Fallback al nombre por defecto en PATH segun plataforma. En macOS/Linux
38
+ // probamos primero `whisper-cli` (releases) y luego `whisper-cpp` (Homebrew).
39
+ if (process.platform === 'win32')
40
+ return 'whisper-cli.exe';
41
+ return 'whisper-cli';
38
42
  }
39
43
  function getConfiguredModel() {
40
44
  const model = process.env.CONTEXT_CODE_DICTATION_MODEL ??
@@ -207,7 +211,7 @@ async function findFileRecursive(rootDir, predicate) {
207
211
  async function findInstalledExecutable(installDir) {
208
212
  const candidateNames = process.platform === 'win32'
209
213
  ? ['whisper-cli.exe', 'main.exe']
210
- : ['whisper-cli', 'main'];
214
+ : ['whisper-cli', 'whisper-cpp', 'main'];
211
215
  return findFileRecursive(installDir, path => candidateNames.some(name => path.toLowerCase().endsWith(name.toLowerCase())));
212
216
  }
213
217
  function persistInstalledDictationConfig(config) {
@@ -280,7 +284,146 @@ export async function getLocalDictationStatus() {
280
284
  }
281
285
  return lines.join('\n');
282
286
  }
287
+ async function isCommandAvailable(command) {
288
+ const probe = process.platform === 'win32' ? 'where' : 'which';
289
+ try {
290
+ const result = await execa(probe, [command], {
291
+ reject: false,
292
+ windowsHide: true,
293
+ timeout: 5_000,
294
+ });
295
+ return result.exitCode === 0;
296
+ }
297
+ catch {
298
+ return false;
299
+ }
300
+ }
301
+ async function getBrewPrefix() {
302
+ try {
303
+ const result = await execa('brew', ['--prefix'], {
304
+ reject: false,
305
+ windowsHide: true,
306
+ timeout: 10_000,
307
+ });
308
+ if (result.exitCode === 0) {
309
+ const prefix = result.stdout.trim();
310
+ return prefix || null;
311
+ }
312
+ }
313
+ catch {
314
+ // ignore
315
+ }
316
+ return null;
317
+ }
318
+ async function installViaHomebrew(modelName) {
319
+ const normalizedModelName = normalizeModelName(modelName);
320
+ const modelFileName = buildModelFileName(normalizedModelName);
321
+ const installDir = getInstallRoot();
322
+ // Instala (o reinstala/actualiza si ya estaba) whisper-cpp con brew.
323
+ const brewResult = await execa('brew', ['install', 'whisper-cpp'], {
324
+ reject: false,
325
+ windowsHide: true,
326
+ timeout: 600_000,
327
+ });
328
+ if (brewResult.exitCode !== 0) {
329
+ throw new Error(`brew install whisper-cpp fallo:\n${brewResult.stderr || brewResult.stdout || 'error desconocido'}`);
330
+ }
331
+ // Homebrew instala el binario como `whisper-cli` (igual que los releases),
332
+ // pero algunas versiones antiguas usaban `whisper-cpp`. Probamos ambos.
333
+ const brewPrefix = await getBrewPrefix();
334
+ const binNames = ['whisper-cli', 'whisper-cpp'];
335
+ const candidatePaths = [];
336
+ for (const name of binNames) {
337
+ if (brewPrefix)
338
+ candidatePaths.push(join(brewPrefix, 'bin', name));
339
+ candidatePaths.push(`/opt/homebrew/bin/${name}`);
340
+ candidatePaths.push(`/usr/local/bin/${name}`);
341
+ }
342
+ let executablePath = candidatePaths.find(fileExists) ?? null;
343
+ if (!executablePath) {
344
+ // Fallback: resolver via `which` para cualquiera de los nombres.
345
+ for (const name of binNames) {
346
+ const which = await execa('which', [name], {
347
+ reject: false,
348
+ windowsHide: true,
349
+ timeout: 5_000,
350
+ });
351
+ if (which.exitCode === 0 && which.stdout.trim()) {
352
+ executablePath = which.stdout.trim();
353
+ break;
354
+ }
355
+ }
356
+ }
357
+ if (!executablePath) {
358
+ throw new Error('brew instalo whisper-cpp pero no encontre el binario en el PATH.');
359
+ }
360
+ // Descarga el modelo GGML (Homebrew no lo trae) en nuestra carpeta privada.
361
+ const modelDir = join(installDir, 'models');
362
+ const modelPath = join(modelDir, modelFileName);
363
+ await mkdir(modelDir, { recursive: true });
364
+ if (!fileExists(modelPath)) {
365
+ await writeFile(modelPath, await fetchBuffer(`${WHISPER_MODEL_BASE_URL}/${modelFileName}?download=1`, `modelo ${modelFileName}`));
366
+ }
367
+ // Intenta resolver la version instalada para guardarla como releaseTag.
368
+ let releaseTag = 'homebrew';
369
+ try {
370
+ const versionResult = await execa(executablePath, ['--version'], {
371
+ reject: false,
372
+ windowsHide: true,
373
+ timeout: 10_000,
374
+ });
375
+ const text = `${versionResult.stdout}\n${versionResult.stderr}`;
376
+ const match = text.match(/v?\d+\.\d+(?:\.\d+)?/);
377
+ if (match)
378
+ releaseTag = `homebrew-${match[0]}`;
379
+ }
380
+ catch {
381
+ // ignore
382
+ }
383
+ persistInstalledDictationConfig({
384
+ executablePath,
385
+ installDir,
386
+ installedAt: new Date().toISOString(),
387
+ modelName: normalizedModelName,
388
+ modelPath,
389
+ releaseTag,
390
+ });
391
+ logForDebugging(`[dictation] Instalacion via Homebrew: ${executablePath}, modelo en ${modelPath}`);
392
+ return { executablePath, installDir, modelPath, releaseTag };
393
+ }
394
+ function getManualInstallInstructions() {
395
+ if (process.platform === 'darwin') {
396
+ return [
397
+ 'En macOS no hay binarios precompilados en los releases oficiales de whisper.cpp.',
398
+ 'Opciones para instalarlo manualmente:',
399
+ '1) Instala Homebrew (https://brew.sh) y luego ejecuta `brew install whisper-cpp`.',
400
+ '2) Compila desde fuente: clona https://github.com/ggml-org/whisper.cpp y',
401
+ ' ejecuta `cmake -B build && cmake --build build -j --config Release`.',
402
+ 'Despues apunta el backend con la variable CONTEXT_CODE_DICTATION_EXECUTABLE',
403
+ 'y el modelo con CONTEXT_CODE_DICTATION_MODEL, o vuelve a ejecutar /dictar install.',
404
+ ].join('\n');
405
+ }
406
+ if (process.platform === 'linux') {
407
+ return [
408
+ 'En Linux no hay binarios precompilados en los releases oficiales de whisper.cpp.',
409
+ 'Compila desde fuente:',
410
+ ' git clone https://github.com/ggml-org/whisper.cpp && cd whisper.cpp',
411
+ ' cmake -B build && cmake --build build -j --config Release',
412
+ 'Despues exporta CONTEXT_CODE_DICTATION_EXECUTABLE y CONTEXT_CODE_DICTATION_MODEL,',
413
+ 'o vuelve a ejecutar /dictar install una vez tengas el binario en el PATH.',
414
+ ].join('\n');
415
+ }
416
+ return 'No encontre un binario compatible para tu plataforma.';
417
+ }
283
418
  export async function installLocalDictation(modelName) {
419
+ // En macOS los releases oficiales solo publican binarios para Windows e iOS,
420
+ // asi que usamos Homebrew como camino preferido.
421
+ if (process.platform === 'darwin') {
422
+ if (await isCommandAvailable('brew')) {
423
+ return installViaHomebrew(modelName);
424
+ }
425
+ throw new Error(`No se encontro Homebrew en el sistema.\n${getManualInstallInstructions()}`);
426
+ }
284
427
  const normalizedModelName = normalizeModelName(modelName);
285
428
  const modelFileName = buildModelFileName(normalizedModelName);
286
429
  const installDir = getInstallRoot();
@@ -289,7 +432,7 @@ export async function installLocalDictation(modelName) {
289
432
  const release = await fetchLatestRelease();
290
433
  const asset = pickReleaseAsset(release.assets);
291
434
  if (!asset) {
292
- throw new Error(`No encontre un binario compatible de whisper.cpp para ${process.platform}/${process.arch}.`);
435
+ throw new Error(`No encontre un binario compatible de whisper.cpp para ${process.platform}/${process.arch}.\n${getManualInstallInstructions()}`);
293
436
  }
294
437
  const archivePath = join(tempDir, asset.name);
295
438
  const modelPath = join(installDir, 'models', modelFileName);
@@ -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
+ }
@@ -552,6 +552,8 @@ export function modelDisplayString(model) {
552
552
  }
553
553
  // @[MODEL LAUNCH]: Add a marketing name mapping for the new model below.
554
554
  export function getMarketingNameForModel(modelId) {
555
+ if (!modelId)
556
+ return undefined;
555
557
  if (getAPIProvider() === 'foundry') {
556
558
  // deployment ID is user-defined in Foundry, so it may have no relation to the actual model
557
559
  return undefined;
package/docs/comandos.md CHANGED
@@ -5,7 +5,7 @@ Segun [commands.ts](/D:/Documents/GitHub/Claude/claude-code_V1/Context_Code_V1/C
5
5
  | `/add-dir` | Agrega directorios extra al contexto permitido. | Trabajar con carpetas fuera del cwd actual. |
6
6
  | `/advisor` | Activa/desactiva modo asesor. | Pedir criterio o revision antes de ejecutar cambios. |
7
7
  | `/agents` | UI de gestion de agentes. | Crear, editar, ver o eliminar agentes personalizados. |
8
- | `/agent` | Manejo o entrada a agente. | Invocar o controlar agentes especializados. |
8
+ | `/agent` | Gestion de agentes por rol. | Crear agentes (backend/frontend/qa/docs) y delegar desde el hilo principal. |
9
9
  | `/branch` | Gestiona ramas o worktrees. | Separar trabajo por rama sin ensuciar otra linea. |
10
10
  | `/btw` | Anade una nota rapida al hilo. | Dejar contexto lateral sin cambiar la tarea principal. |
11
11
  | `/clear` | Limpia conversacion o contexto local. | Reiniciar contexto para bajar ruido y tokens. |
@@ -36,7 +36,6 @@ Segun [commands.ts](/D:/Documents/GitHub/Claude/claude-code_V1/Context_Code_V1/C
36
36
  | `/policy` | Muestra o aplica politicas. | Ver restricciones de uso o empresa. |
37
37
  | `/run` | Ejecuta una instruccion no interactiva. | Automatizar tareas desde CLI. |
38
38
  | `/workspace` | Gestiona workspace. | Cambiar o inspeccionar raiz de trabajo. |
39
- | `/orchestrate` | Orquesta tareas o agentes. | Coordinar trabajo complejo. |
40
39
  | `/provider` | Cambia proveedor. | Usar Anthropic, OpenAI u otros segun configuracion. |
41
40
  | `/output-style` | Cambia estilo de respuesta. | Adaptar tono o formato del asistente. |
42
41
  | `/remote-env` | Configura entorno remoto. | Trabajar contra sesiones remotas. |
@@ -53,8 +52,6 @@ Segun [commands.ts](/D:/Documents/GitHub/Claude/claude-code_V1/Context_Code_V1/C
53
52
  | `/statusline` | Configura linea de estado. | Personalizar informacion en pantalla. |
54
53
  | `/stickers` | Gestiona stickers o mensajes visuales. | Personalizacion del TUI. |
55
54
  | `/tag` | Etiqueta sesion. | Clasificar conversaciones. |
56
- | `/tasks` | Gestiona tareas. | Llevar seguimiento interno. |
57
- | `/team` | Gestiona equipo o agentes. | Coordinar agentes o companeros. |
58
55
  | `/team-auto` | Automatiza equipo. | Ejecutar coordinacion automatica. |
59
56
  | `/limites` | Consulta limites o cuotas. | Controlar tokens, rate limits y plan. |
60
57
  | `/theme` | Cambia tema visual. | Ajustar apariencia terminal. |
@@ -76,6 +73,21 @@ Segun [commands.ts](/D:/Documents/GitHub/Claude/claude-code_V1/Context_Code_V1/C
76
73
  | `/telegram` | Configura Telegram. | Usar el asistente desde Telegram. |
77
74
  | `/whatsapp` | Configura WhatsApp. | Usar el asistente desde WhatsApp. |
78
75
 
76
+ ### Flujo recomendado con `/agent`
77
+
78
+ 1. Ejecuta `/agent`.
79
+ 2. Crea y configura todo de una vez con `/agent setup <proveedor> [model] [profile]`.
80
+ 3. Escribe el objetivo en el hilo principal para delegacion automatica.
81
+
82
+ Ejemplos:
83
+
84
+ ```text
85
+ /agent setup openai gpt-5
86
+ /agent setup claude opus
87
+ /agent setup minimax minimax-m1
88
+ /agent setup zai
89
+ ```
90
+
79
91
  **Opcionales por feature flag**
80
92
 
81
93
  | Comando | Que hace | Para que te sirve |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iaforged/context-code",
3
- "version": "1.1.5",
3
+ "version": "1.1.9",
4
4
  "description": "Context Code es un asistente de desarrollo para la terminal. Puede revisar tu proyecto, editar archivos, ejecutar comandos y apoyarte en tareas reales de programacion.",
5
5
  "author": "Context AI",
6
6
  "license": "MIT",