@iaforged/context-code 1.0.81 → 1.0.83

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 (32) hide show
  1. package/dist/src/commands.js +0 -2
  2. package/dist/src/components/PromptInput/PromptInput.js +7 -2
  3. package/dist/src/components/PromptInput/useMaybeTruncateInput.js +1 -14
  4. package/dist/src/components/agents/AgentDetail.js +19 -210
  5. package/dist/src/components/agents/AgentEditor.js +60 -70
  6. package/dist/src/components/agents/AgentsList.js +3 -2
  7. package/dist/src/components/agents/AgentsMenu.js +10 -10
  8. package/dist/src/components/agents/ColorPicker.js +2 -2
  9. package/dist/src/components/agents/ToolSelector.js +13 -13
  10. package/dist/src/components/agents/agentFileUtils.js +12 -11
  11. package/dist/src/components/agents/new-agent-creation/CreateAgentWizard.js +1 -1
  12. package/dist/src/components/agents/new-agent-creation/wizard-steps/ColorStep.js +22 -68
  13. package/dist/src/components/agents/new-agent-creation/wizard-steps/ConfirmStep.js +30 -369
  14. package/dist/src/components/agents/new-agent-creation/wizard-steps/ConfirmStepWrapper.js +13 -15
  15. package/dist/src/components/agents/new-agent-creation/wizard-steps/DescriptionStep.js +4 -4
  16. package/dist/src/components/agents/new-agent-creation/wizard-steps/GenerateStep.js +15 -22
  17. package/dist/src/components/agents/new-agent-creation/wizard-steps/LocationStep.js +2 -2
  18. package/dist/src/components/agents/new-agent-creation/wizard-steps/MemoryStep.js +9 -9
  19. package/dist/src/components/agents/new-agent-creation/wizard-steps/MethodStep.js +3 -3
  20. package/dist/src/components/agents/new-agent-creation/wizard-steps/ModelStep.js +8 -38
  21. package/dist/src/components/agents/new-agent-creation/wizard-steps/PromptStep.js +4 -4
  22. package/dist/src/components/agents/new-agent-creation/wizard-steps/ToolsStep.js +1 -1
  23. package/dist/src/components/agents/new-agent-creation/wizard-steps/TypeStep.js +3 -3
  24. package/dist/src/components/agents/validateAgent.js +16 -16
  25. package/dist/src/hooks/usePasteHandler.js +68 -7
  26. package/dist/src/services/compact/compact.js +22 -20
  27. package/dist/src/tools/AgentTool/AgentTool.js +12 -8
  28. package/dist/src/tools/AgentTool/agentDisplay.js +5 -2
  29. package/dist/src/tools/AgentTool/loadAgentsDir.js +36 -0
  30. package/dist/src/tools/AgentTool/resumeAgent.js +7 -3
  31. package/dist/src/utils/model/providers.js +14 -0
  32. package/package.json +1 -1
