@milpa/comal 0.1.0 → 0.1.2

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 (187) hide show
  1. package/README.md +30 -28
  2. package/backend/contracts/remote-commands-v1.json +125 -0
  3. package/bin/comal.mjs +16 -5
  4. package/docs/investigation.md +2 -2
  5. package/governance/chrome-task-v2.json +67 -0
  6. package/node_modules/@jitl/quickjs-ffi-types/LICENSE +21 -0
  7. package/node_modules/@jitl/quickjs-ffi-types/README.md +5 -0
  8. package/node_modules/@jitl/quickjs-ffi-types/dist/index.d.mts +549 -0
  9. package/node_modules/@jitl/quickjs-ffi-types/dist/index.d.ts +549 -0
  10. package/node_modules/@jitl/quickjs-ffi-types/dist/index.js +2 -0
  11. package/node_modules/@jitl/quickjs-ffi-types/dist/index.js.map +1 -0
  12. package/node_modules/@jitl/quickjs-ffi-types/dist/index.mjs +2 -0
  13. package/node_modules/@jitl/quickjs-ffi-types/dist/index.mjs.map +1 -0
  14. package/node_modules/@jitl/quickjs-ffi-types/package.json +36 -0
  15. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/LICENSE +47 -0
  16. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/README.md +93 -0
  17. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/emscripten-module.browser.d.ts +15 -0
  18. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/emscripten-module.browser.mjs +2 -0
  19. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/emscripten-module.cjs +2 -0
  20. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/emscripten-module.cloudflare.cjs +2 -0
  21. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/emscripten-module.cloudflare.d.ts +15 -0
  22. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/emscripten-module.d.ts +15 -0
  23. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/emscripten-module.mjs +2 -0
  24. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/emscripten-module.wasm +0 -0
  25. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/emscripten-module.wasm.map +1 -0
  26. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/ffi.d.mts +94 -0
  27. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/ffi.d.ts +94 -0
  28. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/ffi.js +2 -0
  29. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/ffi.js.map +1 -0
  30. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/ffi.mjs +2 -0
  31. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/ffi.mjs.map +1 -0
  32. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/index.d.mts +20 -0
  33. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/index.d.ts +20 -0
  34. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/index.js +2 -0
  35. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/index.js.map +1 -0
  36. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/index.mjs +2 -0
  37. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/index.mjs.map +1 -0
  38. package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/package.json +61 -0
  39. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/LICENSE +47 -0
  40. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/README.md +88 -0
  41. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/emscripten-module.browser.d.ts +11 -0
  42. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/emscripten-module.browser.mjs +5430 -0
  43. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/emscripten-module.cjs +5485 -0
  44. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/emscripten-module.cloudflare.cjs +5424 -0
  45. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/emscripten-module.cloudflare.d.ts +11 -0
  46. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/emscripten-module.d.ts +11 -0
  47. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/emscripten-module.mjs +5541 -0
  48. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/emscripten-module.wasm +0 -0
  49. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/emscripten-module.wasm.map +1 -0
  50. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/ffi.d.mts +85 -0
  51. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/ffi.d.ts +85 -0
  52. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/ffi.js +2 -0
  53. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/ffi.js.map +1 -0
  54. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/ffi.mjs +2 -0
  55. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/ffi.mjs.map +1 -0
  56. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/index.d.mts +20 -0
  57. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/index.d.ts +20 -0
  58. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/index.js +2 -0
  59. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/index.js.map +1 -0
  60. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/index.mjs +2 -0
  61. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/index.mjs.map +1 -0
  62. package/node_modules/@jitl/quickjs-wasmfile-debug-sync/package.json +61 -0
  63. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/LICENSE +47 -0
  64. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/README.md +89 -0
  65. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/emscripten-module.browser.d.ts +15 -0
  66. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/emscripten-module.browser.mjs +26 -0
  67. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/emscripten-module.cjs +25 -0
  68. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/emscripten-module.cloudflare.cjs +25 -0
  69. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/emscripten-module.cloudflare.d.ts +15 -0
  70. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/emscripten-module.d.ts +15 -0
  71. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/emscripten-module.mjs +30 -0
  72. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/emscripten-module.wasm +0 -0
  73. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/ffi.d.mts +94 -0
  74. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/ffi.d.ts +94 -0
  75. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/ffi.js +2 -0
  76. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/ffi.js.map +1 -0
  77. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/ffi.mjs +2 -0
  78. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/ffi.mjs.map +1 -0
  79. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/index.d.mts +20 -0
  80. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/index.d.ts +20 -0
  81. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/index.js +2 -0
  82. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/index.js.map +1 -0
  83. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/index.mjs +2 -0
  84. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/index.mjs.map +1 -0
  85. package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/package.json +61 -0
  86. package/node_modules/@jitl/quickjs-wasmfile-release-sync/LICENSE +47 -0
  87. package/node_modules/@jitl/quickjs-wasmfile-release-sync/README.md +82 -0
  88. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.browser.d.ts +11 -0
  89. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.browser.mjs +22 -0
  90. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.cjs +21 -0
  91. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.cloudflare.cjs +21 -0
  92. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.cloudflare.d.ts +11 -0
  93. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.d.ts +11 -0
  94. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.mjs +25 -0
  95. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.wasm +0 -0
  96. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/ffi.d.mts +85 -0
  97. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/ffi.d.ts +85 -0
  98. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/ffi.js +2 -0
  99. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/ffi.js.map +1 -0
  100. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/ffi.mjs +2 -0
  101. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/ffi.mjs.map +1 -0
  102. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/index.d.mts +20 -0
  103. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/index.d.ts +20 -0
  104. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/index.js +2 -0
  105. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/index.js.map +1 -0
  106. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/index.mjs +2 -0
  107. package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/index.mjs.map +1 -0
  108. package/node_modules/@jitl/quickjs-wasmfile-release-sync/package.json +61 -0
  109. package/node_modules/quickjs-emscripten/LICENSE +21 -0
  110. package/node_modules/quickjs-emscripten/README.md +928 -0
  111. package/node_modules/quickjs-emscripten/dist/chunk-OHAYRCBA.mjs +2 -0
  112. package/node_modules/quickjs-emscripten/dist/chunk-OHAYRCBA.mjs.map +1 -0
  113. package/node_modules/quickjs-emscripten/dist/index.d.mts +57 -0
  114. package/node_modules/quickjs-emscripten/dist/index.d.ts +57 -0
  115. package/node_modules/quickjs-emscripten/dist/index.global.js +7309 -0
  116. package/node_modules/quickjs-emscripten/dist/index.js +2 -0
  117. package/node_modules/quickjs-emscripten/dist/index.js.map +1 -0
  118. package/node_modules/quickjs-emscripten/dist/index.mjs +2 -0
  119. package/node_modules/quickjs-emscripten/dist/index.mjs.map +1 -0
  120. package/node_modules/quickjs-emscripten/dist/variants.d.mts +39 -0
  121. package/node_modules/quickjs-emscripten/dist/variants.d.ts +39 -0
  122. package/node_modules/quickjs-emscripten/dist/variants.js +2 -0
  123. package/node_modules/quickjs-emscripten/dist/variants.js.map +1 -0
  124. package/node_modules/quickjs-emscripten/dist/variants.mjs +2 -0
  125. package/node_modules/quickjs-emscripten/dist/variants.mjs.map +1 -0
  126. package/node_modules/quickjs-emscripten/package.json +69 -0
  127. package/node_modules/quickjs-emscripten-core/LICENSE +21 -0
  128. package/node_modules/quickjs-emscripten-core/README.md +341 -0
  129. package/node_modules/quickjs-emscripten-core/README.template.md +70 -0
  130. package/node_modules/quickjs-emscripten-core/dist/chunk-TAV5CUKK.mjs +2 -0
  131. package/node_modules/quickjs-emscripten-core/dist/chunk-TAV5CUKK.mjs.map +1 -0
  132. package/node_modules/quickjs-emscripten-core/dist/chunk-V2S4ZYJR.mjs +7 -0
  133. package/node_modules/quickjs-emscripten-core/dist/chunk-V2S4ZYJR.mjs.map +1 -0
  134. package/node_modules/quickjs-emscripten-core/dist/index.d.mts +2033 -0
  135. package/node_modules/quickjs-emscripten-core/dist/index.d.ts +2033 -0
  136. package/node_modules/quickjs-emscripten-core/dist/index.js +7 -0
  137. package/node_modules/quickjs-emscripten-core/dist/index.js.map +1 -0
  138. package/node_modules/quickjs-emscripten-core/dist/index.mjs +2 -0
  139. package/node_modules/quickjs-emscripten-core/dist/index.mjs.map +1 -0
  140. package/node_modules/quickjs-emscripten-core/dist/module-ES6BEMUI.mjs +2 -0
  141. package/node_modules/quickjs-emscripten-core/dist/module-ES6BEMUI.mjs.map +1 -0
  142. package/node_modules/quickjs-emscripten-core/dist/module-asyncify-2EFITU5U.mjs +2 -0
  143. package/node_modules/quickjs-emscripten-core/dist/module-asyncify-2EFITU5U.mjs.map +1 -0
  144. package/node_modules/quickjs-emscripten-core/package.json +49 -0
  145. package/package.json +6 -2
  146. package/src/agent.mjs +17 -6
  147. package/src/artifact-tools.mjs +3 -2
  148. package/src/auto-guard.mjs +5 -1
  149. package/src/cli.mjs +267 -201
  150. package/src/cloud-client.mjs +58 -8
  151. package/src/code-run-worker.mjs +70 -0
  152. package/src/code-run.mjs +97 -0
  153. package/src/codex-provider.mjs +28 -28
  154. package/src/compaction.mjs +1 -1
  155. package/src/goals.mjs +2 -2
  156. package/src/house.mjs +24 -24
  157. package/src/i18n/cli-es.mjs +97 -0
  158. package/src/i18n/es.mjs +227 -0
  159. package/src/i18n/index.mjs +48 -0
  160. package/src/image-attachments.mjs +263 -0
  161. package/src/model-controls.mjs +4 -4
  162. package/src/onboarding.mjs +91 -0
  163. package/src/openai-provider.mjs +24 -24
  164. package/src/peer-tools.mjs +4 -3
  165. package/src/permissions.mjs +53 -17
  166. package/src/provider-auth.mjs +30 -30
  167. package/src/provider-profiles.mjs +27 -27
  168. package/src/provider.mjs +30 -30
  169. package/src/remote-commands.mjs +11 -0
  170. package/src/remote-control.mjs +180 -0
  171. package/src/sessions.mjs +3 -3
  172. package/src/subagents.mjs +7 -3
  173. package/src/tool-packs.mjs +5 -2
  174. package/src/tools.mjs +92 -34
  175. package/src/tui/brand.mjs +2 -1
  176. package/src/tui/clipboard.mjs +5 -4
  177. package/src/tui/composer.mjs +77 -11
  178. package/src/tui/controller.mjs +111 -29
  179. package/src/tui/discovery.mjs +105 -100
  180. package/src/tui/markdown.mjs +10 -9
  181. package/src/tui/message-queue.mjs +27 -9
  182. package/src/tui/screen.mjs +209 -118
  183. package/src/vision.mjs +31 -3
  184. package/src/work-assertions.mjs +7 -7
  185. package/src/work-plan.mjs +189 -21
  186. package/src/work-recovery-proof.mjs +13 -1
  187. package/src/workflows/chrome-task.mjs +24 -7
