chocolatito-code 1.0.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 (140) hide show
  1. package/.mcp.json.ejemplo +19 -0
  2. package/COMPUTER-USE.md +484 -0
  3. package/LICENSE +21 -0
  4. package/README.md +186 -0
  5. package/dist/agent/context.d.ts +9 -0
  6. package/dist/agent/context.js +123 -0
  7. package/dist/agent/fileTracker.d.ts +16 -0
  8. package/dist/agent/fileTracker.js +53 -0
  9. package/dist/agent/loop.d.ts +25 -0
  10. package/dist/agent/loop.js +524 -0
  11. package/dist/agent/mentions.d.ts +11 -0
  12. package/dist/agent/mentions.js +43 -0
  13. package/dist/agent/retry.d.ts +17 -0
  14. package/dist/agent/retry.js +57 -0
  15. package/dist/agent/subagent.d.ts +2 -0
  16. package/dist/agent/subagent.js +116 -0
  17. package/dist/agent/syntaxValidator.d.ts +5 -0
  18. package/dist/agent/syntaxValidator.js +71 -0
  19. package/dist/agent/tracker.d.ts +19 -0
  20. package/dist/agent/tracker.js +38 -0
  21. package/dist/agent/undoManager.d.ts +37 -0
  22. package/dist/agent/undoManager.js +95 -0
  23. package/dist/config/constants.d.ts +77 -0
  24. package/dist/config/constants.js +146 -0
  25. package/dist/config/engine.d.ts +49 -0
  26. package/dist/config/engine.js +86 -0
  27. package/dist/config/env.d.ts +13 -0
  28. package/dist/config/env.js +103 -0
  29. package/dist/config/license.d.ts +39 -0
  30. package/dist/config/license.js +306 -0
  31. package/dist/config/permissions.d.ts +38 -0
  32. package/dist/config/permissions.js +239 -0
  33. package/dist/config/quota.d.ts +58 -0
  34. package/dist/config/quota.js +156 -0
  35. package/dist/hooks/manager.d.ts +60 -0
  36. package/dist/hooks/manager.js +193 -0
  37. package/dist/index.d.ts +2 -0
  38. package/dist/index.js +470 -0
  39. package/dist/mcp/client.d.ts +55 -0
  40. package/dist/mcp/client.js +282 -0
  41. package/dist/mcp/manager.d.ts +26 -0
  42. package/dist/mcp/manager.js +167 -0
  43. package/dist/memory/manager.d.ts +9 -0
  44. package/dist/memory/manager.js +59 -0
  45. package/dist/prompts/systemPrompt.d.ts +5 -0
  46. package/dist/prompts/systemPrompt.js +177 -0
  47. package/dist/sessions/manager.d.ts +21 -0
  48. package/dist/sessions/manager.js +54 -0
  49. package/dist/skills/manager.d.ts +14 -0
  50. package/dist/skills/manager.js +86 -0
  51. package/dist/tools/browserCdp.d.ts +26 -0
  52. package/dist/tools/browserCdp.js +610 -0
  53. package/dist/tools/browserExtension.d.ts +58 -0
  54. package/dist/tools/browserExtension.js +479 -0
  55. package/dist/tools/browserLive.d.ts +12 -0
  56. package/dist/tools/browserLive.js +265 -0
  57. package/dist/tools/changeDir.d.ts +5 -0
  58. package/dist/tools/changeDir.js +63 -0
  59. package/dist/tools/computerUse.d.ts +58 -0
  60. package/dist/tools/computerUse.js +567 -0
  61. package/dist/tools/createImage.d.ts +1 -0
  62. package/dist/tools/createImage.js +34 -0
  63. package/dist/tools/definitions.d.ts +8 -0
  64. package/dist/tools/definitions.js +401 -0
  65. package/dist/tools/deleteFile.d.ts +1 -0
  66. package/dist/tools/deleteFile.js +17 -0
  67. package/dist/tools/editFile.d.ts +5 -0
  68. package/dist/tools/editFile.js +88 -0
  69. package/dist/tools/findFiles.d.ts +1 -0
  70. package/dist/tools/findFiles.js +33 -0
  71. package/dist/tools/getSystemInfo.d.ts +1 -0
  72. package/dist/tools/getSystemInfo.js +23 -0
  73. package/dist/tools/gitTools.d.ts +3 -0
  74. package/dist/tools/gitTools.js +47 -0
  75. package/dist/tools/grepSearch.d.ts +1 -0
  76. package/dist/tools/grepSearch.js +37 -0
  77. package/dist/tools/listDir.d.ts +1 -0
  78. package/dist/tools/listDir.js +33 -0
  79. package/dist/tools/moveCopyFile.d.ts +1 -0
  80. package/dist/tools/moveCopyFile.js +21 -0
  81. package/dist/tools/runCommand.d.ts +1 -0
  82. package/dist/tools/runCommand.js +61 -0
  83. package/dist/tools/runner.d.ts +5 -0
  84. package/dist/tools/runner.js +177 -0
  85. package/dist/tools/safety.d.ts +17 -0
  86. package/dist/tools/safety.js +33 -0
  87. package/dist/tools/todoTool.d.ts +8 -0
  88. package/dist/tools/todoTool.js +27 -0
  89. package/dist/tools/toolDefsComputer.d.ts +13 -0
  90. package/dist/tools/toolDefsComputer.js +205 -0
  91. package/dist/tools/truncator.d.ts +11 -0
  92. package/dist/tools/truncator.js +24 -0
  93. package/dist/tools/viewFile.d.ts +1 -0
  94. package/dist/tools/viewFile.js +45 -0
  95. package/dist/tools/visionBridge.d.ts +37 -0
  96. package/dist/tools/visionBridge.js +107 -0
  97. package/dist/tools/webFetch.d.ts +1 -0
  98. package/dist/tools/webFetch.js +25 -0
  99. package/dist/tools/webSearch.d.ts +1 -0
  100. package/dist/tools/webSearch.js +35 -0
  101. package/dist/tools/win/hostClient.d.ts +31 -0
  102. package/dist/tools/win/hostClient.js +158 -0
  103. package/dist/tools/win/hostScript.d.ts +1 -0
  104. package/dist/tools/win/hostScript.js +925 -0
  105. package/dist/tools/writeFile.d.ts +1 -0
  106. package/dist/tools/writeFile.js +13 -0
  107. package/dist/ui/banner.d.ts +10 -0
  108. package/dist/ui/banner.js +165 -0
  109. package/dist/ui/diffViewer.d.ts +1 -0
  110. package/dist/ui/diffViewer.js +60 -0
  111. package/dist/ui/interrupt.d.ts +17 -0
  112. package/dist/ui/interrupt.js +156 -0
  113. package/dist/ui/markdownStream.d.ts +23 -0
  114. package/dist/ui/markdownStream.js +0 -0
  115. package/dist/ui/modes.d.ts +28 -0
  116. package/dist/ui/modes.js +57 -0
  117. package/dist/ui/permissionPrompt.d.ts +35 -0
  118. package/dist/ui/permissionPrompt.js +271 -0
  119. package/dist/ui/prompt.d.ts +7 -0
  120. package/dist/ui/prompt.js +466 -0
  121. package/dist/ui/reasoningStream.d.ts +6 -0
  122. package/dist/ui/reasoningStream.js +38 -0
  123. package/dist/ui/renderer.d.ts +10 -0
  124. package/dist/ui/renderer.js +142 -0
  125. package/dist/ui/spinner.d.ts +24 -0
  126. package/dist/ui/spinner.js +111 -0
  127. package/dist/ui/theme.d.ts +36 -0
  128. package/dist/ui/theme.js +37 -0
  129. package/dist/ui/typeAhead.d.ts +31 -0
  130. package/dist/ui/typeAhead.js +87 -0
  131. package/extension/README.md +74 -0
  132. package/extension/background.js +575 -0
  133. package/extension/content.js +446 -0
  134. package/extension/manifest.json +36 -0
  135. package/extension/popup.html +22 -0
  136. package/extension/popup.js +12 -0
  137. package/package.json +66 -0
  138. package/skills/complex-refactor/SKILL.md +13 -0
  139. package/skills/deep-debugger/SKILL.md +14 -0
  140. package/skills/software-architect/SKILL.md +20 -0