@@ -41,28 +41,28 @@ export function MemoryStep() {
41
41
  if ($[1] !== isUserScope) {
42
42
  const folder = getProjectMemoryFolderLabel();
43
43
  t1 = isUserScope ? [{
44
- label: "User scope (~/.context/agent-memory/) (Recommended)",
44
+ label: "Alcance de usuario (~/.context/agent-memory/) (Recomendado)",
45
45
  value: "user"
46
46
  }, {
47
- label: "None (no persistent memory)",
47
+ label: "Ninguna (sin memoria persistente)",
48
48
  value: "none"
49
49
  }, {
50
- label: `Project scope (${folder}/agent-memory/)`,
50
+ label: `Alcance de proyecto (${folder}/agent-memory/)`,
51
51
  value: "project"
52
52
  }, {
53
- label: `Local scope (${folder}/agent-memory-local/)`,
53
+ label: `Alcance local (${folder}/agent-memory-local/)`,
54
54
  value: "local"
55
55
  }] : [{
56
- label: `Project scope (${folder}/agent-memory/) (Recommended)`,
56
+ label: `Alcance de proyecto (${folder}/agent-memory/) (Recomendado)`,
57
57
  value: "project"
58
58
  }, {
59
- label: "None (no persistent memory)",
59
+ label: "Ninguna (sin memoria persistente)",
60
60
  value: "none"
61
61
  }, {
62
- label: "User scope (~/.context/agent-memory/)",
62
+ label: "Alcance de usuario (~/.context/agent-memory/)",
63
63
  value: "user"
64
64
  }, {
65
- label: `Local scope (${folder}/agent-memory-local/)`,
65
+ label: `Alcance local (${folder}/agent-memory-local/)`,
66
66
  value: "local"
67
67
  }];
68
68
  $[1] = isUserScope;
@@ -107,7 +107,7 @@ export function MemoryStep() {
107
107
  }
108
108
  let t4;
109
109
  if ($[9] !== goBack || $[10] !== handleSelect || $[11] !== memoryOptions) {
110
- t4 = _jsx(WizardDialogLayout, { subtitle: "Configure agent memory", footerText: t3, children: _jsx(Box, { children: _jsx(Select, { options: memoryOptions, onChange: handleSelect, onCancel: goBack }, "memory-select") }) });
110
+ t4 = _jsx(WizardDialogLayout, { subtitle: "Configurar memoria del agente", footerText: t3, children: _jsx(Box, { children: _jsx(Select, { options: memoryOptions, onChange: handleSelect, onCancel: goBack }, "memory-select") }) });
111
111
  $[9] = goBack;
112
112
  $[10] = handleSelect;
113
113
  $[11] = memoryOptions;
@@ -13,10 +13,10 @@ export function MethodStep() {
13
13
  let t0;
14
14
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
15
15
  t0 = [{
16
- label: "Generate with Claude (recommended)",
16
+ label: "Generar con Claude (recomendado)",
17
17
  value: "generate"
18
18
  }, {
19
- label: "Manual configuration",
19
+ label: "Configuracion manual",
20
20
  value: "manual"
21
21
  }];
22
22
  $[0] = t0;
@@ -67,7 +67,7 @@ export function MethodStep() {
67
67
  }
68
68
  let t4;
69
69
  if ($[8] !== t2 || $[9] !== t3) {
70
- t4 = _jsx(WizardDialogLayout, { subtitle: "Creation method", footerText: t1, children: _jsx(Box, { children: _jsx(Select, { options: methodOptions, onChange: t2, onCancel: t3 }, "method-select") }) });
70
+ t4 = _jsx(WizardDialogLayout, { subtitle: "Metodo de creacion", footerText: t1, children: _jsx(Box, { children: _jsx(Select, { options: methodOptions, onChange: t2, onCancel: t3 }, "method-select") }) });
71
71
  $[8] = t2;
72
72
  $[9] = t3;
73
73
  $[10] = t4;
@@ -1,5 +1,4 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { c as _c } from "react/compiler-runtime";
3
2
  import { ConfigurableShortcutHint } from '../../../ConfigurableShortcutHint.js';
4
3
  import { Byline } from '../../../design-system/Byline.js';
5
4
  import { KeyboardShortcutHint } from '../../../design-system/KeyboardShortcutHint.js';
@@ -7,42 +6,13 @@ import { useWizard } from '../../../wizard/index.js';
7
6
  import { WizardDialogLayout } from '../../../wizard/WizardDialogLayout.js';
8
7
  import { ModelSelector } from '../../ModelSelector.js';
9
8
  export function ModelStep() {
10
- const $ = _c(8);
11
9
  const { goNext, goBack, updateWizardData, wizardData } = useWizard();
12
- let t0;
13
- if ($[0] !== goNext || $[1] !== updateWizardData) {
14
- t0 = model => {
15
- updateWizardData({
16
- selectedModel: model
17
- });
18
- goNext();
19
- };
20
- $[0] = goNext;
21
- $[1] = updateWizardData;
22
- $[2] = t0;
23
- }
24
- else {
25
- t0 = $[2];
26
- }
27
- const handleComplete = t0;
28
- let t1;
29
- if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
30
- t1 = _jsxs(Byline, { children: [_jsx(KeyboardShortcutHint, { shortcut: "\u2191\u2193", action: "navegar" }), _jsx(KeyboardShortcutHint, { shortcut: "Enter", action: "seleccionar" }), _jsx(ConfigurableShortcutHint, { action: "confirm:no", context: "Confirmation", fallback: "Esc", description: "volver" })] });
31
- $[3] = t1;
32
- }
33
- else {
34
- t1 = $[3];
35
- }
36
- let t2;
37
- if ($[4] !== goBack || $[5] !== handleComplete || $[6] !== wizardData.selectedModel) {
38
- t2 = _jsx(WizardDialogLayout, { subtitle: "Select model", footerText: t1, children: _jsx(ModelSelector, { initialModel: wizardData.selectedModel, onComplete: handleComplete, onCancel: goBack }) });
39
- $[4] = goBack;
40
- $[5] = handleComplete;
41
- $[6] = wizardData.selectedModel;
42
- $[7] = t2;
43
- }
44
- else {
45
- t2 = $[7];
46
- }
47
- return t2;
10
+ const handleComplete = (selection) => {
11
+ updateWizardData({
12
+ selectedModel: selection.model,
13
+ selectedProvider: selection.provider,
14
+ });
15
+ goNext();
16
+ };
17
+ return (_jsx(WizardDialogLayout, { subtitle: "Proveedor y modelo", footerText: _jsxs(Byline, { children: [_jsx(KeyboardShortcutHint, { shortcut: "\u2191\u2193", action: "navegar" }), _jsx(KeyboardShortcutHint, { shortcut: "Enter", action: "seleccionar" }), _jsx(ConfigurableShortcutHint, { action: "confirm:no", context: "Confirmation", fallback: "Esc", description: "volver" })] }), children: _jsx(ModelSelector, { initialModel: wizardData.selectedModel, initialProvider: wizardData.selectedProvider, onComplete: handleComplete, onCancel: goBack }) }));
48
18
  }
@@ -59,7 +59,7 @@ export function PromptStep() {
59
59
  t3 = () => {
60
60
  const trimmedPrompt = systemPrompt.trim();
61
61
  if (!trimmedPrompt) {
62
- setError("System prompt is required");
62
+ setError("El system prompt es obligatorio");
63
63
  return;
64
64
  }
65
65
  setError(null);
@@ -88,8 +88,8 @@ export function PromptStep() {
88
88
  let t5;
89
89
  let t6;
90
90
  if ($[9] === Symbol.for("react.memo_cache_sentinel")) {
91
- t5 = _jsx(Text, { children: "Enter the system prompt for your agent:" });
92
- t6 = _jsx(Text, { dimColor: true, children: "Be comprehensive for best results" });
91
+ t5 = _jsx(Text, { children: "Escribe el system prompt de tu agente:" });
92
+ t6 = _jsx(Text, { dimColor: true, children: "Se detallado para obtener mejores resultados" });
93
93
  $[9] = t5;
94
94
  $[10] = t6;
95
95
  }
@@ -99,7 +99,7 @@ export function PromptStep() {
99
99
  }
100
100
  let t7;
101
101
  if ($[11] !== cursorOffset || $[12] !== handleSubmit || $[13] !== systemPrompt) {
102
- t7 = _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: systemPrompt, onChange: setSystemPrompt, onSubmit: handleSubmit, placeholder: "You are a helpful code reviewer who...", columns: 80, cursorOffset: cursorOffset, onChangeCursorOffset: setCursorOffset, focus: true, showCursor: true }) });
102
+ t7 = _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: systemPrompt, onChange: setSystemPrompt, onSubmit: handleSubmit, placeholder: "Eres un revisor de codigo util que...", columns: 80, cursorOffset: cursorOffset, onChangeCursorOffset: setCursorOffset, focus: true, showCursor: true }) });
103
103
  $[11] = cursorOffset;