@@ -1,91 +1,94 @@
1
+ import { t } from '../i18n/index.mjs';
1
2
  // Presentation-only discovery: supplied snapshots in, suggestions out. Never
2
3
  // reads a workspace, contacts a provider, executes tools, or changes authority.
3
4
  import { graphemes, safeText } from './text.mjs';
4
5
 
5
6
  const COMMANDS = [
6
- ['help', '', 'Ayuda y teclado'], ['status', '', 'Sesión, proveedor y presupuesto'],
7
- ['tools', '', 'Herramientas disponibles en este momento'], ['house', '', 'Recibos y operaciones interrumpidas'],
8
- ['operations', '', 'Contratos y efectos de las operaciones'], ['sessions', '', 'Abrir el selector de sesiones'],
9
- ['resume', 'UUID', 'Reanudar una sesión guardada'], ['clear', '', 'Crear una conversación nueva'],
10
- ['copy', '', 'Copiar la última respuesta completa'], ['select', '', 'Seleccionar texto con el terminal'],
11
- ['compact', '', 'Derivar el estado del libro sin LLM'], ['context', '', 'Consultar el presupuesto de contexto'],
12
- ['state', '', 'Petición y estado actual del trabajo'], ['audit', '', 'Verificar la integridad del libro'],
13
- ['board', '', 'Abrir el tablero de tareas'], ['work', 'resume|recover', 'Continuar trabajo o reconciliar una publicación Chrome'],
14
- ['tasks', '', 'Abrir el tablero de tareas'], ['model', 'ID', 'Consultar o cambiar el modelo'],
15
- ['models', '', 'Consultar modelos del proveedor; puede hacer una petición'],
16
- ['effort', 'NIVEL', 'Consultar o cambiar el esfuerzo anunciado por el modelo'],
17
- ['capabilities', '[refresh]', 'Soporte y procedencia de las capacidades'],
18
- ['permissions', 'MODO', 'Cambiar el modo de permisos'], ['goal', '[condición|status|pause|resume|clear]', 'Definir o controlar un objetivo persistente'],
19
- ['queue', '[pause|resume|clear|edit N]', 'Gestionar los mensajes en cola'],
20
- ['rename', 'NOMBRE', 'Cambiar el nombre visible de esta sesión'], ['table', 'ARCHIVO', 'Mostrar una tabla CSV, TSV, JSON o Markdown'],
21
- ['mcp', '[acción]', 'Ver o configurar servidores MCP'], ['plugins', '[acción]', 'Ver plugins y controlar su ciclo de vida'],
22
- ['agent', '[explore|general|background] TAREA', 'Delegar una tarea a un subagente'], ['agents', '[UUID|status|wait|stop|resume|background|continue] [RUN]', 'Ver y controlar ejecuciones hijas'],
23
- ['search', 'CONSULTA', 'Buscar en la web'], ['list-agents', '', 'Descubrir otras sesiones vivas'],
24
- ['send', 'ID_O_NOMBRE TEXTO', 'Enviar un mensaje a otra sesión'], ['inbox', '[acción]', 'Controlar el buzón entre sesiones'],
25
- ['providers', '', 'Ver perfiles y proveedores de login'], ['provider', 'PERFIL', 'Cambiar al perfil configurado'],
26
- ['login', 'PROVEEDOR', 'Autenticar un proveedor sin pegar claves'], ['logout', 'PROVEEDOR', 'Retirar credenciales de Comal'],
27
- ['exit', '', 'Guardar y salir'], ['quit', '', 'Guardar y salir'],
7
+ ['cloud', '[login|status|logout]', 'Connect your Comal account for Artifacts and Remote Control'],
8
+ ['remote-control', '[start|status|stop]', 'Open this local session on the web'], ['rc', '[start|status|stop]', 'Remote Control shortcut'],
9
+ ['help', '', "Help and keyboard"], ['status', '', "Session, provider and budget"],
10
+ ['tools', '', "Tools available at this time"], ['house', '', "Receipts and interrupted operations"],
11
+ ['operations', '', "Contracts and effects of operations"], ['sessions', '', "Open the session selector"],
12
+ ['resume', 'UUID', "Resume a saved session"], ['clear', '', "Create a new conversation"],
13
+ ['copy', '', "Copy the last complete response"], ['select', '', "Select text with the terminal"],
14
+ ['compact', '', "Derive ledger state without an LLM"], ['context', '', "Check the context budget"],
15
+ ['state', '', "Request and current job status"], ['audit', '', "Verify ledger integrity"],
16
+ ['board', '', "Open the task board"], ['work', 'resume|recover|review', "Continue work, recover Chrome or review a native failure locally"],
17
+ ['tasks', '', "Open the task board"], ['model', 'ID', "Check or change the model"],
18
+ ['models', '', "Check provider models; can make a request"],
19
+ ['effort', "LEVEL", "Check or change the effort announced by the model"],
20
+ ['capabilities', '[refresh]', "Support and provenance of capabilities"],
21
+ ['permissions', "MODE", "Change the permissions mode"], ['goal', "[condition|status|pause|resume|clear]", "Define or control a persistent goal"],
22
+ ['queue', '[pause|resume|clear|edit N]', "Manage queued messages"],
23
+ ['rename', "NAME", "Change the visible name of this session"], ['table', "FILE", "Display a CSV, TSV, JSON, or Markdown table"],
24
+ ['mcp', "[action]", "View or configure MCP servers"], ['plugins', "[action]", "View plugins and control their lifecycle"],
25
+ ['agent', "[explore|general|background] TASK", "Delegate a task to a subagent"], ['agents', '[UUID|status|wait|stop|resume|background|continue] [RUN]', "View and control child runs"],
26
+ ['search', "QUERY", "Search the web"], ['list-agents', '', "Discover other live sessions"],
27
+ ['send', "ID_OR_NAME TEXT", "Send a message to another session"], ['inbox', "[action]", "Control the mailbox between sessions"],
28
+ ['providers', '', "View profiles and login providers"], ['provider', "PROFILE", "Switch to the configured profile"],
29
+ ['login', "PROVIDER", "Authenticate a provider without pasting keys"], ['logout', "PROVIDER", "Remove Comal credentials"],
30
+ ['exit', '', "Save and exit"], ['quit', '', "Save and exit"],
28
31
  ].map(([name, args, description]) => Object.freeze({ name, args, description }));
