@iaforged/context-code 1.3.6 → 1.3.7
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.
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatearDuracion, } from '../../services/objetivo/types.js';
|
|
2
|
+
import { getSessionBypassPermissionsMode, setSessionBypassPermissionsMode, } from '../../bootstrap/state.js';
|
|
2
3
|
import { isExternalPermissionMode } from '../../utils/permissions/PermissionMode.js';
|
|
3
4
|
import { applyPermissionUpdate } from '../../utils/permissions/PermissionUpdate.js';
|
|
4
5
|
const ALIAS_CLEAR = new Set([
|
|
@@ -50,12 +51,24 @@ export const call = async (onDone, context, args) => {
|
|
|
50
51
|
onDone('No había objetivo activo.');
|
|
51
52
|
return null;
|
|
52
53
|
}
|
|
54
|
+
// Restaurar el flag global ANTES del setAppState para evitar ventanas en
|
|
55
|
+
// las que AppState ya dice "no bypass" pero el global sigue en true.
|
|
56
|
+
if (objetivoActual.sessionBypassPreviamenteActivo !== undefined) {
|
|
57
|
+
setSessionBypassPermissionsMode(objetivoActual.sessionBypassPreviamenteActivo);
|
|
58
|
+
}
|
|
53
59
|
context.setAppState(prev => {
|
|
54
60
|
const next = { ...prev, objetivo: undefined };
|
|
55
61
|
const modoPrevio = prev.objetivo?.modoPermisoPrevio;
|
|
62
|
+
const bypassPrev = prev.objetivo?.bypassPreviamenteDisponible;
|
|
56
63
|
if (modoPrevio !== undefined && modoPrevio !== prev.toolPermissionContext.mode) {
|
|
57
64
|
next.toolPermissionContext = applyPermissionUpdate(prev.toolPermissionContext, { type: 'setMode', mode: modoPrevio, destination: 'session' });
|
|
58
65
|
}
|
|
66
|
+
if (bypassPrev !== undefined) {
|
|
67
|
+
next.toolPermissionContext = {
|
|
68
|
+
...next.toolPermissionContext,
|
|
69
|
+
isBypassPermissionsModeAvailable: bypassPrev,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
59
72
|
return next;
|
|
60
73
|
});
|
|
61
74
|
const restaurado = objetivoActual.modoPermisoPrevio;
|
|
@@ -70,39 +83,57 @@ export const call = async (onDone, context, args) => {
|
|
|
70
83
|
onDone(`La condición es demasiado larga (${argumento.length} chars). Máximo: ${MAX_LONGITUD_META}.`);
|
|
71
84
|
return null;
|
|
72
85
|
}
|
|
73
|
-
//
|
|
74
|
-
// autónomamente y no tiene sentido detenerlo con
|
|
75
|
-
// cada Bash/Edit/Write. Guardamos el modo previo
|
|
76
|
-
//
|
|
77
|
-
//
|
|
86
|
+
// Forzamos bypassPermissions mientras el objetivo corre: el agente trabaja
|
|
87
|
+
// autónomamente y no tiene sentido detenerlo con diálogos de permisos para
|
|
88
|
+
// cada Bash/Edit/Write. Guardamos el modo previo y el valor anterior del
|
|
89
|
+
// flag `isBypassPermissionsModeAvailable` para restaurar exactamente el
|
|
90
|
+
// estado original al cancelar o cumplir.
|
|
91
|
+
//
|
|
92
|
+
// El flag `isBypassPermissionsModeAvailable` se decide al arrancar el CLI
|
|
93
|
+
// (depende de --permission-mode / --dangerously-skip-permissions y de
|
|
94
|
+
// killswitches Statsig/settings). Aquí lo sobreescribimos en sesión: si el
|
|
95
|
+
// usuario lanzó /objetivo es porque acepta el modo bypass.
|
|
78
96
|
const estadoActualizado = context.getAppState();
|
|
79
97
|
const modoActual = estadoActualizado.toolPermissionContext.mode;
|
|
80
|
-
const
|
|
98
|
+
const bypassPreviamenteDisponible = estadoActualizado.toolPermissionContext.isBypassPermissionsModeAvailable;
|
|
99
|
+
const sessionBypassPreviamenteActivo = getSessionBypassPermissionsMode();
|
|
81
100
|
const modoActualEsExterno = isExternalPermissionMode(modoActual);
|
|
82
|
-
const debeCambiarModo =
|
|
83
|
-
modoActual !== 'bypassPermissions' &&
|
|
84
|
-
modoActualEsExterno;
|
|
101
|
+
const debeCambiarModo = modoActual !== 'bypassPermissions' && modoActualEsExterno;
|
|
85
102
|
const nuevo = {
|
|
86
103
|
meta: argumento,
|
|
87
104
|
iniciadoEn: Date.now(),
|
|
88
105
|
turnos: 0,
|
|
89
106
|
ultimaRazon: null,
|
|
90
107
|
modoPermisoPrevio: debeCambiarModo && modoActualEsExterno ? modoActual : undefined,
|
|
108
|
+
bypassPreviamenteDisponible,
|
|
109
|
+
sessionBypassPreviamenteActivo,
|
|
91
110
|
};
|
|
111
|
+
// El flag global sessionBypassPermissionsMode lo consultan varios subsistemas
|
|
112
|
+
// (spawnMultiAgent, claudeInChrome, trust dialog) por fuera de AppState. Sin
|
|
113
|
+
// este flip, ejecutar /objetivo sin haber lanzado el CLI con
|
|
114
|
+
// --dangerously-skip-permissions dejaba al sistema "a medias": el modo en
|
|
115
|
+
// AppState decía bypass, pero el global seguía en false y bloqueaba tools.
|
|
116
|
+
setSessionBypassPermissionsMode(true);
|
|
92
117
|
context.setAppState(prev => {
|
|
93
118
|
const next = { ...prev, objetivo: nuevo };
|
|
119
|
+
// Forzar el flag de disponibilidad ANTES de cambiar el modo, porque el
|
|
120
|
+
// setMode lo respeta. Si el usuario está en un modo interno (auto/bubble),
|
|
121
|
+
// solo levantamos el flag para que el siguiente turno pueda usar bypass
|
|
122
|
+
// a nivel de tools, sin tocar el modo explícitamente.
|
|
123
|
+
next.toolPermissionContext = {
|
|
124
|
+
...prev.toolPermissionContext,
|
|
125
|
+
isBypassPermissionsModeAvailable: true,
|
|
126
|
+
};
|
|
94
127
|
if (debeCambiarModo) {
|
|
95
|
-
next.toolPermissionContext = applyPermissionUpdate(
|
|
128
|
+
next.toolPermissionContext = applyPermissionUpdate(next.toolPermissionContext, { type: 'setMode', mode: 'bypassPermissions', destination: 'session' });
|
|
96
129
|
}
|
|
97
130
|
return next;
|
|
98
131
|
});
|
|
99
132
|
const notaPermisos = debeCambiarModo
|
|
100
|
-
? '\nModo de permisos: bypass
|
|
133
|
+
? '\nModo de permisos: bypass forzado mientras dure el objetivo (sin prompts). Se restaura al cancelar o cumplir.'
|
|
101
134
|
: modoActual === 'bypassPermissions'
|
|
102
135
|
? '\nModo de permisos: ya estabas en bypass, no se cambia.'
|
|
103
|
-
:
|
|
104
|
-
? '\nNota: el modo bypass no está disponible en este entorno; el agente seguirá pidiendo permisos según el modo actual.'
|
|
105
|
-
: `\nNota: estás en modo "${modoActual}" (interno); no se cambia automáticamente.`;
|
|
136
|
+
: `\nNota: estás en modo "${modoActual}" (interno); no se cambia automáticamente, pero los tools no pedirán permiso.`;
|
|
106
137
|
// Disparar un turno inmediatamente con un mensaje meta (visible al modelo,
|
|
107
138
|
// oculto al usuario) para que el agente arranque a trabajar sin que el
|
|
108
139
|
// usuario tenga que escribir un prompt adicional. El evaluador correrá al
|
|
@@ -114,23 +145,28 @@ export const call = async (onDone, context, args) => {
|
|
|
114
145
|
// ve cero evidencia. Por eso se insiste explícitamente en NO describir y
|
|
115
146
|
// ejecutar herramientas YA en este mismo turno.
|
|
116
147
|
onDone(`◎ Objetivo fijado: ${argumento}\n` +
|
|
117
|
-
'
|
|
148
|
+
'◎ Iniciando turno autónomo (el agente trabajará y narrará cada acción)...\n' +
|
|
149
|
+
'Cancela cuando quieras con `/objetivo clear`.' +
|
|
118
150
|
notaPermisos, {
|
|
119
151
|
shouldQuery: true,
|
|
120
152
|
metaMessages: [
|
|
121
153
|
`OBJETIVO ACTIVO: "${argumento}"
|
|
122
154
|
|
|
123
|
-
Trabajas en modo autónomo.
|
|
155
|
+
Trabajas en modo autónomo hasta cumplir el objetivo. Compórtate exactamente como cuando el usuario te da una tarea normal en este chat.
|
|
156
|
+
|
|
157
|
+
REGLAS ESTRICTAS:
|
|
158
|
+
|
|
159
|
+
1. Haz TÚ MISMO el trabajo en este chat. PROHIBIDO delegar al sub-agente con la tool "Agent" o "Task". PROHIBIDO crear tareas con TaskCreate para que las haga otro: el usuario quiere ver el trabajo aquí, no en un proceso de fondo.
|
|
160
|
+
2. Antes de cada acción, escribe UNA frase corta diciendo lo que vas a hacer ahora ("Voy a listar la estructura", "Reviso el package.json", "Corro los tests"). Inmediatamente después invoca la tool correspondiente.
|
|
161
|
+
3. Usa herramientas directas: Read, Glob, Grep, Bash (o PowerShell en Windows), Edit, Write. El usuario está mirando y necesita ver actividad continua: alterna entre frase corta y tool call.
|
|
162
|
+
4. Después de cada tool result, escribe otra frase corta resumiendo lo que aprendiste y conecta con la siguiente acción.
|
|
163
|
+
5. Cada turno debe contener al menos UNA llamada real a herramienta de las anteriores. No respondas solo con texto. No respondas solo con TaskCreate.
|
|
164
|
+
6. Si algo falla, sigue ejecutando herramientas para diagnosticar y arreglar.
|
|
165
|
+
7. Cuando creas que el objetivo se cumple, dilo claramente con la evidencia concreta (rutas, comandos ejecutados, salidas observadas).
|
|
124
166
|
|
|
125
|
-
|
|
126
|
-
2. EJECUTA herramientas YA en este mismo turno: Read, Glob, Grep, Bash, Edit, Write, según haga falta. El usuario quiere ver acciones reales, no narración.
|
|
127
|
-
3. Cada turno debe contener al menos UNA llamada a herramienta hasta que el objetivo se cumpla.
|
|
128
|
-
4. Si algo falla, ejecuta otra herramienta para diagnosticar y arreglar — no te detengas a explicar el error.
|
|
129
|
-
5. Al final del turno, en una frase corta, di solo lo que descubriste o cambiaste (nunca lo que "vas a hacer").
|
|
130
|
-
6. Al cerrar el turno una evaluación automática decide si el objetivo se cumplió; si no, recibirás la razón concreta y seguirás trabajando.
|
|
131
|
-
7. Cuando creas que el objetivo se cumple, dilo claramente con la evidencia (rutas, comandos, salidas).
|
|
167
|
+
Tienes permiso bypass activado, así que los Bash/Edit/Write se ejecutan sin pedir confirmación. Aprovéchalo: ejecuta lo necesario sin dudar.
|
|
132
168
|
|
|
133
|
-
Empieza ahora
|
|
169
|
+
Empieza ahora: di en una frase qué vas a hacer primero y haz la llamada a la herramienta correspondiente — TÚ, no un sub-agente.`,
|
|
134
170
|
],
|
|
135
171
|
});
|
|
136
172
|
return null;
|
package/dist/src/query.js
CHANGED
|
@@ -1007,20 +1007,37 @@ async function* queryLoop(params, consumedCommandUuids) {
|
|
|
1007
1007
|
import('./services/objetivo/evaluator.js'),
|
|
1008
1008
|
import('./services/objetivo/types.js'),
|
|
1009
1009
|
]);
|
|
1010
|
+
// Mensaje visible mientras el evaluador corre — sin esto el usuario
|
|
1011
|
+
// mira un reloj sin saber si pasa algo. Toma 1-5s normalmente.
|
|
1012
|
+
yield createSystemMessage(`◎ Evaluando si el objetivo se cumplió (turno ${objetivoActivo.turnos + 1})...`);
|
|
1010
1013
|
const evaluacion = await evaluarObjetivo(objetivoActivo.meta, messagesForQuery, assistantMessages, toolUseContext.abortController.signal);
|
|
1011
1014
|
const turnoActual = objetivoActivo.turnos + 1;
|
|
1012
1015
|
const tiempoTranscurrido = fmtDuracion(Date.now() - objetivoActivo.iniciadoEn);
|
|
1013
1016
|
if (evaluacion?.cumplido) {
|
|
1014
1017
|
// Restaurar el modo de permisos previo si /objetivo lo había cambiado
|
|
1015
|
-
// a bypassPermissions al fijar la meta.
|
|
1016
|
-
|
|
1018
|
+
// a bypassPermissions al fijar la meta. También el flag global de
|
|
1019
|
+
// sessionBypassPermissionsMode (los dos viven en sitios distintos).
|
|
1020
|
+
const [{ applyPermissionUpdate: applyPermUpd }, bootstrapState] = await Promise.all([
|
|
1021
|
+
import('./utils/permissions/PermissionUpdate.js'),
|
|
1022
|
+
import('./bootstrap/state.js'),
|
|
1023
|
+
]);
|
|
1024
|
+
if (objetivoActivo.sessionBypassPreviamenteActivo !== undefined) {
|
|
1025
|
+
bootstrapState.setSessionBypassPermissionsMode(objetivoActivo.sessionBypassPreviamenteActivo);
|
|
1026
|
+
}
|
|
1017
1027
|
toolUseContext.setAppState(prev => {
|
|
1018
1028
|
const next = { ...prev, objetivo: undefined };
|
|
1019
1029
|
const modoPrevio = prev.objetivo?.modoPermisoPrevio;
|
|
1030
|
+
const bypassPrev = prev.objetivo?.bypassPreviamenteDisponible;
|
|
1020
1031
|
if (modoPrevio !== undefined &&
|
|
1021
1032
|
modoPrevio !== prev.toolPermissionContext.mode) {
|
|
1022
1033
|
next.toolPermissionContext = applyPermUpd(prev.toolPermissionContext, { type: 'setMode', mode: modoPrevio, destination: 'session' });
|
|
1023
1034
|
}
|
|
1035
|
+
if (bypassPrev !== undefined) {
|
|
1036
|
+
next.toolPermissionContext = {
|
|
1037
|
+
...next.toolPermissionContext,
|
|
1038
|
+
isBypassPermissionsModeAvailable: bypassPrev,
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1024
1041
|
return next;
|
|
1025
1042
|
});
|
|
1026
1043
|
yield createSystemMessage(`◎ Objetivo cumplido en ${tiempoTranscurrido} (${turnoActual} ${turnoActual === 1 ? 'turno' : 'turnos'}) — ${evaluacion.razon}`);
|
|
@@ -1041,12 +1058,16 @@ async function* queryLoop(params, consumedCommandUuids) {
|
|
|
1041
1058
|
content: `OBJETIVO ACTIVO (turno ${turnoActual}): "${objetivoActivo.meta}"\n\n` +
|
|
1042
1059
|
`El evaluador automático revisó el turno anterior y dice que aún falta:\n` +
|
|
1043
1060
|
`${evaluacion.razon}\n\n` +
|
|
1044
|
-
`Recordatorio
|
|
1045
|
-
`-
|
|
1046
|
-
`-
|
|
1061
|
+
`Recordatorio:\n` +
|
|
1062
|
+
`- Haz TÚ MISMO el trabajo en este chat. PROHIBIDO delegar al sub-agente con "Agent" / "Task" ni crear TaskCreate planificadores; el usuario quiere ver el trabajo aquí.\n` +
|
|
1063
|
+
`- Antes de cada acción, escribe UNA frase corta ("Reviso X", "Corro Y") y haz la llamada a la herramienta inmediatamente.\n` +
|
|
1064
|
+
`- Usa Read, Glob, Grep, Bash/PowerShell, Edit, Write — son las herramientas para ejecutar tú mismo.\n` +
|
|
1065
|
+
`- Después de cada tool result, en otra frase corta resume lo aprendido y enlaza con la siguiente acción.\n` +
|
|
1066
|
+
`- Cada turno debe contener al menos UNA llamada real a las herramientas anteriores.\n` +
|
|
1047
1067
|
`- Si algo falla, sigue ejecutando herramientas para diagnosticar y arreglar.\n` +
|
|
1048
|
-
`- Cuando creas que está cumplido, dilo claramente con evidencia concreta (rutas, comandos, salidas).\n
|
|
1049
|
-
|
|
1068
|
+
`- Cuando creas que está cumplido, dilo claramente con evidencia concreta (rutas, comandos, salidas).\n` +
|
|
1069
|
+
`- Tienes permiso bypass activado: los comandos no piden confirmación. Aprovéchalo.\n\n` +
|
|
1070
|
+
`Continúa ahora: di en una frase qué vas a hacer y ejecuta la herramienta — TÚ, no un sub-agente.`,
|
|
1050
1071
|
isMeta: true,
|
|
1051
1072
|
});
|
|
1052
1073
|
state = {
|
|
@@ -1063,7 +1084,41 @@ async function* queryLoop(params, consumedCommandUuids) {
|
|
|
1063
1084
|
};
|
|
1064
1085
|
continue;
|
|
1065
1086
|
}
|
|
1066
|
-
|
|
1087
|
+
else {
|
|
1088
|
+
// evaluacion === null (red/parse/timeout falló). En vez de terminar
|
|
1089
|
+
// el turno en silencio (lo que deja al usuario mirando un reloj que
|
|
1090
|
+
// no avanza), avisamos y forzamos una continuación genérica para
|
|
1091
|
+
// mantener vivo el loop autónomo.
|
|
1092
|
+
yield createSystemMessage(`◎ Continuando hacia el objetivo (turno ${turnoActual} · ${tiempoTranscurrido}) — el evaluador no respondió a tiempo, se asume "aún falta" y se continúa.`);
|
|
1093
|
+
const continuacionFallback = createUserMessage({
|
|
1094
|
+
content: `OBJETIVO ACTIVO (turno ${turnoActual}): "${objetivoActivo.meta}"\n\n` +
|
|
1095
|
+
`La evaluación automática del turno anterior falló o no respondió a tiempo. Asume que aún falta trabajo.\n\n` +
|
|
1096
|
+
`Recordatorio:\n` +
|
|
1097
|
+
`- Haz TÚ MISMO el trabajo en este chat. PROHIBIDO delegar al sub-agente con "Agent" / "Task".\n` +
|
|
1098
|
+
`- Antes de cada acción, escribe UNA frase corta y haz la llamada a la herramienta inmediatamente.\n` +
|
|
1099
|
+
`- Cada turno debe contener al menos UNA llamada real a Read/Glob/Grep/Bash/PowerShell/Edit/Write.\n` +
|
|
1100
|
+
`- Tienes permiso bypass activado, no se piden confirmaciones.\n\n` +
|
|
1101
|
+
`Continúa ahora ejecutando la siguiente acción concreta hacia el objetivo.`,
|
|
1102
|
+
isMeta: true,
|
|
1103
|
+
});
|
|
1104
|
+
state = {
|
|
1105
|
+
messages: [
|
|
1106
|
+
...messagesForQuery,
|
|
1107
|
+
...assistantMessages,
|
|
1108
|
+
continuacionFallback,
|
|
1109
|
+
],
|
|
1110
|
+
toolUseContext,
|
|
1111
|
+
autoCompactTracking: tracking,
|
|
1112
|
+
maxOutputTokensRecoveryCount: 0,
|
|
1113
|
+
hasAttemptedReactiveCompact: false,
|
|
1114
|
+
maxOutputTokensOverride: undefined,
|
|
1115
|
+
pendingToolUseSummary: undefined,
|
|
1116
|
+
stopHookActive: undefined,
|
|
1117
|
+
turnCount,
|
|
1118
|
+
transition: { reason: 'objetivo_continuation' },
|
|
1119
|
+
};
|
|
1120
|
+
continue;
|
|
1121
|
+
}
|
|
1067
1122
|
}
|
|
1068
1123
|
if (feature('TOKEN_BUDGET')) {
|
|
1069
1124
|
const decision = checkTokenBudget(budgetTracker, toolUseContext.agentId, getCurrentTurnTokenBudget(), getTurnOutputTokens());
|
|
@@ -11,6 +11,7 @@ const resultadoSchema = z.object({
|
|
|
11
11
|
razon: z.string(),
|
|
12
12
|
});
|
|
13
13
|
const MAX_CARACTERES_CONTEXTO = 4000;
|
|
14
|
+
const TIMEOUT_EVALUADOR_MS = 45_000;
|
|
14
15
|
/**
|
|
15
16
|
* Aplana el último intercambio en texto plano: mensaje del usuario, respuesta
|
|
16
17
|
* del agente y tool_results. Se trunca al final para mantener el call al
|
|
@@ -93,6 +94,15 @@ export async function evaluarObjetivo(meta, messagesForQuery, assistantMessages,
|
|
|
93
94
|
if (!contexto.trim()) {
|
|
94
95
|
return null;
|
|
95
96
|
}
|
|
97
|
+
// Timeout local: si el provider se cuelga (red lenta, modelo largo), el
|
|
98
|
+
// turno se queda bloqueado esperando. Mejor cortarlo, devolver null y dejar
|
|
99
|
+
// que el caller inyecte una continuación de fallback que mantenga vivo el
|
|
100
|
+
// loop autónomo.
|
|
101
|
+
const timeoutController = new AbortController();
|
|
102
|
+
const timeoutId = setTimeout(() => timeoutController.abort(), TIMEOUT_EVALUADOR_MS);
|
|
103
|
+
const composedSignal = signal.aborted ? signal : timeoutController.signal;
|
|
104
|
+
const onUserAbort = () => timeoutController.abort();
|
|
105
|
+
signal.addEventListener('abort', onUserAbort, { once: true });
|
|
96
106
|
try {
|
|
97
107
|
const response = await sideQuery({
|
|
98
108
|
querySource: 'objetivo_eval',
|
|
@@ -106,7 +116,7 @@ export async function evaluarObjetivo(meta, messagesForQuery, assistantMessages,
|
|
|
106
116
|
],
|
|
107
117
|
max_tokens: 512,
|
|
108
118
|
thinking: false,
|
|
109
|
-
signal,
|
|
119
|
+
signal: composedSignal,
|
|
110
120
|
skipSystemPromptPrefix: true,
|
|
111
121
|
});
|
|
112
122
|
const texto = extractTextContent(response.content);
|
|
@@ -126,4 +136,8 @@ export async function evaluarObjetivo(meta, messagesForQuery, assistantMessages,
|
|
|
126
136
|
});
|
|
127
137
|
return null;
|
|
128
138
|
}
|
|
139
|
+
finally {
|
|
140
|
+
clearTimeout(timeoutId);
|
|
141
|
+
signal.removeEventListener('abort', onUserAbort);
|
|
142
|
+
}
|
|
129
143
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iaforged/context-code",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.7",
|
|
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",
|