104
104
  $[12] = handleSubmit;
105
105
  $[13] = systemPrompt;
@@ -37,7 +37,7 @@ export function ToolsStep(t0) {
37
37
  }
38
38
  let t3;
39
39
  if ($[4] !== goBack || $[5] !== handleComplete || $[6] !== initialTools || $[7] !== tools) {
40
- t3 = _jsx(WizardDialogLayout, { subtitle: "Select tools", footerText: t2, children: _jsx(ToolSelector, { tools: tools, initialTools: initialTools, onComplete: handleComplete, onCancel: goBack }) });
40
+ t3 = _jsx(WizardDialogLayout, { subtitle: "Seleccionar herramientas", footerText: t2, children: _jsx(ToolSelector, { tools: tools, initialTools: initialTools, onComplete: handleComplete, onCancel: goBack }) });
41
41
  $[4] = goBack;
42
42
  $[5] = handleComplete;
43
43
  $[6] = initialTools;
@@ -60,7 +60,7 @@ export function TypeStep(_props) {
60
60
  }
61
61
  let t3;
62
62
  if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
63
- t3 = _jsx(Text, { children: "Enter a unique identifier for your agent:" });
63
+ t3 = _jsx(Text, { children: "Escribe un identificador unico para tu agente:" });
64
64
  $[5] = t3;
65
65
  }
66
66
  else {
@@ -68,7 +68,7 @@ export function TypeStep(_props) {
68
68
  }
69
69
  let t4;
70
70
  if ($[6] !== agentType || $[7] !== cursorOffset || $[8] !== handleSubmit) {
71
- t4 = _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: agentType, onChange: setAgentType, onSubmit: handleSubmit, placeholder: "e.g., test-runner, tech-lead, etc", columns: 60, cursorOffset: cursorOffset, onChangeCursorOffset: setCursorOffset, focus: true, showCursor: true }) });
71
+ t4 = _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: agentType, onChange: setAgentType, onSubmit: handleSubmit, placeholder: "p. ej., test-runner, tech-lead, etc", columns: 60, cursorOffset: cursorOffset, onChangeCursorOffset: setCursorOffset, focus: true, showCursor: true }) });
72
72
  $[6] = agentType;
73
73
  $[7] = cursorOffset;
74
74
  $[8] = handleSubmit;
@@ -88,7 +88,7 @@ export function TypeStep(_props) {
88
88
  }
89
89
  let t6;