29
32
  export const COMMAND_CATALOG = Object.freeze(COMMANDS);
30
33
 
31
34
  // Search vocabulary only: exact registered names select these terms. They never
32
35
  // add an operation or replace the host's description/capability contract.
33
36
  const TOOL_KEYWORDS = Object.freeze({
34
- Read: 'leer archivo archivos texto contenido código codigo inspeccionar',
35
- Write: 'escribir crear guardar archivo archivos código codigo',
36
- Edit: 'editar modificar reemplazar archivo archivos código codigo',
37
- Glob: 'buscar encontrar archivos rutas nombres patrón patron',
38
- Grep: 'buscar encontrar texto contenido coincidencias expresión regular',
37
+ Read: "read file files text content code code inspect leer archivo archivos texto contenido código codigo inspeccionar",
38
+ Write: "write create save file files code code escribir crear guardar archivo archivos código codigo",
39
+ Edit: "edit modify replace file files code code editar modificar reemplazar archivo archivos código codigo",
40
+ Glob: "search find files paths names pattern pattern buscar encontrar archivos rutas nombres patrón patron",
41
+ Grep: "search find text content matches regular expression buscar encontrar texto contenido coincidencias expresión regular",
39
42
  Bash: 'terminal shell comandos ejecutar proceso procesos',
40
- ListDirectory: 'listar directorio carpeta carpetas archivos explorar sólo lectura solo lectura',
41
- FileInfo: 'información informacion archivo tamaño tamano formato firma cabecera sólo lectura solo lectura',
42
- ReadTable: 'leer tabla tablas datos CSV TSV JSON Markdown',
43
- WebSearch: 'buscar búsqueda busqueda web internet fuentes investigar',
44
- WebFetch: 'leer descargar página pagina enlace sitio web internet fuente',
45
- AnalyzeMedia: 'imagen imágenes imagenes visión vision fotografía fotografia foto captura multimedia video vídeo analizar',
46
- ProviderSearch: 'buscar búsqueda busqueda web internet fuentes proveedor nativo investigar',
47
- ProviderCapabilities: 'capacidades soporte proveedor modelo visión vision video vídeo herramientas razonamiento',
48
- ListAgents: 'listar descubrir pares vivos otras sesiones instancias agentes comunicación comunicacion',
49
- SendMessage: 'enviar mensaje mensajes comunicar comunicación comunicacion sesiones pares delegar informe respuesta',
50
- Agent: 'subagente subagentes delegar explorar investigar tarea',
51
- AgentBatch: 'subagentes delegar paralelo tareas equipo lote',
43
+ ListDirectory: "list directory folder folders files explore read-only read-only listar directorio carpeta carpetas archivos explorar sólo lectura solo lectura",
44
+ FileInfo: "information information file size size format signature header read-only read-only información informacion archivo tamaño tamano formato firma cabecera sólo lectura solo lectura",
45
+ ReadTable: "read table tables data CSV TSV JSON Markdown leer tabla tablas datos CSV TSV JSON Markdown",
46
+ WebSearch: "search search web internet sources research buscar búsqueda busqueda web internet fuentes investigar",
47
+ WebFetch: "read download page page link website internet source leer descargar página pagina enlace sitio web internet fuente",
48
+ AnalyzeMedia: "image images images vision vision photography photography photo capture multimedia video video analyze imagen imágenes imagenes visión vision fotografía fotografia foto captura multimedia video vídeo analizar",
49
+ ProviderSearch: "search search search web internet sources provider native investigate buscar búsqueda busqueda web internet fuentes proveedor nativo investigar",
50
+ ProviderCapabilities: "capabilities support provider model vision vision video video tools reasoning capacidades soporte proveedor modelo visión vision video vídeo herramientas razonamiento",
51
+ ListAgents: "list discover peers alive other sessions instances agents communication communication listar descubrir pares vivos otras sesiones instancias agentes comunicación comunicacion",
52
+ SendMessage: "send message messages communicate communication communication sessions peers delegate report response enviar mensaje mensajes comunicar comunicación comunicacion sesiones pares delegar informe respuesta",
53
+ Agent: "subagent subagents delegate explore investigate task subagente subagentes delegar explorar investigar tarea",
54
+ AgentBatch: "subagents delegate parallel tasks team batch subagentes delegar paralelo tareas equipo lote",
52
55
  AgentStatus:'estado subagentes ejecuciones pendientes resultado verificado',
53
- AgentWait:'esperar subagente ejecución resultado',
54
- AgentStop:'detener cancelar subagente ejecución',
55
- AgentResume:'reanudar subagente interrumpido checkpoint',
56
- AgentContinue:'subagente recuperar delegación invocación interrumpida',
56
+ AgentWait:"wait subagent execution result esperar subagente ejecución resultado",
57
+ AgentStop:"stop cancel subagent execution detener cancelar subagente ejecución",
58
+ AgentResume:"resume subagent interrupted checkpoint reanudar subagente interrumpido checkpoint",
59
+ AgentContinue:"subagent recover delegation invocation interrupted subagente recuperar delegación invocación interrumpida",
57
60
  AgentBackground:'subagente segundo plano background',
58
- ToolPack: 'cargar descargar herramientas MCP paquetes packs temporales esquemas descubrir adquirir liberar',
59
- McpStatus: 'MCP servidores conexiones herramientas estado',
60
- McpConfigure: 'MCP configurar servidores añadir agregar quitar conexiones',
61
- McpReload: 'MCP recargar reconectar servidores conexiones herramientas',
61
+ ToolPack: "load unload tools MCP packages packs temporary schemas discover acquire release cargar descargar herramientas MCP paquetes packs temporales esquemas descubrir adquirir liberar",
62
+ McpStatus: "MCP servers connections tools status MCP servidores conexiones herramientas estado",
63
+ McpConfigure: "MCP configure servers add add remove connections MCP configurar servidores añadir agregar quitar conexiones",
64
+ McpReload: "MCP reload reconnect servers connections tools MCP recargar reconectar servidores conexiones herramientas",
62
65
  PluginStatus: 'plugins extensiones complementos dependencias operaciones estado',
63
- PluginRun: 'plugins extensiones complementos operación operacion ejecutar',
66
+ PluginRun: "plugins extensions add-ons operation operation execute plugins extensiones complementos operación operacion ejecutar",
64
67
  PluginEnable: 'plugins extensiones complementos activar habilitar',
65
68
  PluginDisable: 'plugins extensiones complementos desactivar retirar',
66
69
  PluginReload: 'plugins extensiones complementos recargar actualizar',
67
- WorkBoard: 'tablero kanban tareas trabajo pendientes progreso',
68
- WorkSnapshot: 'petición peticion estado trabajo checkpoint memoria',
69
- AuditStatus: 'auditoría auditoria verificar evidencia libro integridad',
70
- EvidenceRead: 'leer evidencia recibos libro prueba pruebas',
71
- HouseSnapshot: 'casa juez recibos operaciones estado ejecución ejecucion',
72
- HouseContext: 'casa juez contexto evidencia memoria',
73
- OperationCatalog: 'catálogo catalogo operaciones contratos efectos herramientas',
70
+ WorkBoard: "board kanban tasks work pending progress tablero kanban tareas trabajo pendientes progreso",
71
+ WorkSnapshot: "request request status work checkpoint memory petición peticion estado trabajo checkpoint memoria",
72
+ AuditStatus: "audit audit verify evidence ledger integrity auditoría auditoria verificar evidencia libro integridad",
73
+ EvidenceRead: "read evidence receipts ledger proof proofs leer evidencia recibos libro prueba pruebas",
74
+ HouseSnapshot: "house judge receipts operations status execution execution casa juez recibos operaciones estado ejecución ejecucion",
75
+ HouseContext: "house judge context evidence memory casa juez contexto evidencia memoria",
76
+ OperationCatalog: "catalog catalog operations contracts effects tools catálogo catalogo operaciones contratos efectos herramientas",
74
77
  });