@@ -0,0 +1 @@
1
+ export declare function viewFile(filePath: string, startLine?: number, endLine?: number, cwd?: string): Promise<string>;
@@ -0,0 +1,45 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ export async function viewFile(filePath, startLine, endLine, cwd = process.cwd()) {
4
+ const resolved = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
5
+ if (!fs.existsSync(resolved)) {
6
+ return `Error: El archivo "${filePath}" no existe.`;
7
+ }
8
+ const stat = fs.statSync(resolved);
9
+ if (stat.isDirectory()) {
10
+ return `Error: "${filePath}" es un directorio, usa list_dir en su lugar.`;
11
+ }
12
+ const BINARY_EXTENSIONS = new Set([
13
+ ".png", ".jpg", ".jpeg", ".gif", ".webp", ".ico", ".svgz",
14
+ ".zip", ".tar", ".gz", ".7z", ".rar",
15
+ ".pdf", ".exe", ".dll", ".so", ".dylib", ".bin",
16
+ ".mp4", ".mov", ".avi", ".mkv", ".mp3", ".wav",
17
+ ".woff", ".woff2", ".ttf", ".eot"
18
+ ]);
19
+ const ext = path.extname(resolved).toLowerCase();
20
+ if (BINARY_EXTENSIONS.has(ext)) {
21
+ return `Aviso: "${filePath}" es un archivo binario (${ext.slice(1).toUpperCase()}) y no contiene texto plano legible. No intentes leerlo como texto.`;
22
+ }
23
+ const content = fs.readFileSync(resolved, "utf-8");
24
+ const lines = content.split(/\r?\n/);
25
+ const totalLines = lines.length;
26
+ const isUnbounded = !startLine && !endLine;
27
+ const maxInitialLines = 400;
28
+ let sLine = Math.max(1, startLine || 1);
29
+ let eLine = Math.min(totalLines, endLine || (isUnbounded && totalLines > maxInitialLines ? maxInitialLines : totalLines));
30
+ if (sLine > totalLines) {
31
+ return `Error: startLine (${sLine}) supera el total de líneas (${totalLines}).`;
32
+ }
33
+ const sliced = lines.slice(sLine - 1, eLine);
34
+ const numbered = sliced.map((line, idx) => {
35
+ const lineNum = sLine + idx;
36
+ return `${lineNum.toString().padStart(4, " ")} | ${line}`;
37
+ });
38
+ const overflowNotice = isUnbounded && totalLines > maxInitialLines
39
+ ? `\n\n[... Archivo extenso: mostrando primeras ${maxInitialLines} de ${totalLines} líneas. Especifica startLine y endLine para inspeccionar el resto ...]`
40
+ : "";
41
+ return [
42
+ `--- Archivo: ${path.relative(cwd, resolved)} (Líneas ${sLine}-${eLine} de ${totalLines}) ---`,
43
+ numbered.join("\n") + overflowNotice,
44
+ ].join("\n");
45
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Puente de vision.
3
+ *
4
+ * El modelo principal del bucle (deepseek-v4-flash / -pro) es de texto: una
5
+ * captura de pantalla no le dice absolutamente nada. Antes el agente tomaba la
6
+ * foto, recibia "captura guardada, 340 KB" y a partir de ahi hacia clic en
7
+ * coordenadas inventadas. De ahi que "siempre abriera lo que no es".
8
+ *
9
+ * Aqui la captura se manda a un modelo multimodal (deepseek-v4-flash-vision-exp)
10
+ * que devuelve TEXTO: o una descripcion de lo que hay en pantalla, o las
11
+ * coordenadas del elemento buscado. Ese texto si lo entiende el bucle principal.
12
+ */
13
+ export interface VisionResult {
14
+ ok: boolean;
15
+ text?: string;
16
+ error?: string;
17
+ usage?: {
18
+ prompt: number;
19
+ completion: number;
20
+ };
21
+ }
22
+ /** Describe lo que se ve, orientado a decidir el siguiente paso. */
23
+ export declare function describeScreen(apiKey: string, imagePath: string, question?: string): Promise<VisionResult>;
24
+ export interface LocateResult {
25
+ ok: boolean;
26
+ x?: number;
27
+ y?: number;
28
+ found?: boolean;
29
+ reason?: string;
30
+ error?: string;
31
+ }
32
+ /**
33
+ * Localiza un elemento y devuelve su centro EN PIXELES DE LA IMAGEN.
34
+ * La conversion a coordenadas de pantalla la hace quien llama, que es el unico
35
+ * que conoce el factor de escala y el origen de la captura.
36
+ */
37
+ export declare function locateElement(apiKey: string, imagePath: string, target: string, imageWidth: number, imageHeight: number): Promise<LocateResult>;
@@ -0,0 +1,107 @@
1
+ import fs from "node:fs";
2
+ import { VISION_MODEL } from "../config/constants.js";
3
+ import { createEngineClient } from "../config/engine.js";
4
+ function client(apiKey) {
5
+ return createEngineClient(apiKey, "vision");
6
+ }
7
+ function toDataUrl(imagePath) {
8
+ const buf = fs.readFileSync(imagePath);
9
+ return `data:image/png;base64,${buf.toString("base64")}`;
10
+ }
11
+ async function ask(apiKey, prompt, imagePath, maxTokens) {
12
+ if (!apiKey)
13
+ return { ok: false, error: "No hay API key disponible para el modelo de vision." };
14
+ if (!fs.existsSync(imagePath))
15
+ return { ok: false, error: `No existe la imagen "${imagePath}".` };
16
+ try {
17
+ const res = await client(apiKey).chat.completions.create({
18
+ model: VISION_MODEL,
19
+ max_tokens: maxTokens,
20
+ temperature: 0,
21
+ messages: [
22
+ {
23
+ role: "user",
24
+ content: [
25
+ { type: "text", text: prompt },
26
+ { type: "image_url", image_url: { url: toDataUrl(imagePath) } },
27
+ ],
28
+ },
29
+ ],
30
+ });
31
+ const msg = res.choices[0]?.message;
32
+ // El modelo de vision razona antes de responder. Si el presupuesto de tokens
33
+ // se agota razonando, 'content' llega vacio y el agente se queda sin ojos sin
34
+ // enterarse: en ese caso se aprovecha el razonamiento como respuesta.
35
+ let text = (msg?.content || "").trim();
36
+ if (!text && typeof msg?.reasoning_content === "string") {
37
+ text = msg.reasoning_content.trim();
38
+ }
39
+ if (!text) {
40
+ return { ok: false, error: "el modelo de vision no devolvio texto (presupuesto de tokens agotado)." };
41
+ }
42
+ if (res.choices[0]?.finish_reason === "length") {
43
+ text += "\n[La descripcion se corto por longitud; pregunta algo mas concreto si necesitas mas detalle.]";
44
+ }
45
+ return {
46
+ ok: true,
47
+ text,
48
+ usage: {
49
+ prompt: res.usage?.prompt_tokens || 0,
50
+ completion: res.usage?.completion_tokens || 0,
51
+ },
52
+ };
53
+ }
54
+ catch (err) {
55
+ return { ok: false, error: `Fallo el modelo de vision: ${err?.message || String(err)}` };
56
+ }
57
+ }
58
+ /** Describe lo que se ve, orientado a decidir el siguiente paso. */
59
+ export async function describeScreen(apiKey, imagePath, question) {
60
+ const q = question && question.trim().length > 0 ? question.trim() : "Describe el estado actual de la pantalla.";
61
+ const prompt = `Eres los ojos de un agente que controla este ordenador. Observa la captura y responde en espanol, ` +
62
+ `de forma concreta y sin adornos.\n\n` +
63
+ `Pregunta: ${q}\n\n` +
64
+ `Incluye siempre: que aplicacion y que pantalla concreta se ve, que elementos accionables hay ` +
65
+ `(botones, campos, pestanas) con su texto exacto, y si hay dialogos, errores o cargas en curso. ` +
66
+ `Si algo esta a medio cargar, dilo explicitamente. No inventes elementos que no aparezcan.\n` +
67
+ `Se breve: como mucho 12 lineas. Nada de preambulos.`;
68
+ return ask(apiKey, prompt, imagePath, 1600);
69
+ }
70
+ /**
71
+ * Localiza un elemento y devuelve su centro EN PIXELES DE LA IMAGEN.
72
+ * La conversion a coordenadas de pantalla la hace quien llama, que es el unico
73
+ * que conoce el factor de escala y el origen de la captura.
74
+ */
75
+ export async function locateElement(apiKey, imagePath, target, imageWidth, imageHeight) {
76
+ const prompt = `Observa la captura de pantalla, de ${imageWidth} x ${imageHeight} pixeles.\n\n` +
77
+ `Localiza este elemento: "${target}"\n\n` +
78
+ `Responde UNICAMENTE con un objeto JSON, sin texto alrededor y sin bloques de codigo:\n` +
79
+ `{"found": true, "x": <centro horizontal en pixeles>, "y": <centro vertical en pixeles>, "reason": "<que viste>"}\n` +
80
+ `Si el elemento no aparece en la captura, responde {"found": false, "x": 0, "y": 0, "reason": "<por que>"}.\n` +
81
+ `El origen (0,0) es la esquina superior izquierda de la imagen. No inventes: si dudas, found=false.`;
82
+ const res = await ask(apiKey, prompt, imagePath, 900);
83
+ if (!res.ok)
84
+ return { ok: false, error: res.error };
85
+ const raw = (res.text || "").replace(/```json/gi, "").replace(/```/g, "").trim();
86
+ const match = raw.match(/\{[\s\S]*\}/);
87
+ if (!match)
88
+ return { ok: false, error: `El modelo de vision no devolvio JSON: ${raw.slice(0, 200)}` };
89
+ try {
90
+ const parsed = JSON.parse(match[0]);
91
+ const x = Math.round(Number(parsed.x));
92
+ const y = Math.round(Number(parsed.y));
93
+ const found = parsed.found === true && Number.isFinite(x) && Number.isFinite(y);
94
+ // Un punto fuera de la imagen es una alucinacion, no un acierto.
95
+ const inside = x >= 0 && y >= 0 && x <= imageWidth && y <= imageHeight;
96
+ return {
97
+ ok: true,
98
+ found: found && inside,
99
+ x,
100
+ y,
101
+ reason: typeof parsed.reason === "string" ? parsed.reason : "",
102
+ };
103
+ }
104
+ catch (err) {
105
+ return { ok: false, error: `JSON invalido del modelo de vision: ${err?.message || String(err)}` };
106
+ }
107
+ }
@@ -0,0 +1 @@
1
+ export declare function webFetch(url: string): Promise<string>;
@@ -0,0 +1,25 @@
1
+ export async function webFetch(url) {
2
+ try {
3
+ const res = await fetch(url, {
4
+ headers: {
5
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ChocolatitoCode/1.0",
6
+ },
7
+ signal: AbortSignal.timeout(15_000),
8
+ });
9
+ if (!res.ok) {
10
+ return `Error HTTP ${res.status}: ${res.statusText} al consultar ${url}`;
11
+ }
12
+ const text = await res.text();
13
+ // Clean basic html tags for readability
14
+ const stripped = text
15
+ .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")
16
+ .replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, "")
17
+ .replace(/<[^>]+>/g, " ")
18
+ .replace(/\s+/g, " ")
19
+ .trim();
20
+ return stripped.slice(0, 8_000) || "Página web leída pero sin contenido de texto.";
21
+ }
22
+ catch (err) {
23
+ return `Error al consultar URL "${url}": ${err?.message || String(err)}`;
24
+ }
25
+ }
@@ -0,0 +1 @@
1
+ export declare function webSearch(query: string): Promise<string>;
@@ -0,0 +1,35 @@
1
+ export async function webSearch(query) {
2
+ try {
3
+ const encoded = encodeURIComponent(query);
4
+ const url = `https://html.duckduckgo.com/html/?q=${encoded}`;
5
+ const res = await fetch(url, {
6
+ headers: {
7
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
8
+ },
9
+ signal: AbortSignal.timeout(12_000),
10
+ });
11
+ if (!res.ok) {
12
+ return `Error al buscar en la web: HTTP ${res.status}`;
13
+ }
14
+ const html = await res.text();
15
+ // Parse results from DuckDuckGo HTML
16
+ const results = [];
17
+ const regex = /<a class="result__url"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g;
18
+ let match;
19
+ let count = 0;
20
+ while ((match = regex.exec(html)) !== null && count < 6) {
21
+ count++;
22
+ const link = match[1].trim();
23
+ const title = match[2].replace(/<[^>]+>/g, "").trim();
24
+ const snippet = match[3].replace(/<[^>]+>/g, "").trim();
25
+ results.push(`[${count}] ${title}\n URL: ${link}\n ${snippet}`);
26
+ }
27
+ if (results.length === 0) {
28
+ return `No se encontraron resultados directos para "${query}".`;
29
+ }
30
+ return `Resultados de búsqueda web para "${query}":\n\n${results.join("\n\n")}`;
31
+ }
32
+ catch (err) {
33
+ return `Error en búsqueda web: ${err?.message || String(err)}`;
34
+ }
35
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Cliente del host persistente de Windows.
3
+ *
4
+ * La version anterior lanzaba un powershell.exe nuevo por cada accion y volvia a
5
+ * compilar los tipos nativos con Add-Type cada vez (~700-1500ms por clic). Con 15
6
+ * acciones seguidas eso son 20 segundos de nada, y es lo que hacia que el agente
7
+ * pareciera "trabado".
8
+ *
9
+ * Aqui se arranca UN solo proceso, se compila una vez, y despues cada accion
10
+ * cuesta unos pocos milisegundos por stdin/stdout.
11
+ */
12
+ export interface HostResponse {
13
+ ok?: boolean;
14
+ error?: string;
15
+ [key: string]: any;
16
+ }
17
+ declare class WinHost {
18
+ private proc;
19
+ private ready;
20
+ private nextId;
21
+ private pending;
22
+ private scriptPath;
23
+ private lastError;
24
+ constructor();
25
+ isWindows(): boolean;
26
+ private start;
27
+ send(action: string, args?: Record<string, any>, timeoutMs?: number): Promise<HostResponse>;
28
+ shutdown(): void;
29
+ }
30
+ export declare const winHost: WinHost;
31
+ export {};
@@ -0,0 +1,158 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ import crypto from "node:crypto";
5
+ import readline from "node:readline";
6
+ import { spawn } from "node:child_process";
7
+ import { WIN_HOST_SCRIPT } from "./hostScript.js";
8
+ const READY_TIMEOUT_MS = 60_000;
9
+ const DEFAULT_TIMEOUT_MS = 30_000;
10
+ class WinHost {
11
+ proc = null;
12
+ ready = null;
13
+ nextId = 1;
14
+ pending = new Map();
15
+ scriptPath;
16
+ lastError = null;
17
+ constructor() {
18
+ const dir = path.join(os.homedir(), ".chocolatito");
19
+ if (!fs.existsSync(dir))
20
+ fs.mkdirSync(dir, { recursive: true });
21
+ // El nombre lleva el hash del script: si se actualiza el codigo del host, se
22
+ // escribe un fichero nuevo y no queda una version vieja cacheada.
23
+ const hash = crypto.createHash("sha1").update(WIN_HOST_SCRIPT).digest("hex").slice(0, 10);
24
+ this.scriptPath = path.join(dir, `win-host-${hash}.ps1`);
25
+ if (!fs.existsSync(this.scriptPath)) {
26
+ fs.writeFileSync(this.scriptPath, WIN_HOST_SCRIPT, "utf-8");
27
+ }
28
+ }
29
+ isWindows() {
30
+ return process.platform === "win32";
31
+ }
32
+ start() {
33
+ if (this.ready)
34
+ return this.ready;
35
+ this.ready = new Promise((resolve, reject) => {
36
+ let settled = false;
37
+ const proc = spawn("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", this.scriptPath], { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
38
+ this.proc = proc;
39
+ const bootTimer = setTimeout(() => {
40
+ if (!settled) {
41
+ settled = true;
42
+ reject(new Error("El host de Windows no arranco en 60s."));
43
+ }
44
+ }, READY_TIMEOUT_MS);
45
+ const rl = readline.createInterface({ input: proc.stdout });
46
+ rl.on("line", (line) => {
47
+ const trimmed = line.trim();
48
+ if (!trimmed.startsWith("CHOCO#"))
49
+ return;
50
+ const sep = trimmed.indexOf("#", 6);
51
+ if (sep < 0)
52
+ return;
53
+ const id = parseInt(trimmed.slice(6, sep), 10);
54
+ let payload;
55
+ try {
56
+ payload = JSON.parse(trimmed.slice(sep + 1));
57
+ }
58
+ catch {
59
+ payload = { ok: false, error: `respuesta ilegible del host: ${trimmed.slice(sep + 1, sep + 200)}` };
60
+ }
61
+ if (id === 0) {
62
+ clearTimeout(bootTimer);
63
+ if (!settled) {
64
+ settled = true;
65
+ resolve();
66
+ }
67
+ return;
68
+ }
69
+ const waiter = this.pending.get(id);
70
+ if (waiter) {
71
+ clearTimeout(waiter.timer);
72
+ this.pending.delete(id);
73
+ waiter.resolve(payload);
74
+ }
75
+ });
76
+ let stderrBuf = "";
77
+ proc.stderr.on("data", (d) => {
78
+ stderrBuf += d.toString();
79
+ if (stderrBuf.length > 4000)
80
+ stderrBuf = stderrBuf.slice(-4000);
81
+ this.lastError = stderrBuf;
82
+ });
83
+ proc.on("error", (err) => {
84
+ clearTimeout(bootTimer);
85
+ if (!settled) {
86
+ settled = true;
87
+ reject(err);
88
+ }
89
+ });
90
+ proc.on("exit", (code) => {
91
+ clearTimeout(bootTimer);
92
+ // Si el host muere, se libera a los que esperan y se marca para reinicio.
93
+ for (const [, waiter] of this.pending) {
94
+ clearTimeout(waiter.timer);
95
+ waiter.resolve({ ok: false, error: `El host de Windows termino (codigo ${code}). ${stderrBuf.slice(0, 500)}` });
96
+ }
97
+ this.pending.clear();
98
+ this.proc = null;
99
+ this.ready = null;
100
+ if (!settled) {
101
+ settled = true;
102
+ reject(new Error(`El host de Windows no pudo iniciar: ${stderrBuf.slice(0, 800) || `codigo ${code}`}`));
103
+ }
104
+ });
105
+ });
106
+ return this.ready;
107
+ }
108
+ async send(action, args = {}, timeoutMs = DEFAULT_TIMEOUT_MS) {
109
+ if (!this.isWindows()) {
110
+ return { ok: false, error: "computer_use nativo solo esta implementado para Windows." };
111
+ }
112
+ try {
113
+ await this.start();
114
+ }
115
+ catch (err) {
116
+ return { ok: false, error: `No se pudo iniciar el host de Windows: ${err?.message || String(err)}` };
117
+ }
118
+ const proc = this.proc;
119
+ if (!proc || proc.killed) {
120
+ return { ok: false, error: "El host de Windows no esta disponible." };
121
+ }
122
+ const id = this.nextId++;
123
+ const payload = JSON.stringify({ id, action, ...args });
124
+ return new Promise((resolve) => {
125
+ const timer = setTimeout(() => {
126
+ this.pending.delete(id);
127
+ resolve({ ok: false, error: `La accion "${action}" supero el tiempo limite de ${timeoutMs}ms.` });
128
+ }, timeoutMs);
129
+ this.pending.set(id, { resolve, timer });
130
+ try {
131
+ proc.stdin.write(payload + "\n");
132
+ }
133
+ catch (err) {
134
+ clearTimeout(timer);
135
+ this.pending.delete(id);
136
+ resolve({ ok: false, error: `No se pudo escribir al host: ${err?.message || String(err)}` });
137
+ }
138
+ });
139
+ }
140
+ shutdown() {
141
+ if (this.proc && !this.proc.killed) {
142
+ try {
143
+ this.proc.stdin.end();
144
+ this.proc.kill();
145
+ }
146
+ catch { }
147
+ }
148
+ this.proc = null;
149
+ this.ready = null;
150
+ }
151
+ }
152
+ export const winHost = new WinHost();
153
+ // Al cerrar la CLI no dejamos un powershell.exe huerfano de fondo.
154
+ process.on("exit", () => winHost.shutdown());
155
+ process.on("SIGINT", () => {
156
+ winHost.shutdown();
157
+ process.exit(0);
158
+ });
@@ -0,0 +1 @@
1
+ export declare const WIN_HOST_SCRIPT: string;