chocolatito-code 1.0.0 → 1.1.0

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 (102) hide show
  1. package/README.md +89 -0
  2. package/dist/agent/compaction.d.ts +65 -0
  3. package/dist/agent/compaction.js +134 -0
  4. package/dist/agent/context.d.ts +3 -0
  5. package/dist/agent/context.js +35 -0
  6. package/dist/agent/loop.d.ts +91 -2
  7. package/dist/agent/loop.js +536 -78
  8. package/dist/agent/loopDetector.d.ts +57 -0
  9. package/dist/agent/loopDetector.js +0 -0
  10. package/dist/agent/mentions.d.ts +13 -0
  11. package/dist/agent/mentions.js +49 -0
  12. package/dist/agent/repoMap.d.ts +117 -0
  13. package/dist/agent/repoMap.js +560 -0
  14. package/dist/agent/result.d.ts +64 -0
  15. package/dist/agent/result.js +48 -0
  16. package/dist/agent/retry.d.ts +35 -0
  17. package/dist/agent/retry.js +123 -4
  18. package/dist/agent/subagent.js +62 -15
  19. package/dist/agent/syntaxValidator.js +288 -52
  20. package/dist/agent/toolGate.d.ts +58 -0
  21. package/dist/agent/toolGate.js +111 -0
  22. package/dist/agent/tracker.js +4 -7
  23. package/dist/agent/undoManager.d.ts +44 -11
  24. package/dist/agent/undoManager.js +95 -24
  25. package/dist/agent/usageMeter.d.ts +56 -0
  26. package/dist/agent/usageMeter.js +51 -0
  27. package/dist/agent/verifier.d.ts +49 -0
  28. package/dist/agent/verifier.js +158 -0
  29. package/dist/config/constants.js +14 -0
  30. package/dist/config/engine.d.ts +19 -0
  31. package/dist/config/engine.js +35 -3
  32. package/dist/config/license.d.ts +5 -0
  33. package/dist/config/license.js +107 -9
  34. package/dist/config/limits.d.ts +28 -0
  35. package/dist/config/limits.js +51 -0
  36. package/dist/config/permissions.d.ts +76 -1
  37. package/dist/config/permissions.js +379 -23
  38. package/dist/config/quota.js +24 -1
  39. package/dist/config/updater.d.ts +68 -0
  40. package/dist/config/updater.js +215 -0
  41. package/dist/hooks/manager.js +159 -14
  42. package/dist/index.js +222 -34
  43. package/dist/mcp/client.d.ts +22 -0
  44. package/dist/mcp/client.js +93 -11
  45. package/dist/prompts/systemPrompt.js +12 -1
  46. package/dist/sessions/manager.d.ts +12 -0
  47. package/dist/sessions/manager.js +33 -5
  48. package/dist/sessions/resume.d.ts +36 -0
  49. package/dist/sessions/resume.js +43 -0
  50. package/dist/skills/manager.d.ts +1 -0
  51. package/dist/skills/manager.js +9 -0
  52. package/dist/tools/binary.d.ts +46 -0
  53. package/dist/tools/binary.js +100 -0
  54. package/dist/tools/browserCdp.js +13 -1
  55. package/dist/tools/browserExtension.d.ts +5 -0
  56. package/dist/tools/browserExtension.js +202 -18
  57. package/dist/tools/browserSession.d.ts +23 -0
  58. package/dist/tools/browserSession.js +28 -0
  59. package/dist/tools/computerUse.js +33 -1
  60. package/dist/tools/createImage.d.ts +11 -0
  61. package/dist/tools/createImage.js +13 -1
  62. package/dist/tools/definitions.js +9 -3
  63. package/dist/tools/editDiagnosis.d.ts +54 -0
  64. package/dist/tools/editDiagnosis.js +142 -0
  65. package/dist/tools/editFile.d.ts +1 -0
  66. package/dist/tools/editFile.js +118 -62
  67. package/dist/tools/findFiles.d.ts +21 -0
  68. package/dist/tools/findFiles.js +75 -6
  69. package/dist/tools/getSystemInfo.js +15 -7
  70. package/dist/tools/gitTools.d.ts +24 -0
  71. package/dist/tools/gitTools.js +87 -26
  72. package/dist/tools/moveCopyFile.d.ts +18 -1
  73. package/dist/tools/moveCopyFile.js +74 -4
  74. package/dist/tools/patchCascade.d.ts +57 -0
  75. package/dist/tools/patchCascade.js +338 -0
  76. package/dist/tools/runCommand.js +213 -25
  77. package/dist/tools/runner.js +57 -4
  78. package/dist/tools/toolDefsComputer.js +12 -2
  79. package/dist/tools/truncator.d.ts +27 -1
  80. package/dist/tools/truncator.js +117 -11
  81. package/dist/tools/viewFile.js +15 -11
  82. package/dist/tools/visionBridge.js +7 -0
  83. package/dist/tools/webSearch.d.ts +21 -0
  84. package/dist/tools/webSearch.js +125 -14
  85. package/dist/tools/writeFile.js +18 -1
  86. package/dist/ui/interrupt.d.ts +4 -1
  87. package/dist/ui/interrupt.js +56 -5
  88. package/dist/ui/keyboardGuard.d.ts +55 -0
  89. package/dist/ui/keyboardGuard.js +65 -0
  90. package/dist/ui/loopPrompt.d.ts +17 -0
  91. package/dist/ui/loopPrompt.js +59 -0
  92. package/dist/ui/permissionPrompt.js +84 -2
  93. package/dist/ui/prompt.d.ts +35 -0
  94. package/dist/ui/prompt.js +345 -125
  95. package/dist/ui/reasoningStream.d.ts +8 -0
  96. package/dist/ui/reasoningStream.js +99 -7
  97. package/extension/README.md +95 -74
  98. package/extension/background.js +598 -178
  99. package/extension/content.js +92 -9
  100. package/extension/manifest.json +2 -1
  101. package/extension/popup.js +28 -12
  102. package/package.json +1 -1