75
78
  const COMMAND_KEYWORDS = Object.freeze({
76
- help: 'ayuda atajos teclado', tools: 'catálogo catalogo descubrir herramientas',
77
- operations: 'contratos efectos herramientas catálogo catalogo',
78
- sessions: 'sesiones historial retomar recuperar', resume: 'sesiones historial retomar recuperar continuar',
79
- compact: 'memoria proyección proyeccion derivar resumen contexto',
80
- board: 'kanban pendientes tareas progreso', work: 'kanban checkpoint continuar retomar',
81
- effort: 'esfuerzo razonamiento pensar thinking', capabilities: 'capacidades visión vision video imágenes imagenes soporte',
82
- permissions: 'modos seguridad permisos autorizar plan automático automatico',
83
- goal: 'objetivo autónomo autonomo continuar persistente', queue: 'cola mensajes pendientes pausar reordenar',
84
- mcp: 'herramientas servidores conectar recargar stdio HTTP', plugins: 'extensiones complementos módulos modulos',
85
- agent: 'subagente delegar tarea explorar', agents: 'subagentes delegados estado',
86
- search: 'investigar búsqueda busqueda internet fuentes', 'list-agents': 'pares vivos otras instancias comunicar sesiones',
87
- send: 'comunicar mensaje sesiones pares', inbox: 'buzón buzon mensajes recibir sesiones',
88
- login: 'iniciar sesión sesion autenticar suscripción suscripcion', logout: 'cerrar sesión sesion credenciales',
79
+ help: 'ayuda atajos teclado', tools: "catalog catalog discover tools catálogo catalogo descubrir herramientas",
80
+ operations: "contracts effects tools catalog catalog contratos efectos herramientas catálogo catalogo",
81
+ sessions: "sessions history resume recover sesiones historial retomar recuperar", resume: "sessions history resume recover continue sesiones historial retomar recuperar continuar",
82
+ compact: "memory projection projection derive summary context memoria proyección proyeccion derivar resumen contexto",
83
+ board: "kanban pending tasks progress kanban pendientes tareas progreso", work: "kanban checkpoint continue resume kanban checkpoint continuar retomar",
84
+ effort: 'esfuerzo razonamiento pensar thinking', capabilities: "capabilities vision vision video images images support capacidades visión vision video imágenes imagenes soporte",
85
+ permissions: "modes security permissions authorize plan automatic automatic modos seguridad permisos autorizar plan automático automatico",
86
+ goal: "goal autonomous autonomous continue persistent objetivo autónomo autonomo continuar persistente", queue: "queue messages pending pause reorder cola mensajes pendientes pausar reordenar",
87
+ mcp: "tools servers connect reload stdio HTTP herramientas servidores conectar recargar stdio HTTP", plugins: "extensions add-ons modules modules extensiones complementos módulos modulos",
88
+ agent: "subagent delegate task explore subagente delegar tarea explorar", agents: 'subagentes delegados estado',
89
+ search: "investigate search search internet sources investigar búsqueda busqueda internet fuentes", 'list-agents': "peers alive other instances communicate sessions pares vivos otras instancias comunicar sesiones",
90
+ send: "communicate message between peer sessions comunicar mensaje sesiones pares", inbox: "mailbox mailbox messages receive sessions buzón buzon mensajes recibir sesiones",
91
+ login: "log in session authenticate subscription subscription iniciar sesión sesion autenticar suscripción suscripcion", logout: "log out session credentials cerrar sesión sesion credenciales",
89
92
  });