90
90
  if ($[12] !== t4 || $[13] !== t5) {
91
- t6 = _jsx(WizardDialogLayout, { subtitle: "Agent type (identifier)", footerText: t2, children: _jsxs(Box, { flexDirection: "column", children: [t3, t4, t5] }) });
91
+ t6 = _jsx(WizardDialogLayout, { subtitle: "Tipo de agente (identificador)", footerText: t2, children: _jsxs(Box, { flexDirection: "column", children: [t3, t4, t5] }) });
92
92
  $[12] = t4;
93
93
  $[13] = t5;
94
94
  $[14] = t6;
@@ -2,16 +2,16 @@ import { resolveAgentTools } from '../../tools/AgentTool/agentToolUtils.js';
2
2
  import { getAgentSourceDisplayName } from './utils.js';
3
3
  export function validateAgentType(agentType) {
4
4
  if (!agentType) {
5
- return 'Agent type is required';
5
+ return 'El tipo de agente es obligatorio';
6
6
  }
7
7
  if (!/^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$/.test(agentType)) {
8
- return 'Agent type must start and end with alphanumeric characters and contain only letters, numbers, and hyphens';
8
+ return 'El tipo de agente debe iniciar y terminar con caracteres alfanumericos y solo puede contener letras, numeros y guiones';
9
9
  }
10
10
  if (agentType.length < 3) {
11
- return 'Agent type must be at least 3 characters long';
11
+ return 'El tipo de agente debe tener al menos 3 caracteres';
12
12
  }
13
13
  if (agentType.length > 50) {
14
- return 'Agent type must be less than 50 characters';
14
+ return 'El tipo de agente debe tener menos de 50 caracteres';
15
15
  }
16
16
  return null;
17
17
  }
@@ -20,7 +20,7 @@ export function validateAgent(agent, availableTools, existingAgents) {
20
20
  const warnings = [];
21
21
  // Validate agent type
22
22
  if (!agent.agentType) {
23
- errors.push('Agent type is required');
23
+ errors.push('El tipo de agente es obligatorio');
24
24
  }
25
25
  else {
26
26
  const typeError = validateAgentType(agent.agentType);
@@ -30,46 +30,46 @@ export function validateAgent(agent, availableTools, existingAgents) {
30
30
  // Check for duplicates (excluding self for editing)
31
31
  const duplicate = existingAgents.find(a => a.agentType === agent.agentType && a.source !== agent.source);
32
32
  if (duplicate) {
33
- errors.push(`Agent type "${agent.agentType}" already exists in ${getAgentSourceDisplayName(duplicate.source)}`);
33
+ errors.push(`El tipo de agente "${agent.agentType}" ya existe en ${getAgentSourceDisplayName(duplicate.source)}`);
34
34
  }
35
35
  }
36
36
  // Validate description
37
37
  if (!agent.whenToUse) {
38
- errors.push('Description (description) is required');
38
+ errors.push('La descripcion es obligatoria');
39
39
  }
40
40
  else if (agent.whenToUse.length < 10) {
41
- warnings.push('Description should be more descriptive (at least 10 characters)');
41
+ warnings.push('La descripcion deberia ser mas clara (al menos 10 caracteres)');
42
42
  }
43
43
  else if (agent.whenToUse.length > 5000) {
44
- warnings.push('Description is very long (over 5000 characters)');
44
+ warnings.push('La descripcion es muy larga (mas de 5000 caracteres)');
45
45
  }
46
46
  // Validate tools
47
47
  if (agent.tools !== undefined && !Array.isArray(agent.tools)) {
48
- errors.push('Tools must be an array');
48
+ errors.push('Las herramientas deben ser un arreglo');
49
49
  }
50
50
  else {
51
51
  if (agent.tools === undefined) {
52
- warnings.push('Agent has access to all tools');
52
+ warnings.push('El agente tendra acceso a todas las herramientas');
53
53
  }
54
54
  else if (agent.tools.length === 0) {
55
- warnings.push('No tools selected - agent will have very limited capabilities');
55
+ warnings.push('No hay herramientas seleccionadas: el agente tendra capacidades muy limitadas');
56
56
  }
57
57
  // Check for invalid tools
58
58
  const resolvedTools = resolveAgentTools(agent, availableTools, false);
59
59
  if (resolvedTools.invalidTools.length > 0) {
60
- errors.push(`Invalid tools: ${resolvedTools.invalidTools.join(', ')}`);
60
+ errors.push(`Herramientas no validas: ${resolvedTools.invalidTools.join(', ')}`);
61
61
  }
62
62
  }
63
63
  // Validate system prompt
64
64
  const systemPrompt = agent.getSystemPrompt();
65
65
  if (!systemPrompt) {
66
- errors.push('System prompt is required');
66
+ errors.push('El system prompt es obligatorio');
67
67
  }
68
68
  else if (systemPrompt.length < 20) {
69
- errors.push('System prompt is too short (minimum 20 characters)');
69
+ errors.push('El system prompt es demasiado corto (minimo 20 caracteres)');
70
70
  }
71
71
  else if (systemPrompt.length > 10000) {
72
- warnings.push('System prompt is very long (over 10,000 characters)');
72
+ warnings.push('El system prompt es muy largo (mas de 10,000 caracteres)');
73
73
  }
74
74
  return {
75
75
  isValid: errors.length === 0,
@@ -2,7 +2,7 @@ import { basename } from 'path';
2
2
  import React from 'react';
3
3
  import { logError } from '../utils/log.js';
4
4
  import { useDebounceCallback } from 'usehooks-ts';
5
- import { getImageFromClipboard, isImageFilePath, PASTE_THRESHOLD, tryReadImageFromPath, } from '../utils/imagePaste.js';
5
+ import { getImageFromClipboard, isImageFilePath, tryReadImageFromPath, } from '../utils/imagePaste.js';
6
6
  import { getPlatform } from '../utils/platform.js';
7
7
  const CLIPBOARD_CHECK_DEBOUNCE_MS = 50;
8
8
  const PASTE_COMPLETION_TIMEOUT_MS = 100;
@@ -16,10 +16,27 @@ export function usePasteHandler({ onPaste, onInput, onImagePaste, }) {
16
16
  // reads stale pasteState.timeoutId (null) and takes the onInput path. If
17
17
  // that key is Enter, it submits the old input and the paste is lost.
18
18
  const pastePendingRef = React.useRef(false);
19
+ // For detecting fast non-bracketed pastes
20
+ const typingQueueRef = React.useRef([]);
21
+ const typingTimeoutRef = React.useRef(null);
22
+ const flushTypingQueue = React.useCallback((onInputCallback) => {
23
+ if (typingTimeoutRef.current) {
24
+ clearTimeout(typingTimeoutRef.current);
25
+ typingTimeoutRef.current = null;
26
+ }
27
+ const queue = typingQueueRef.current;
28
+ typingQueueRef.current = [];
29
+ for (const q of queue) {
30
+ onInputCallback(q.input, q.key);
31
+ }
32
+ }, []);
19
33
  const isMacOS = React.useMemo(() => getPlatform() === 'macos', []);
20
34
  React.useEffect(() => {
21
35
  return () => {
22
36
  isMountedRef.current = false;
37
+ if (typingTimeoutRef.current) {
38
+ clearTimeout(typingTimeoutRef.current);
39
+ }
23
40
  };
24
41
  }, []);
25
42
  const checkClipboardForImageImpl = React.useCallback(() => {
@@ -156,13 +173,22 @@ export function usePasteHandler({ onPaste, onInput, onImagePaste, }) {
156
173
  return;
157
174
  }
158
175
  // Check if we should handle as paste (from bracketed paste, large input, or continuation)
159
- const shouldHandleAsPaste = onPaste &&
160
- (input.length > PASTE_THRESHOLD ||
161
- pastePendingRef.current ||
162
- hasImageFilePath ||
163
- isFromPaste);
164
- if (shouldHandleAsPaste) {
176
+ const isAlreadyPaste = pastePendingRef.current ||
177
+ hasImageFilePath ||
178
+ isFromPaste ||
179
+ input.length > 50;
180
+ if (onPaste && isAlreadyPaste) {
165
181
  pastePendingRef.current = true;
182
+ // If we had buffered typing that was actually part of this paste, move it!
183
+ if (typingQueueRef.current.length > 0) {
184
+ const queuedText = typingQueueRef.current.map(q => q.input).join('');
185
+ input = queuedText + input;
186
+ if (typingTimeoutRef.current) {
187
+ clearTimeout(typingTimeoutRef.current);
188
+ typingTimeoutRef.current = null;
189
+ }
190
+ typingQueueRef.current = [];
191
+ }
166
192
  setPasteState(({ chunks, timeoutId }) => {
167
193
  return {
168
194
  chunks: [...chunks, input],
@@ -171,6 +197,41 @@ export function usePasteHandler({ onPaste, onInput, onImagePaste, }) {
171
197
  });
172
198
  return;
173
199
  }
200
+ // It might be the start of a paste! (length <= 50)
201
+ // Check if it's pure text (not a control key)
202
+ const isControl = key.ctrl || key.meta || key.option || key.super || key.fn ||
203
+ (key.name !== undefined && key.name.length > 1 && key.name !== 'space' && key.name !== 'number');
204
+ if (onPaste && input.length > 0 && !isControl) {
205
+ typingQueueRef.current.push({ input, key });
206
+ // Calculate total length of queued typing
207
+ const totalLength = typingQueueRef.current.reduce((acc, q) => acc + q.input.length, 0);
208
+ if (totalLength > 50) {
209
+ // Oh! It accumulated to > 50 within the tiny window! It IS a paste!
210
+ pastePendingRef.current = true;
211
+ const queuedText = typingQueueRef.current.map(q => q.input).join('');
212
+ typingQueueRef.current = [];
213
+ if (typingTimeoutRef.current) {
214
+ clearTimeout(typingTimeoutRef.current);
215
+ typingTimeoutRef.current = null;
216
+ }
217
+ setPasteState(({ chunks, timeoutId }) => {
218
+ return {
219
+ chunks: [...chunks, queuedText],
220
+ timeoutId: resetPasteTimeout(timeoutId),
221
+ };
222
+ });
223
+ return;
224
+ }
225
+ // Schedule flush if not already scheduled
226
+ if (!typingTimeoutRef.current) {
227
+ typingTimeoutRef.current = setTimeout(() => {
228
+ flushTypingQueue(onInput);
229
+ }, 10); // 10ms is completely imperceptible
230
+ }
231
+ return;
232
+ }
233
+ // If it's a control key or onPaste is not defined, flush any pending typing immediately
234
+ flushTypingQueue(onInput);
174
235
  onInput(input, key);
175
236
  if (input.length > 10) {
176
237
  // Ensure that setIsPasting is turned off on any other multicharacter
@@ -49,6 +49,7 @@ import { getCompactPrompt, getCompactUserSummaryMessage, getPartialCompactPrompt
49
49
  export const POST_COMPACT_MAX_FILES_TO_RESTORE = 5;
50
50
  export const POST_COMPACT_TOKEN_BUDGET = 50_000;
51
51
  export const POST_COMPACT_MAX_TOKENS_PER_FILE = 5_000;
52
+ export const POST_COMPACT_MAX_TOKENS_FOR_PLAN = 5_000;
52
53
  // Skills can be large (verify=18.7KB, claude-api=20.1KB). Previously re-injected
53
54
  // unbounded on every compact → 5-10K tok/compact. Per-skill truncation beats
54
55
  // dropping — instructions at the top of a skill file are usually the critical
@@ -120,22 +121,23 @@ export function stripImagesFromMessages(messages) {
120
121
  };
121
122
  });
122
123
  }
123
- /**
124
- * Strip attachment types that are re-injected post-compaction anyway.
125
- * skill_discovery/skill_listing are re-surfaced by resetSentSkillNames()
126
- * + the next turn's discovery signal, so feeding them to the summarizer
127
- * wastes tokens and pollutes the summary with stale skill suggestions.
128
- *
129
- * No-op when EXPERIMENTAL_SKILL_SEARCH is off (the attachment types
130
- * don't exist on external builds).
131
- */
132
124
  export function stripReinjectedAttachments(messages) {
133
- if (feature('EXPERIMENTAL_SKILL_SEARCH')) {
134
- return messages.filter(m => !(m.type === 'attachment' &&
125
+ const stripSkillSearchAttachments = feature('EXPERIMENTAL_SKILL_SEARCH');
126
+ return messages.filter(m => {
127
+ if (m.type !== 'attachment') {
128
+ return true;
129
+ }
130
+ if (m.attachment.type === 'plan_file_reference' ||
131
+ m.attachment.type === 'invoked_skills') {
132
+ return false;
133
+ }
134
+ if (stripSkillSearchAttachments &&
135
135
  (m.attachment.type === 'skill_discovery' ||
136
- m.attachment.type === 'skill_listing')));
137
- }
138
- return messages;
136
+ m.attachment.type === 'skill_listing')) {
137
+ return false;
138
+ }
139
+ return true;
140
+ });
139
141
  }
140
142
  export const ERROR_MESSAGE_NOT_ENOUGH_MESSAGES = 'Not enough messages to compact.';
141
143
  const MAX_PTL_RETRIES = 3;
@@ -1070,11 +1072,12 @@ export async function createPostCompactFileAttachments(readFileState, toolUseCon
1070
1072
  * This ensures the plan is preserved after compaction.
1071
1073
  */
1072
1074
  export function createPlanAttachmentIfNeeded(agentId) {
1073
- const planContent = getPlan(agentId);
1074
- if (!planContent) {
1075
+ const rawPlanContent = getPlan(agentId);
1076
+ if (!rawPlanContent) {
1075
1077
  return null;
1076
1078
  }
1077
1079
  const planFilePath = getPlanFilePath(agentId);
1080
+ const planContent = truncateToTokens(rawPlanContent, POST_COMPACT_MAX_TOKENS_FOR_PLAN, '\n\n[... plan content truncated for compaction; use Read on the plan path if you need the full text]');
1078
1081
  return createAttachmentMessage({
1079
1082
  type: 'plan_file_reference',
1080
1083
  planFilePath,
@@ -1216,19 +1219,18 @@ function collectReadToolFilePaths(messages) {
1216
1219
  }
1217
1220
  return paths;
1218
1221
  }
1219
- const SKILL_TRUNCATION_MARKER = '\n\n[... skill content truncated for compaction; use Read on the skill path if you need the full text]';
1220
1222
  /**
1221
1223
  * Truncate content to roughly maxTokens, keeping the head. roughTokenCountEstimation
1222
1224
  * uses ~4 chars/token (its default bytesPerToken), so char budget = maxTokens * 4
1223
1225
  * minus the marker so the result stays within budget. Marker tells the model it
1224
1226
  * can Read the full file if needed.
1225
1227
  */
1226
- function truncateToTokens(content, maxTokens) {
1228
+ function truncateToTokens(content, maxTokens, marker = '\n\n[... skill content truncated for compaction; use Read on the skill path if you need the full text]') {
1227
1229
  if (roughTokenCountEstimation(content) <= maxTokens) {
1228
1230
  return content;
1229
1231
  }
1230
- const charBudget = maxTokens * 4 - SKILL_TRUNCATION_MARKER.length;
1231
- return content.slice(0, charBudget) + SKILL_TRUNCATION_MARKER;
1232
+ const charBudget = maxTokens * 4 - marker.length;
1233
+ return content.slice(0, Math.max(0, charBudget)) + marker;
1232
1234
  }
1233
1235
  function shouldExcludeFromPostCompactRestore(filename, agentId) {
1234
1236
  const normalizedFilename = expandPath(filename);
@@ -25,6 +25,8 @@ import { AbortError, errorMessage, toError } from '../../utils/errors.js';
25
25
  import { lazySchema } from '../../utils/lazySchema.js';
26
26
  import { createUserMessage, extractTextContent, isSyntheticMessage, normalizeMessages } from '../../utils/messages.js';
27
27
  import { getAgentModel } from '../../utils/model/agent.js';
28
+ import { runWithProviderOverride } from '../../utils/model/providerOverrideContext.js';
29
+ import { providerPreferenceToApiProvider } from '../../utils/model/providers.js';
28
30
  import { permissionModeSchema } from '../../utils/permissions/PermissionMode.js';
29
31
  import { filterDeniedAgents, getDenyRuleForAgent } from '../../utils/permissions/permissions.js';
30
32
  import { enqueueSdkEvent } from '../../utils/sdkEventQueue.js';
@@ -316,8 +318,10 @@ export const AgentTool = buildTool({
316
318
  if (selectedAgent.color) {
317
319
  setAgentColor(selectedAgent.agentType, selectedAgent.color);
318
320
  }
321
+ const agentProviderOverride = providerPreferenceToApiProvider(selectedAgent.provider);
322
+ const withAgentProvider = (fn => runWithProviderOverride(agentProviderOverride, fn));
319
323
  // Resolve agent params for logging (these are already resolved in runAgent)
320
- const resolvedAgentModel = getAgentModel(selectedAgent.model, toolUseContext.options.mainLoopModel, isForkPath ? undefined : model, permissionMode);
324
+ const resolvedAgentModel = withAgentProvider(() => getAgentModel(selectedAgent.model, toolUseContext.options.mainLoopModel, isForkPath ? undefined : model, permissionMode));
321
325
  logEvent('tengu_agent_tool_selected', {
322
326
  agent_type: selectedAgent.agentType,
323
327
  model: resolvedAgentModel,
@@ -425,7 +429,7 @@ export const AgentTool = buildTool({
425
429
  });
426
430
  }
427
431
  // Apply environment details enhancement
428
- enhancedSystemPrompt = await enhanceSystemPromptWithEnvDetails([agentPrompt], resolvedAgentModel, additionalWorkingDirectories);
432
+ enhancedSystemPrompt = await withAgentProvider(() => enhanceSystemPromptWithEnvDetails([agentPrompt], resolvedAgentModel, additionalWorkingDirectories));
429
433
  }
430
434
  catch (error) {
431
435
  logForDebugging(`Failed to get system prompt for agent ${selectedAgent.agentType}: ${errorMessage(error)}`);
@@ -600,7 +604,7 @@ export const AgentTool = buildTool({
600
604
  // invocation time — when this `void` fires — and survives every await
601
605
  // inside. No capture/restore needed; the detached closure sees the
602
606
  // parent turn's workload automatically, isolated from its finally.
603
- void runWithAgentContext(asyncAgentContext, () => wrapWithCwd(() => runAsyncAgentLifecycle({
607
+ void withAgentProvider(() => runWithAgentContext(asyncAgentContext, () => wrapWithCwd(() => runAsyncAgentLifecycle({
604
608
  taskId: agentBackgroundTask.agentId,
605
609
  abortController: agentBackgroundTask.abortController,
606
610
  makeStream: onCacheSafeParams => runAgent({
@@ -619,7 +623,7 @@ export const AgentTool = buildTool({
619
623
  agentIdForCleanup: asyncAgentId,
620
624
  enableSummarization: isCoordinator || isForkSubagentEnabled() || getSdkAgentProgressSummariesEnabled(),
621
625
  getWorktreeResult: cleanupWorktreeIfNeeded
622
- })));
626
+ }))));
623
627
  const canReadOutputFile = toolUseContext.options.tools.some(t => toolMatchesName(t, FILE_READ_TOOL_NAME) || toolMatchesName(t, BASH_TOOL_NAME));
624
628
  return {
625
629
  data: {
@@ -651,7 +655,7 @@ export const AgentTool = buildTool({
651
655
  };
652
656
  // Wrap entire sync agent execution in context for analytics attribution
653
657
  // and optionally in a worktree cwd override for filesystem isolation
654
- return runWithAgentContext(syncAgentContext, () => wrapWithCwd(async () => {
658
+ return withAgentProvider(() => runWithAgentContext(syncAgentContext, () => wrapWithCwd(async () => {
655
659
  const agentMessages = [];
656
660
  const agentStartTime = Date.now();
657
661
  const syncTracker = createProgressTracker();
@@ -761,7 +765,7 @@ export const AgentTool = buildTool({
761
765
  // Workload: inherited via ALS at `void` invocation time,
762
766
  // same as the async-from-start path above.
763
767
  // Continue agent in background and return async result
764
- void runWithAgentContext(syncAgentContext, async () => {
768
+ void withAgentProvider(() => runWithAgentContext(syncAgentContext, async () => {
765
769
  let stopBackgroundedSummarization;
766
770
  try {
767
771
  // Clean up the foreground iterator so its finally block runs
@@ -883,7 +887,7 @@ export const AgentTool = buildTool({
883
887
  // Note: worktree cleanup is done before enqueueAgentNotification
884
888
  // in both try and catch paths so we can include worktree info
885
889
  }
886
- });
890
+ }));
887
891
  // Return async_launched result immediately
888
892
  const canReadOutputFile = toolUseContext.options.tools.some(t => toolMatchesName(t, FILE_READ_TOOL_NAME) || toolMatchesName(t, BASH_TOOL_NAME));
889
893
  return {
@@ -1091,7 +1095,7 @@ export const AgentTool = buildTool({
1091
1095
  ...worktreeResult
1092
1096
  }
1093
1097
  };
1094
- }));
1098
+ })));
1095
1099
  }
1096
1100
  },
1097
1101
  isReadOnly() {
@@ -2,7 +2,7 @@
2
2
  * Shared utilities for displaying agent information.
3
3
  * Used by both the CLI `claude agents` handler and the interactive `/agents` command.
4
4
  */
5
- import { getDefaultSubagentModel } from '../../utils/model/agent.js';
5
+ import { getAgentProviderDisplay, getDefaultSubagentModel, } from '../../utils/model/agent.js';
6
6
  const OVERRIDE_SOURCE_LABELS = {
7
7
  userSettings: 'usuario',
8
8
  projectSettings: 'proyecto',
@@ -61,7 +61,10 @@ export function resolveAgentModelDisplay(agent) {
61
61
  const model = agent.model || getDefaultSubagentModel();
62
62
  if (!model)
63
63
  return undefined;
64
- return model === 'inherit' ? 'heredar' : model;
64
+ return model === 'inherit' ? 'Heredar del padre' : model;
65
+ }
66
+ export function resolveAgentProviderDisplay(agent) {
67
+ return getAgentProviderDisplay(agent.provider);
65
68
  }
66
69
  /**
67
70
  * Get a human-readable label for the source that overrides an agent.
@@ -42,6 +42,19 @@ const AgentJsonSchema = lazySchema(() => z.object({
42
42
  .min(1, 'Model cannot be empty')
43
43
  .transform(m => (m.toLowerCase() === 'inherit' ? 'inherit' : m))
44
44
  .optional(),
45
+ provider: z
46
+ .enum([
47
+ 'claude',
48
+ 'openai',
49
+ 'openrouter',
50
+ 'ollama',
51
+ 'ollama-cloud',
52
+ 'gemini-api',
53
+ 'gemini-google',
54
+ 'zai',
55
+ 'minimax',
56
+ ])
57
+ .optional(),
45
58
  effort: z.union([z.enum(EFFORT_LEVELS), z.number().int()]).optional(),
46
59
  permissionMode: z.enum(PERMISSION_MODES).optional(),
47
60
  mcpServers: z.array(AgentMcpServerSpecSchema()).optional(),
@@ -285,6 +298,7 @@ export function parseAgentFromJson(name, definition, source = 'flagSettings') {
285
298
  },
286
299
  source,
287
300
  ...(parsed.model ? { model: parsed.model } : {}),
301
+ ...(parsed.provider ? { provider: parsed.provider } : {}),
288
302
  ...(parsed.effort !== undefined ? { effort: parsed.effort } : {}),
289
303
  ...(parsed.permissionMode
290
304
  ? { permissionMode: parsed.permissionMode }
@@ -347,6 +361,27 @@ export function parseAgentFromMarkdown(filePath, baseDir, frontmatter, content,
347
361
  // Unescape newlines in whenToUse that were escaped for YAML parsing
348
362
  whenToUse = whenToUse.replace(/\\n/g, '\n');
349
363
  const color = frontmatter['color'];
364
+ const providerRaw = frontmatter['provider'];
365
+ const VALID_PROVIDERS = [
366
+ 'claude',
367
+ 'openai',
368
+ 'openrouter',
369
+ 'ollama',
370
+ 'ollama-cloud',
371
+ 'gemini-api',
372
+ 'gemini-google',
373
+ 'zai',
374
+ 'minimax',
375
+ ];
376
+ let provider;
377
+ if (typeof providerRaw === 'string') {
378
+ if (VALID_PROVIDERS.includes(providerRaw)) {
379
+ provider = providerRaw;
380
+ }
381
+ else {
382
+ logForDebugging(`Agent file ${filePath} has invalid provider value '${providerRaw}'. Valid options: ${VALID_PROVIDERS.join(', ')}`);
383
+ }
384
+ }
350
385
  const modelRaw = frontmatter['model'];
351
386
  let model;
352
387
  if (typeof modelRaw === 'string' && modelRaw.trim().length > 0) {
@@ -477,6 +512,7 @@ export function parseAgentFromMarkdown(filePath, baseDir, frontmatter, content,
477
512
  ? { color }
478
513
  : {}),
479
514
  ...(model !== undefined ? { model } : {}),
515
+ ...(provider !== undefined ? { provider } : {}),
480
516
  ...(parsedEffort !== undefined ? { effort: parsedEffort } : {}),
481
517
  ...(isValidPermissionMode
482
518
  ? { permissionMode: permissionModeRaw }