@@ -47,13 +47,25 @@ function readRc() {
47
47
  return {};
48
48
  }
49
49
  }
50
+ /**
51
+ * Devuelve null si se guardo, o el motivo del fallo si no.
52
+ *
53
+ * Antes se tragaba la excepcion entera. Con el perfil del usuario en un recurso
54
+ * de solo lectura, el archivo sin permisos o el disco lleno, activar decia
55
+ * "Licencia activada" y no guardaba nada: el arranque siguiente volvia a pedir
56
+ * la clave y consumia otra de las 3 activaciones del cliente, hasta bloquearle
57
+ * una licencia que era suya.
58
+ */
50
59
  function writeRc(patch) {
51
60
  try {
52
61
  const p = getGlobalConfigPath();
53
62
  const actual = readRc();
54
63
  fs.writeFileSync(p, JSON.stringify({ ...actual, ...patch }, null, 2), "utf-8");
64
+ return null;
65
+ }
66
+ catch (err) {
67
+ return err?.code || err?.message || String(err);
55
68
  }
56
- catch { }
57
69
  }
58
70
  export function getStoredLicense() {
59
71
  return readRc().license || null;
@@ -63,6 +75,30 @@ function nombreDeMaquina() {
63
75
  const host = os.hostname() || "equipo";
64
76
  return `${host} (${os.platform()})`;
65
77
  }
78
+ /**
79
+ * Codigos que significan "no he podido preguntar", no "te digo que no".
80
+ *
81
+ * 429 es el limite de peticiones documentado de Lemon Squeezy: basta con que
82
+ * dos equipos del mismo cliente arranquen a la vez. 408, 409, 425 y todo 5xx
83
+ * son caidas o reintentos. Ninguno opina sobre la licencia de nadie.
84
+ */
85
+ function esCaidaTemporal(status) {
86
+ return status === 408 || status === 409 || status === 425 || status === 429 || status >= 500;
87
+ }
88
+ /**
89
+ * Solo la API de licencias contesta con estos campos.
90
+ *
91
+ * Un {"error":"Not Found"} de un cambio de ruta, o el {"message":"Server
92
+ * Error"} de un 500, son JSON perfectamente validos y parsean igual de bien,
93
+ * pero no son un veredicto sobre ninguna clave.
94
+ */
95
+ function esRespuestaDeLicencia(data) {
96
+ if (!data || typeof data !== "object")
97
+ return false;
98
+ return (typeof data.activated === "boolean" ||
99
+ typeof data.valid === "boolean" ||
100
+ typeof data.deactivated === "boolean");
101
+ }
66
102
  async function post(endpoint, params) {
67
103
  const res = await fetch(`${API}/${endpoint}`, {
68
104
  method: "POST",
@@ -73,8 +109,33 @@ async function post(endpoint, params) {
73
109
  body: new URLSearchParams(params).toString(),
74
110
  signal: AbortSignal.timeout(15_000),
75
111
  });
76
- // 400 y 404 traen cuerpo JSON con el motivo; no son fallos de red.
77
- return await res.json();
112
+ // Aqui estaba la mitad que faltaba del punto 2 de arriba: se hacia res.json()
113
+ // sin mirar NUNCA el codigo. Solo contaba como "no he podido preguntar" que
114
+ // fetch lanzase. Una respuesta de error CON cuerpo JSON —el 429 del limite de
115
+ // peticiones, un 500 de una caida, un 404 por cambio de ruta— llegaba abajo
116
+ // como valid/activated ausentes, o sea como "la licencia no vale", y echaba
117
+ // al cliente que habia pagado sin darle el margen de 7 dias.
118
+ if (esCaidaTemporal(res.status)) {
119
+ throw new Error(res.status === 429
120
+ ? "Lemon Squeezy esta limitando las peticiones (HTTP 429)"
121
+ : `Lemon Squeezy no responde bien (HTTP ${res.status})`);
122
+ }
123
+ let data;
124
+ try {
125
+ data = await res.json();
126
+ }
127
+ catch {
128
+ // Pasa con las paginas HTML que mete un balanceador delante de la API.
129
+ throw new Error(`Respuesta ilegible de Lemon Squeezy (HTTP ${res.status})`);
130
+ }
131
+ // 400 y 404 SI traen el motivo en el cuerpo cuando hablan de la clave, y esos
132
+ // se dejan pasar para que traducir() los explique: siempre vienen con su
133
+ // activated/valid/deactivated. Un 4xx que no trae ninguno de los tres no esta
134
+ // hablando de la licencia, asi que tampoco puede condenarla.
135
+ if (res.status >= 400 && !esRespuestaDeLicencia(data)) {
136
+ throw new Error(`Lemon Squeezy respondio HTTP ${res.status} sin decir nada de la clave`);
137
+ }
138
+ return data;
78
139
  }
79
140
  /** La clave tiene que ser de NUESTRA tienda, no de una cualquiera. */
80
141
  function esDeNuestraTienda(data) {
@@ -112,11 +173,28 @@ export async function activate(licenseKey) {
112
173
  // Chocolatito y el cliente no necesita clave de DeepSeek. Sin escribir esto,
113
174
  // el CLI seguiria apuntando a api.deepseek.com y pidiendole al suscriptor una
114
175
  // clave que no tiene por que tener.
115
- const yaConfigurado = readRc();
116
- const parche = { license };
117
- if (yaConfigurado.managed !== false)
118
- parche.managed = true;
119
- writeRc(parche);
176
+ //
177
+ // Se escribe SIEMPRE. Antes se respetaba un managed:false previo, y el unico
178
+ // sitio de todo el producto que escribe managed:false es deactivate(): quien
179
+ // liberaba el equipo y volvia a activar la misma clave aqui mismo se quedaba
180
+ // fuera del modo suscripcion, con el CLI pidiendole una clave de DeepSeek que
181
+ // no tiene o, peor, gastando la suya si la tenia guardada de antes.
182
+ const fallo = writeRc({ license, managed: true });
183
+ if (fallo) {
184
+ // La activacion ya esta consumida en Lemon Squeezy y no hay donde guardarla,
185
+ // asi que el arranque siguiente pediria la clave otra vez: tres vueltas y el
186
+ // cliente se queda bloqueado con una clave suya. Se devuelve la activacion
187
+ // antes de contarlo, para que reintentar no cueste nada.
188
+ try {
189
+ await post("deactivate", { license_key: license.key, instance_id: license.instanceId });
190
+ }
191
+ catch { }
192
+ return {
193
+ kind: "unpersisted",
194
+ reason: `Tu licencia es valida, pero no se pudo guardar en ${getGlobalConfigPath()} (${fallo}). ` +
195
+ "Arregla los permisos de ese archivo y vuelve a intentarlo.",
196
+ };
197
+ }
120
198
  return { kind: "ok", license };
121
199
  }
122
200
  export async function validate(license) {
@@ -142,6 +220,9 @@ export async function validate(license) {
142
220
  customerEmail: data?.meta?.customer_email || license.customerEmail,
143
221
  productName: data?.meta?.product_name || license.productName,
144
222
  };
223
+ // Si aqui no se puede escribir no se corta nada: la licencia ya esta en el
224
+ // archivo, solo se queda sin refrescar la fecha y se volvera a preguntar en el
225
+ // arranque siguiente. No cuesta activaciones, al contrario que en activate().
145
226
  writeRc({ license: actualizada });
146
227
  return { kind: "ok", license: actualizada };
147
228
  }
@@ -158,7 +239,18 @@ export async function deactivate() {
158
239
  if (!data?.deactivated) {
159
240
  return { ok: false, message: traducir(data?.error) };
160
241
  }
161
- writeRc({ license: undefined, managed: false });
242
+ const fallo = writeRc({ license: undefined, managed: false });
243
+ if (fallo) {
244
+ // Lemon Squeezy ya ha borrado la instancia. Si encima decimos que todo fue
245
+ // bien, la licencia se queda escrita en el archivo y el arranque siguiente
246
+ // valida una activacion que ya no existe: "esta maquina ya no figura como
247
+ // activada" sin que el usuario haya hecho nada raro.
248
+ return {
249
+ ok: false,
250
+ message: `Se libero el equipo en Lemon Squeezy, pero no se pudo actualizar ${getGlobalConfigPath()} (${fallo}): ` +
251
+ "la licencia sigue escrita ahi. Borra ese archivo a mano o el proximo arranque fallara al comprobarla.",
252
+ };
253
+ }
162
254
  return {
163
255
  ok: true,
164
256
  message: "Licencia liberada en este equipo. Ya puedes activarla en otro.",
@@ -256,6 +348,12 @@ export async function ensureLicense() {
256
348
  console.log(chalk.red(`\n✖ ${res.reason}\n`));
257
349
  return false;
258
350
  }
351
+ if (res.kind === "unpersisted") {
352
+ // Decir "✔ Licencia activada" aqui era mentira con consecuencias: sin
353
+ // guardar nada, cada arranque volvia a activar y se comia una activacion.
354
+ console.log(chalk.red(`\n✖ ${res.reason}\n`));
355
+ return false;
356
+ }
259
357
  console.log(chalk.red(`\n✖ No se pudo contactar con Lemon Squeezy para activar (${res.reason}).`) +
260
358
  chalk.gray("\n Comprueba tu conexión e inténtalo otra vez.\n"));
261
359
  return false;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Presupuestos de ejecucion.
3
+ *
4
+ * Estaban escritos en el codigo: 30 iteraciones en el bucle principal y 14 en
5
+ * los subagentes. Son numeros razonables para la tarea media y equivocados en
6
+ * los dos extremos: en una refactorizacion grande el turno se corta a medias
7
+ * ("se agoto el limite de 30 iteraciones") y hay que repetir la peticion para
8
+ * que siga; en un guion desatendido, 30 vueltas de un agente perdido son 30
9
+ * llamadas pagadas antes de que nadie mire.
10
+ *
11
+ * Quien conoce su tarea y su presupuesto es el usuario, asi que se saca a
12
+ * configuracion. Con topes: un limite de 500 iteraciones no es una eleccion
13
+ * informada, es un cero de mas.
14
+ */
15
+ export interface Limites {
16
+ /** Vueltas del bucle principal en un turno. */
17
+ iteracionesPorTurno: number;
18
+ /** Vueltas de un subagente. */
19
+ iteracionesDeSubagente: number;
20
+ }
21
+ export declare const LIMITES_POR_DEFECTO: Limites;
22
+ /**
23
+ * Lee los limites de la configuracion. El proyecto puede bajarlos o subirlos:
24
+ * a diferencia de los permisos, aqui no hay nada que elevar —un limite mas
25
+ * alto no da acceso a nada, solo gasta mas cuota del propio usuario, y el corte
26
+ * duro sigue siendo el tope de 200.
27
+ */
28
+ export declare function leerLimites(cwd?: string): Limites;
@@ -0,0 +1,51 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ export const LIMITES_POR_DEFECTO = {
5
+ iteracionesPorTurno: 30,
6
+ iteracionesDeSubagente: 14,
7
+ };
8
+ const MINIMO = 3;
9
+ const MAXIMO = 200;
10
+ function enRango(valor, porDefecto) {
11
+ const n = typeof valor === "string" ? Number(valor) : valor;
12
+ if (typeof n !== "number" || !Number.isFinite(n))
13
+ return porDefecto;
14
+ return Math.max(MINIMO, Math.min(MAXIMO, Math.round(n)));
15
+ }
16
+ /**
17
+ * Lee los limites de la configuracion. El proyecto puede bajarlos o subirlos:
18
+ * a diferencia de los permisos, aqui no hay nada que elevar —un limite mas
19
+ * alto no da acceso a nada, solo gasta mas cuota del propio usuario, y el corte
20
+ * duro sigue siendo el tope de 200.
21
+ */
22
+ export function leerLimites(cwd = process.cwd()) {
23
+ const limites = { ...LIMITES_POR_DEFECTO };
24
+ for (const ruta of [
25
+ path.join(os.homedir(), ".chocolatito", "settings.json"),
26
+ path.join(cwd, ".chocolatito", "settings.json"),
27
+ ]) {
28
+ try {
29
+ if (!fs.existsSync(ruta))
30
+ continue;
31
+ const parsed = JSON.parse(fs.readFileSync(ruta, "utf-8"));
32
+ const l = parsed?.limits;
33
+ if (!l)
34
+ continue;
35
+ if (l.maxIterations !== undefined) {
36
+ limites.iteracionesPorTurno = enRango(l.maxIterations, limites.iteracionesPorTurno);
37
+ }
38
+ if (l.subagentIterations !== undefined) {
39
+ limites.iteracionesDeSubagente = enRango(l.subagentIterations, limites.iteracionesDeSubagente);
40
+ }
41
+ }
42
+ catch { }
43
+ }
44
+ if (process.env.CHOCOLATITO_MAX_ITERATIONS) {
45
+ limites.iteracionesPorTurno = enRango(process.env.CHOCOLATITO_MAX_ITERATIONS, limites.iteracionesPorTurno);
46
+ }
47
+ if (process.env.CHOCOLATITO_SUBAGENT_ITERATIONS) {
48
+ limites.iteracionesDeSubagente = enRango(process.env.CHOCOLATITO_SUBAGENT_ITERATIONS, limites.iteracionesDeSubagente);
49
+ }
50
+ return limites;
51
+ }
@@ -6,18 +6,60 @@ export interface SettingsConfig {
6
6
  denyPatterns?: string[];
7
7
  };
8
8
  }
9
+ /**
10
+ * Una ruta esta fuera del proyecto si, resuelta, no cuelga de la raiz.
11
+ *
12
+ * Vale para rutas absolutas (C:\Windows\..., ~/.ssh/...) y para las relativas
13
+ * con ".." que salen del arbol, que es la forma que no se ve venir leyendo la
14
+ * llamada por encima. path.relative en Windows ya compara sin distinguir
15
+ * mayusculas y devuelve la ruta entera cuando cambia de unidad.
16
+ */
17
+ export declare function isOutsideProject(projectRoot: string, target: unknown): boolean;
18
+ /**
19
+ * create_image mete dos cosas muy distintas en el mismo argumento: un SVG, que
20
+ * solo se escribe en disco, o codigo Python, que se guarda en un .py y se lanza
21
+ * con `python`. Quien lo distingue es esta funcion, y la usan tanto la
22
+ * herramienta como la tabla de permisos: si cada una detectara el SVG a su
23
+ * manera, bastaria una diferencia entre las dos para colar codigo por la rama
24
+ * que no pregunta.
25
+ */
26
+ export declare function isSvgSource(script: unknown): boolean;
9
27
  /**
10
28
  * Detecta comandos destructivos o de alto riesgo que siempre deben pedir confirmación.
11
29
  * Comandos seguros de desarrollo (python, npm, pytest, tsc, git status, etc.) no interrumpen al usuario.
12
30
  */
13
31
  export declare function isDangerousCommand(command: string): boolean;
32
+ /**
33
+ * Firma con la que se recuerda un comando aprobado.
34
+ *
35
+ * El bucle guardaba command.split(" ")[0], o sea solo el binario: aprobar un
36
+ * "git status" dejaba escrito el patron "git" y, como el allow autoriza por
37
+ * prefijo, a partir de ahi pasaban sin dialogo tambien "git push --force" y
38
+ * "git reset --hard", en todas las sesiones del proyecto.
39
+ *
40
+ * La firma es binario + subcomando ("git status", "npm run build"). Si el
41
+ * comando no trae subcomando sino opciones se guarda entero: ensancharlo hasta
42
+ * el binario es justo el fallo que se esta arreglando.
43
+ */
44
+ export declare function commandSignature(command: string): string;
14
45
  export declare class PermissionManager {
15
46
  mode: PermissionMode;
16
47
  allowPatterns: Set<string>;
17
48
  denyPatterns: Set<string>;
18
49
  private settingsFilePath;
50
+ /** Raiz del proyecto: el limite dentro del cual escribir no interrumpe. */
51
+ projectRoot: string;
19
52
  constructor(initialMode?: PermissionMode, cwd?: string);
20
53
  loadSettings(): void;
54
+ /**
55
+ * Aplica una capa de settings.
56
+ *
57
+ * La capa del proyecto solo puede RESTRINGIR. Antes se aplicaba despues de la
58
+ * global y fijaba el modo sin mas, asi que un repositorio clonado con
59
+ * {"permissions":{"mode":"bypass"}} en su .chocolatito/settings.json apagaba
60
+ * los dialogos en la maquina de quien lo abriera, sin decir nada. Elevar
61
+ * permisos es cosa del usuario: su settings global o la linea de comandos.
62
+ */
21
63
  private applySettings;
22
64
  saveAllowPattern(pattern: string): void;
23
65
  setMode(mode: PermissionMode): void;
@@ -27,7 +69,36 @@ export declare class PermissionManager {
27
69
  * Modo plan = SOLO LECTURA de verdad.
28
70
  */
29
71
  isBlockedInPlanMode(toolName: string, args: Record<string, any>): string | null;
30
- requiresConfirmation(toolName: string, args: Record<string, any>): boolean;
72
+ /**
73
+ * `cwd` es contra que directorio se mide "dentro del proyecto". Tiene que ser
74
+ * el MISMO que recibe executeToolCall, porque es contra el que las
75
+ * herramientas resuelven las rutas relativas: si aqui se midiera contra la
76
+ * raiz de la sesion y el bucle escribiera desde otro directorio (currentCwd
77
+ * cambia con cd), un "x.txt" saldria "dentro" en la comprobacion y caeria
78
+ * fuera al escribirse. Sin argumento se usa la raiz con la que se construyo.
79
+ */
80
+ requiresConfirmation(toolName: string, args: Record<string, any>, cwd?: string): boolean;
81
+ /**
82
+ * Rutas de la llamada que caen fuera de la raiz del proyecto. Vacio si todo
83
+ * queda dentro (o si la herramienta no escribe en disco: leer fuera del
84
+ * proyecto es legitimo y no se pregunta).
85
+ */
86
+ rutasFueraDelProyecto(toolName: string, args: Record<string, any>, cwd?: string): string[];
87
+ /**
88
+ * Patron denegado: devuelve cual, o null. Corta la llamada entera; no es un
89
+ * "pregunta antes".
90
+ *
91
+ * denyPatterns se leia del settings.json y se volvia a guardar tal cual, pero
92
+ * NADIE lo consultaba: la unica lista que se miraba era la de allow. Es decir,
93
+ * prohibir algo por configuracion no hacia absolutamente nada.
94
+ *
95
+ * La coincidencia es la misma que la del allow (firma exacta o prefijo de
96
+ * comando) con una diferencia deliberada: un allow no puede autorizar lo que
97
+ * venga encadenado detras, pero un deny si tiene que alcanzarlo. Si no,
98
+ * "echo hola && git push --force" se saltaria un deny de "git push" con solo
99
+ * poner algo inocente delante.
100
+ */
101
+ deniedBy(toolName: string, args: Record<string, any>): string | null;
31
102
  /**
32
103
  * Un patron autorizado vale para ESE comando, no para cualquiera que lo
33
104
  * contenga. Con la comparacion por subcadena de antes, autorizar "git status"
@@ -35,4 +106,8 @@ export declare class PermissionManager {
35
106
  * apareciera en algun sitio de la linea.
36
107
  */
37
108
  private isPatternAllowed;
109
+ /** Firma exacta de la llamada ("run_command:npm test") o ruta/accion exacta. */
110
+ private coincideExacta;
111
+ /** Prefijo de comando ("npm", "git status"), solo al principio de la linea. */
112
+ private coincidePrefijo;
38
113
  }