@iaforged/context-code 1.1.7 → 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;
@@ -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
  }
@@ -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
+ }
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.7",
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",