90
93
 
91
94
  const token = value => typeof value === 'string' && value.length <= 256 && value.length > 0 && !/[\s\x00-\x1f\x7f-\x9f]/u.test(value);
@@ -106,7 +109,7 @@ const skills = context => entries(context.skills).filter(skill => /^[\p{L}\p{N}_
106
109
  */
107
110
  export function commandCatalog(context = {}) {
108
111
  return [
109
- ...COMMANDS.map(command => ({ label: `/${command.name}`, value: `/${command.name}${command.args ? ' ' : ''}`, description: command.description, keywords: COMMAND_KEYWORDS[command.name] || '', kind: 'command', syntax: `/${command.name}${command.args ? ` ${command.args}` : ''}` })),
112
+ ...COMMANDS.map(command => ({ label: `/${command.name}`, value: `/${command.name}${command.args ? ' ' : ''}`, description: t(command.description), keywords: COMMAND_KEYWORDS[command.name] || '', kind: 'command', syntax: `/${command.name}${command.args ? ` ${t(command.args)}` : ''}` })),
110
113
  ...skills(context).map(skill => ({ ...skill, label: `/${skill.value}`, value: `/${skill.value} `, kind: 'skill' })),
111
114
  ...unique(entries(context.tools)).map(tool => ({ ...tool, keywords: TOOL_KEYWORDS[tool.value] || '', kind: 'tool', inspectOnly: true })),
112
115
  ];
@@ -115,51 +118,53 @@ export function commandCatalog(context = {}) {
115
118
  function argumentItems(command, args, context) {
116
119
  const first = args[0] || '', position = args.length - 1;
117
120
  if (position === 0) {
118
- if (command === 'permissions') return named(['default', 'plan', 'acceptEdits', 'auto'], 'Modo de permisos; no cambia el modelo');
119
- if (command === 'effort') return named(context.effortLevels || context.capabilities?.effort?.levels || ['auto'], 'Nivel anunciado por el modelo actual');
121
+ if (command === 'rc' || command === 'remote-control')return named(['start','status','stop'],'Remote session projection; local tools and permissions');
122
+ if (command === 'cloud') return named(['login','status','logout'],'Comal account; separate from the model provider');
123
+ if (command === 'permissions') return named(['default', 'plan', 'acceptEdits', 'auto'], t("Permissions mode; does not change the model"));
124
+ if (command === 'effort') return named(context.effortLevels || context.capabilities?.effort?.levels || ['auto'], t("Level announced by the current model"));
120
125
  if (command === 'provider') return [...entries(context.profiles), ...entries(context.authProviders || ['codex', 'kimi', 'minimax', 'grok'], 'id')];
121
126
  if (command === 'model') return entries(context.models, 'id');
122
127
  if (command === 'resume') return entries(context.sessions, 'id');
123
- if (command === 'agents') return [...named(['status','wait','stop','resume','background','continue'],'Control de ejecuciones hijas'),...entries(context.agents,'id')];
124
- if (command === 'send') return entries(context.peers, 'id').map(peer => ({ ...peer, description: 'Sesión viva conocida; enviar no equivale a ejecutar' }));
128
+ if (command === 'agents') return [...named(['status','wait','stop','resume','background','continue'],t("Child run controls")),...entries(context.agents,'id')];
129
+ if (command === 'send') return entries(context.peers, 'id').map(peer => ({ ...peer, description: t("Known live session; sending does not equal executing") }));
125
130
  if (command === 'capabilities') return named(['refresh'], 'Volver a consultar capacidades; puede hacer peticiones');
126
- if (command === 'work') return named(['resume', 'recover'], 'Continuar trabajo o reconciliar una publicación Chrome');
127
- if (command === 'goal') return named(['status', 'pause', 'resume', 'clear'], 'Control del objetivo; también puedes escribir una condición');
128
- if (command === 'queue') return named(['pause', 'resume', 'clear', 'edit'], 'Acción sobre la cola');
129
- if (command === 'mcp') return named(['status', 'list', 'config', 'reload', 'reconnect', 'add', 'remove'], 'Acción sobre servidores MCP');
130
- if (command === 'plugins') return named(['status', 'list', 'enable', 'disable', 'reload', 'run'], 'Acción sobre plugins');
131
- if (command === 'agent') return named(['explore', 'general','background'], 'Tipo de subagente; completa después la tarea');
132
- if (command === 'inbox') return named(['status', 'accept', 'hold', 'refuse', 'release'], 'Control del buzón entre sesiones');
133
- if (command === 'login' || command === 'logout') return [...(command === 'login' ? named(['status'], 'Consultar el estado del login') : []), ...entries(context.authProviders || ['codex', 'kimi', 'minimax', 'grok'], 'id')];
131
+ if (command === 'work') return named(['resume', 'recover', 'review'], t("Continue work, recover Chrome or review a native failure locally"));
132
+ if (command === 'goal') return named(['status', 'pause', 'resume', 'clear'], t("Target control; you can also write a condition"));
133
+ if (command === 'queue') return named(['pause', 'resume', 'clear', 'edit'], t("Action on the queue"));
134
+ if (command === 'mcp') return named(['status', 'list', 'config', 'reload', 'reconnect', 'add', 'remove'], t("Action on MCP servers"));
135
+ if (command === 'plugins') return named(['status', 'list', 'enable', 'disable', 'reload', 'run'], t("Action on plugins"));
136
+ if (command === 'agent') return named(['explore', 'general','background'], t("Subagent type; complete the task afterwards"));
137
+ if (command === 'inbox') return named(['status', 'accept', 'hold', 'refuse', 'release'], t("Mailbox control between sessions"));
138
+ if (command === 'login' || command === 'logout') return [...(command === 'login' ? named(['status'], t("Check login status")) : []), ...entries(context.authProviders || ['codex', 'kimi', 'minimax', 'grok'], 'id')];
134
139
  }
135
140
  if (position === 1) {
136
141
  if(command==='agents' && ['status','wait','stop','resume','background'].includes(first))return entries(context.agents,'id');
137
142
  if (command === 'mcp' && ['reconnect', 'remove'].includes(first)) return entries(context.mcpServers);
138
143
  if (command === 'plugins' && ['enable', 'disable', 'reload', 'run'].includes(first)) return entries(context.plugins, 'id');
139
- if (command === 'inbox' && first === 'release') return named(['all'], 'Liberar mensajes retenidos; no cambia el modo hold');
140
- if (command === 'queue' && first === 'edit') return named((context.queue?.items || []).map((_, index) => String(index + 1)), 'Editar este mensaje pendiente');
144
+ if (command === 'inbox' && first === 'release') return named(['all'], t("Release held messages; does not change hold mode"));
145
+ if (command === 'queue' && first === 'edit') return named((context.queue?.items || []).map((_, index) => String(index + 1)), t("Edit this pending message"));
141
146
  if (command === 'login' && first === 'status') return entries(context.authProviders || ['codex', 'kimi', 'minimax', 'grok'], 'id');
142
- if (command === 'login' && first !== 'status') return named(['--api-key-env'], 'Nombre de variable de entorno, nunca la clave');
147
+ if (command === 'login' && first !== 'status') return named(['--api-key-env'], t("Environment variable name, never the key"));
143
148
  }
144
149
  if (position === 2 && command === 'plugins' && first === 'run') {
145
150
  const plugin = (context.plugins || []).find(plugin => plugin.id === args[1]);
146
151
  const prefix = `plugin__${args[1]}__`;
147
152
  return entries(plugin?.operations).map(operation => {
148
153
  const value = !plugin.builtin && operation.value.startsWith(prefix) ? operation.value.slice(prefix.length) : operation.value;
149
- return { ...operation, label: value, value, kind: 'operation', description: operation.description || 'Operación registrada por este plugin' };
154
+ return { ...operation, label: value, value, kind: 'operation', description: operation.description || t("Operation registered by this plugin") };
150
155
  });
151
156
  }
152
157
  return [];
153
158
  }
154
159
 
155
160
  function syntaxHint(command, context) {
156
- if (command === 'model' && !(context.models || []).length) return 'Usa /models para consultar el catálogo; no se hacen peticiones al escribir.';
157
- if (command === 'effort' && !context.effortLevels && !context.capabilities?.effort?.levels) return 'Sólo auto está confirmado. /capabilities muestra los niveles disponibles.';
158
- if (command === 'plugins') return '/plugins run ID OPERACIÓN {JSON} · las mutaciones esperan su turno.';
159
- if (command === 'login') return 'Nunca pegues una clave aquí; usa --api-key-env VARIABLE.';
160
- if (skills(context).some(skill => skill.value === command)) return 'Skill local: escribe la tarea después del nombre.';
161
+ if (command === 'model' && !(context.models || []).length) return t("Use /models to check the catalog; no requests are made when typing.");
162
+ if (command === 'effort' && !context.effortLevels && !context.capabilities?.effort?.levels) return t("Only auto is confirmed. /capabilities shows the available levels.");
163
+ if (command === 'plugins') return t("/plugins run ID OPERATION {JSON} · mutations wait their turn.");
164
+ if (command === 'login') return t("Never paste a key here; use --api-key-env VARIABLE.");
165
+ if (skills(context).some(skill => skill.value === command)) return t("Local skill: write the task after the name.");
161
166
  const entry = COMMANDS.find(entry => entry.name === command);
162
- return entry ? `/${entry.name}${entry.args ? ` ${entry.args}` : ''} · ${entry.description}` : 'F6 abre comandos y herramientas disponibles.';
167
+ return entry ? `/${entry.name}${entry.args ? ` ${t(entry.args)}` : ''} · ${t(entry.description)}` : t("F6 opens available commands and tools.");
163
168
  }
164
169
 
165
170
  /** Cursor and replacement ranges use grapheme indices, exactly like Composer.
@@ -198,20 +203,20 @@ export function commandSuggestions(text, cursor = graphemes(text).length, contex
198
203
  */
199
204
  export function applySuggestion(text, result, item) {
200
205
  text = String(text); const buffer = graphemes(text);
201
- if (!result || result.source !== text || !Number.isInteger(result.start) || !Number.isInteger(result.end) || result.start < 0 || result.end < result.start || result.end > buffer.length || !result.items?.some(candidate => candidate.value === item?.value) || typeof item?.value !== 'string' || /[\x00-\x1f\x7f-\x9f]/u.test(item.value)) throw new Error('La sugerencia cambió; vuelve a seleccionarla.');
206
+ if (!result || result.source !== text || !Number.isInteger(result.start) || !Number.isInteger(result.end) || result.start < 0 || result.end < result.start || result.end > buffer.length || !result.items?.some(candidate => candidate.value === item?.value) || typeof item?.value !== 'string' || /[\x00-\x1f\x7f-\x9f]/u.test(item.value)) throw new Error(t("The suggestion changed; select it again."));
202
207
  const inserted = graphemes(item.value);
203
208
  return { text: [...buffer.slice(0, result.start), ...inserted, ...buffer.slice(result.end)].join(''), cursor: result.start + inserted.length };
204
209
  }
205
210
 
206
211
  /** One stable, contextual hint; no timed rotation, extra output, or tool calls. */
207
212
  export function contextualTip(text = '', context = {}) {
208
- if (context.selecting) return 'Selecciona y copia con tu terminal · F5 o Esc vuelve.';
209
- if (context.editingQueue) return 'Editando un mensaje en cola · Enter guarda · Esc conserva el original.';
210
- if (context.queue?.paused) return 'Cola pausada · F4 para revisarla y reanudarla.';
211
- if (context.queue?.interrupted?.length) return 'Hay mensajes interrumpidos · F4 permite revisarlos antes de reintentar.';
213
+ if (context.selecting) return t("Select and copy with your terminal · F5 or Esc returns.");
214
+ if (context.editingQueue) return t("Editing a queued message · Enter saves · Esc keeps the original.");
215
+ if (context.queue?.paused) return t("Queue paused · F4 to review and resume it.");
216
+ if (context.queue?.interrupted?.length) return t("Interrupted messages · F4 lets you review them before retrying.");
212
217
  if (String(text).trimStart().startsWith('/')) return commandSuggestions(String(text), graphemes(String(text)).length, context).hint;
213
- if (context.busy) return 'Puedes seguir escribiendo · Enter encola · Esc interrumpe el turno.';
214
- if (String(text).includes('\n')) return 'Mensaje multilínea · Enter envía · Ctrl+G abre tu editor.';
215
- if (context.mode === 'plan') return 'Modo plan: inspección sin cambios · Shift+Tab cambia de modo.';
216
- return 'F6 descubre comandos y herramientas · / autocompleta · Ctrl+G abre tu editor.';
218
+ if (context.busy) return t("You can keep typing · Enter queues · Esc interrupts the turn.");
219
+ if (String(text).includes('\n')) return t("Multiline message · Enter sends · Ctrl+G opens your editor.");
220
+ if (context.mode === 'plan') return t("Plan mode: inspection without changes · Shift+Tab switches mode.");
221
+ return t("F6 discovers commands and tools · / autocompletes · Ctrl+G opens your editor.");
217
222
  }
@@ -1,3 +1,4 @@
1
+ import { t } from '../i18n/index.mjs';
1
2
  import { Marked } from 'marked';
2
3
  import { safeText, tone, wrap, width, pad, clip } from './text.mjs';
3
4
 
@@ -15,7 +16,7 @@ function inline(tokens = []) {
15
16
  if (token.type === 'codespan') return tone.accent(text);
16
17
  if (token.type === 'br') return '\n';
17
18
  if (token.type === 'link') return tone.accent(text) + (token.href && token.href !== token.text ? tone.muted(` (${safeText(token.href)})`) : '');
18
- if (token.type === 'image') return `[imagen: ${text}]`;
19
+ if (token.type === 'image') return t("[image: {v0}]", {v0: text});
19
20
  return text;
20
21
  }).join('');
21
22
  }
@@ -25,7 +26,7 @@ export class DataTableParser {
25
26
  text = safeText(text, { preserveTabs: true });
26
27
  if (format === 'json') {
27
28
  const parsed = JSON.parse(text), data = Array.isArray(parsed) ? parsed : parsed.data;
28
- if (!Array.isArray(data) || !data.every(row => row && typeof row === 'object' && !Array.isArray(row))) throw new Error('La tabla JSON requiere una lista de objetos, o {data: [...]}.');
29
+ if (!Array.isArray(data) || !data.every(row => row && typeof row === 'object' && !Array.isArray(row))) throw new Error(t("The JSON table requires a list of objects, or {data: [...]}."));
29
30
  const headers = [...new Set(data.flatMap(row => Object.keys(row)))];
30
31
  return { headers, rows: data.map(row => headers.map(key => typeof row[key] === 'object' && row[key] !== null ? JSON.stringify(row[key]) : String(row[key] ?? ''))) };
31
32
  }
@@ -34,19 +35,19 @@ export class DataTableParser {
34
35
  return { headers: rows[0] || [], rows: rows.slice(1) };
35
36
  }
36
37
  const table = lexer.lexer(text).find(token => token.type === 'table');
37
- if (!table) throw new Error('No se encontró una tabla Markdown.');
38
+ if (!table) throw new Error(t("No Markdown table found."));
38
39
  return { headers: table.header.map(cell => inline(cell.tokens)), rows: table.rows.map(row => row.map(cell => inline(cell.tokens))), align: table.align };
39
40
  }
40
41
  render({ headers, rows, align = [] }, columns) {
41
- if (!headers.length) return [tone.muted('(tabla vacía)')];
42
- if (headers.length > 24 || rows.length > 10000) throw new Error('Tabla demasiado grande: máximo 24 columnas y 10000 filas.');
42
+ if (!headers.length) return [tone.muted(t("(empty table)"))];
43
+ if (headers.length > 24 || rows.length > 10000) throw new Error(t("Table too large: maximum 24 columns and 10000 rows."));
43
44
  const shown = rows.slice(0, 200).map(row => headers.map((_, i) => safeText(row[i] ?? '')));
44
45
  headers = headers.map(safeText);
45
46
  const available = columns - (headers.length + 1) - headers.length * 2;
46
47
  if (available < headers.length * 3) {
47
48
  const output = [];
48
- shown.forEach((row, index) => { output.push(tone.muted(`── Fila ${index + 1} ──`)); row.forEach((value, i) => output.push(...wrap(`${tone.bold(headers[i])}: ${value}`, columns))); });
49
- if (rows.length > 200) output.push(tone.muted(`… ${rows.length - 200} filas adicionales`));
49
+ shown.forEach((row, index) => { output.push(tone.muted(t("── Row {v0} ──", {v0: index + 1}))); row.forEach((value, i) => output.push(...wrap(`${tone.bold(headers[i])}: ${value}`, columns))); });
50
+ if (rows.length > 200) output.push(tone.muted(t("… {v0} additional rows", {v0: rows.length - 200})));
50
51
  return output.map(line => clip(line, columns));
51
52
  }
52
53
  const sizes = headers.map((header, i) => Math.max(3, Math.min(32, width(header), available)));
@@ -68,7 +69,7 @@ export class DataTableParser {
68
69
  renderRow(headers, true); output.push(border('├', '┼', '┤'));
69
70
  for (const row of shown) renderRow(row);
70
71
  output.push(border('└', '┴', '┘'));
71
- if (rows.length > shown.length) output.push(tone.muted(`… ${rows.length - shown.length} filas adicionales`));
72
+ if (rows.length > shown.length) output.push(tone.muted(t("… {v0} additional rows", {v0: rows.length - shown.length})));
72
73
  return output.map(line => clip(line, columns));
73
74
  }
74
75
  }
@@ -86,7 +87,7 @@ function delimited(text, separator) {
86
87
  else if (c === '\n') { row.push(cell); rows.push(row); row = []; cell = ''; }
87
88
  else cell += c;
88
89
  }
89
- if (quoted) throw new Error('CSV/TSV tiene una celda entre comillas incompleta.');
90
+ if (quoted) throw new Error(t("CSV/TSV has an incomplete quoted cell."));
90
91
  if (cell || row.length) { row.push(cell); rows.push(row); }
91
92
  return rows;
92
93
  }
@@ -1,25 +1,43 @@
1
- import { randomUUID } from 'node:crypto';
1
+ import { t } from '../i18n/index.mjs';
2
+ import { randomUUID, createHash } from 'node:crypto';
3
+ import { isRemoteCommand } from '../remote-commands.mjs';
2
4
 
3
5
  export class MessageQueue {
4
6
  constructor(snapshot, persist = async () => {}) {
5
7
  this.persist = persist; this.items = snapshot?.items || []; this.running = null;
6
8
  this.interrupted = [...(snapshot?.interrupted || []), ...(snapshot?.running ? [snapshot.running] : [])];
7
9
  this.paused = Boolean(snapshot?.paused || this.items.length || this.interrupted.length);
10
+ this.remoteAdmissions = snapshot?.remoteAdmissions || {};
11
+ this.remoteWriter = Promise.resolve();
8
12
  }
9
- snapshot() { return structuredClone({ items: this.items, running: this.running, interrupted: this.interrupted, paused: this.paused }); }
13
+ snapshot() { return structuredClone({ items: this.items, running: this.running, interrupted: this.interrupted, paused: this.paused, remoteAdmissions: this.remoteAdmissions }); }
10
14
  async save() { await this.persist(this.snapshot()); }
11
15
  validate(text, excludeId) {
12
- if (!text.trim()) throw new Error('El mensaje está vacío.');
13
- if (this.items.length >= 32 && !excludeId) throw new Error('La cola admite hasta 32 mensajes.');
16
+ if (!text.trim()) throw new Error(t("The message is empty."));
17
+ if (this.items.length >= 32 && !excludeId) throw new Error(t("The queue supports up to 32 messages."));
14
18
  const bytes = Buffer.byteLength(text) + this.items.filter(i => i.id !== excludeId).reduce((sum, i) => sum + Buffer.byteLength(i.text), 0);
15
- if (bytes > 1024 * 1024) throw new Error('La cola admite hasta 1 MiB de texto.');
19
+ if (bytes > 1024 * 1024) throw new Error(t("The queue supports up to 1 MiB of text."));
16
20
  }
17
- async enqueue(text) {
18
- this.validate(text); const item = { id: randomUUID(), text, createdAt: new Date().toISOString() };
21
+ async enqueue(text, attachments = []) {
22
+ this.validate(text); if (!Array.isArray(attachments) || attachments.length > 2 || Buffer.byteLength(JSON.stringify(attachments)) > 32768) throw new Error('Invalid image attachment metadata.');
23
+ const item = { id: randomUUID(), text, ...(attachments.length ? {attachments: structuredClone(attachments)} : {}), createdAt: new Date().toISOString() };
19
24
  this.items.push(item);
20
25
  try { await this.save(); } catch (e) { this.items = this.items.filter(i => i.id !== item.id); throw e; }
21
26
  return item;
22
27
  }
28
+ enqueueRemote(text, remote) {
29
+ const action = this.remoteWriter.catch(() => {}).then(async () => {
30
+ if (!remote || !/^[a-f0-9]{32}$/.test(remote.session) || !/^[A-Za-z0-9_-]{8,128}$/.test(remote.request) || Buffer.byteLength(text)>16384 || text.trimStart().startsWith('/') && !isRemoteCommand(text)) throw new Error('Remote input must be plain text or an admitted remote command.');
31
+ const key=remote.session+':'+remote.request, digest=createHash('sha256').update(JSON.stringify([remote.session,remote.request,text])).digest('hex'), previous=this.remoteAdmissions[key];
32
+ if(previous){if(previous.digest!==digest)throw new Error('Remote request ID already names another message.');return {id:previous.id,duplicate:true};}
33
+ if(Object.keys(this.remoteAdmissions).length>=2048)throw new Error('Remote admission history is full. Start a new local session.');
34
+ this.validate(text);const item={id:randomUUID(),text,remote:{...remote},createdAt:new Date().toISOString()};
35
+ this.items.push(item);this.remoteAdmissions[key]={id:item.id,digest};
36
+ try{await this.save();}catch(error){this.items=this.items.filter(i=>i.id!==item.id);delete this.remoteAdmissions[key];throw error;}
37
+ return item;
38
+ });
39
+ this.remoteWriter=action;return action;
40
+ }
23
41
  async take() {
24
42
  if (this.paused || !this.items.length || this.running) return null;
25
43
  const item = this.items.shift(); this.running = item;
@@ -28,12 +46,12 @@ export class MessageQueue {
28
46
  }
29
47
  async finish(error = false) { this.running = null; if (error) this.paused = true; await this.save(); }
30
48
  async defer() { if (this.running) { this.items.unshift(this.running); this.running = null; await this.save(); } }
31
- async update(id, text) { this.validate(text, id); const item = this.items.find(i => i.id === id); if (!item) throw new Error('Ese mensaje ya no está en cola.'); const old = item.text; item.text = text; try { await this.save(); } catch (e) { item.text = old; throw e; } }
49
+ async update(id, text, attachments) { this.validate(text, id); if (attachments !== undefined && (!Array.isArray(attachments) || attachments.length > 2 || Buffer.byteLength(JSON.stringify(attachments)) > 32768)) throw new Error('Invalid image attachment metadata.'); const item = this.items.find(i => i.id === id); if (!item) throw new Error(t("That message is no longer in queue.")); const old = item.text, oldAttachments = item.attachments; item.text = text; if (attachments !== undefined) item.attachments = structuredClone(attachments); try { await this.save(); } catch (e) { item.text = old; item.attachments = oldAttachments; throw e; } }
32
50
  async remove(id) { this.items = this.items.filter(i => i.id !== id); await this.save(); }
33
51
  async move(id, delta) { const index = this.items.findIndex(i => i.id === id), next = index + delta; if (index < 0 || next < 0 || next >= this.items.length) return; const [item] = this.items.splice(index, 1); this.items.splice(next, 0, item); await this.save(); }
34
52
  async setPaused(paused) { this.paused = paused; await this.save(); }
35
53
  async clear() { this.items = []; this.interrupted = []; await this.save(); }
36
- async retryInterrupted(id) { const item = this.interrupted.find(i => i.id === id); if (!item) return; await this.enqueue(item.text); this.interrupted = this.interrupted.filter(i => i.id !== id); await this.save(); }
54
+ async retryInterrupted(id) { const item = this.interrupted.find(i => i.id === id); if (!item) return; await this.enqueue(item.text, item.attachments); this.interrupted = this.interrupted.filter(i => i.id !== id); await this.save(); }
37
55
  }
38
56
 
39
57
  export class QueueRunner {