@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.
- package/README.md +30 -28
- package/backend/contracts/remote-commands-v1.json +125 -0
- package/bin/comal.mjs +16 -5
- package/docs/investigation.md +2 -2
- package/governance/chrome-task-v2.json +67 -0
- package/node_modules/@jitl/quickjs-ffi-types/LICENSE +21 -0
- package/node_modules/@jitl/quickjs-ffi-types/README.md +5 -0
- package/node_modules/@jitl/quickjs-ffi-types/dist/index.d.mts +549 -0
- package/node_modules/@jitl/quickjs-ffi-types/dist/index.d.ts +549 -0
- package/node_modules/@jitl/quickjs-ffi-types/dist/index.js +2 -0
- package/node_modules/@jitl/quickjs-ffi-types/dist/index.js.map +1 -0
- package/node_modules/@jitl/quickjs-ffi-types/dist/index.mjs +2 -0
- package/node_modules/@jitl/quickjs-ffi-types/dist/index.mjs.map +1 -0
- package/node_modules/@jitl/quickjs-ffi-types/package.json +36 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/LICENSE +47 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/README.md +93 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/emscripten-module.browser.d.ts +15 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/emscripten-module.browser.mjs +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/emscripten-module.cjs +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/emscripten-module.cloudflare.cjs +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/emscripten-module.cloudflare.d.ts +15 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/emscripten-module.d.ts +15 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/emscripten-module.mjs +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/emscripten-module.wasm +0 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/emscripten-module.wasm.map +1 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/ffi.d.mts +94 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/ffi.d.ts +94 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/ffi.js +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/ffi.js.map +1 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/ffi.mjs +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/ffi.mjs.map +1 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/index.d.mts +20 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/index.d.ts +20 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/index.js +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/index.js.map +1 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/index.mjs +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/dist/index.mjs.map +1 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-asyncify/package.json +61 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/LICENSE +47 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/README.md +88 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/emscripten-module.browser.d.ts +11 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/emscripten-module.browser.mjs +5430 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/emscripten-module.cjs +5485 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/emscripten-module.cloudflare.cjs +5424 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/emscripten-module.cloudflare.d.ts +11 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/emscripten-module.d.ts +11 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/emscripten-module.mjs +5541 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/emscripten-module.wasm +0 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/emscripten-module.wasm.map +1 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/ffi.d.mts +85 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/ffi.d.ts +85 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/ffi.js +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/ffi.js.map +1 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/ffi.mjs +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/ffi.mjs.map +1 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/index.d.mts +20 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/index.d.ts +20 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/index.js +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/index.js.map +1 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/index.mjs +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/dist/index.mjs.map +1 -0
- package/node_modules/@jitl/quickjs-wasmfile-debug-sync/package.json +61 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/LICENSE +47 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/README.md +89 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/emscripten-module.browser.d.ts +15 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/emscripten-module.browser.mjs +26 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/emscripten-module.cjs +25 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/emscripten-module.cloudflare.cjs +25 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/emscripten-module.cloudflare.d.ts +15 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/emscripten-module.d.ts +15 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/emscripten-module.mjs +30 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/emscripten-module.wasm +0 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/ffi.d.mts +94 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/ffi.d.ts +94 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/ffi.js +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/ffi.js.map +1 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/ffi.mjs +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/ffi.mjs.map +1 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/index.d.mts +20 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/index.d.ts +20 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/index.js +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/index.js.map +1 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/index.mjs +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/dist/index.mjs.map +1 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-asyncify/package.json +61 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/LICENSE +47 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/README.md +82 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.browser.d.ts +11 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.browser.mjs +22 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.cjs +21 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.cloudflare.cjs +21 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.cloudflare.d.ts +11 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.d.ts +11 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.mjs +25 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.wasm +0 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/ffi.d.mts +85 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/ffi.d.ts +85 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/ffi.js +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/ffi.js.map +1 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/ffi.mjs +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/ffi.mjs.map +1 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/index.d.mts +20 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/index.d.ts +20 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/index.js +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/index.js.map +1 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/index.mjs +2 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/dist/index.mjs.map +1 -0
- package/node_modules/@jitl/quickjs-wasmfile-release-sync/package.json +61 -0
- package/node_modules/quickjs-emscripten/LICENSE +21 -0
- package/node_modules/quickjs-emscripten/README.md +928 -0
- package/node_modules/quickjs-emscripten/dist/chunk-OHAYRCBA.mjs +2 -0
- package/node_modules/quickjs-emscripten/dist/chunk-OHAYRCBA.mjs.map +1 -0
- package/node_modules/quickjs-emscripten/dist/index.d.mts +57 -0
- package/node_modules/quickjs-emscripten/dist/index.d.ts +57 -0
- package/node_modules/quickjs-emscripten/dist/index.global.js +7309 -0
- package/node_modules/quickjs-emscripten/dist/index.js +2 -0
- package/node_modules/quickjs-emscripten/dist/index.js.map +1 -0
- package/node_modules/quickjs-emscripten/dist/index.mjs +2 -0
- package/node_modules/quickjs-emscripten/dist/index.mjs.map +1 -0
- package/node_modules/quickjs-emscripten/dist/variants.d.mts +39 -0
- package/node_modules/quickjs-emscripten/dist/variants.d.ts +39 -0
- package/node_modules/quickjs-emscripten/dist/variants.js +2 -0
- package/node_modules/quickjs-emscripten/dist/variants.js.map +1 -0
- package/node_modules/quickjs-emscripten/dist/variants.mjs +2 -0
- package/node_modules/quickjs-emscripten/dist/variants.mjs.map +1 -0
- package/node_modules/quickjs-emscripten/package.json +69 -0
- package/node_modules/quickjs-emscripten-core/LICENSE +21 -0
- package/node_modules/quickjs-emscripten-core/README.md +341 -0
- package/node_modules/quickjs-emscripten-core/README.template.md +70 -0
- package/node_modules/quickjs-emscripten-core/dist/chunk-TAV5CUKK.mjs +2 -0
- package/node_modules/quickjs-emscripten-core/dist/chunk-TAV5CUKK.mjs.map +1 -0
- package/node_modules/quickjs-emscripten-core/dist/chunk-V2S4ZYJR.mjs +7 -0
- package/node_modules/quickjs-emscripten-core/dist/chunk-V2S4ZYJR.mjs.map +1 -0
- package/node_modules/quickjs-emscripten-core/dist/index.d.mts +2033 -0
- package/node_modules/quickjs-emscripten-core/dist/index.d.ts +2033 -0
- package/node_modules/quickjs-emscripten-core/dist/index.js +7 -0
- package/node_modules/quickjs-emscripten-core/dist/index.js.map +1 -0
- package/node_modules/quickjs-emscripten-core/dist/index.mjs +2 -0
- package/node_modules/quickjs-emscripten-core/dist/index.mjs.map +1 -0
- package/node_modules/quickjs-emscripten-core/dist/module-ES6BEMUI.mjs +2 -0
- package/node_modules/quickjs-emscripten-core/dist/module-ES6BEMUI.mjs.map +1 -0
- package/node_modules/quickjs-emscripten-core/dist/module-asyncify-2EFITU5U.mjs +2 -0
- package/node_modules/quickjs-emscripten-core/dist/module-asyncify-2EFITU5U.mjs.map +1 -0
- package/node_modules/quickjs-emscripten-core/package.json +49 -0
- package/package.json +6 -2
- package/src/agent.mjs +17 -6
- package/src/artifact-tools.mjs +3 -2
- package/src/auto-guard.mjs +5 -1
- package/src/cli.mjs +267 -201
- package/src/cloud-client.mjs +58 -8
- package/src/code-run-worker.mjs +70 -0
- package/src/code-run.mjs +97 -0
- package/src/codex-provider.mjs +28 -28
- package/src/compaction.mjs +1 -1
- package/src/goals.mjs +2 -2
- package/src/house.mjs +24 -24
- package/src/i18n/cli-es.mjs +97 -0
- package/src/i18n/es.mjs +227 -0
- package/src/i18n/index.mjs +48 -0
- package/src/image-attachments.mjs +263 -0
- package/src/model-controls.mjs +4 -4
- package/src/onboarding.mjs +91 -0
- package/src/openai-provider.mjs +24 -24
- package/src/peer-tools.mjs +4 -3
- package/src/permissions.mjs +53 -17
- package/src/provider-auth.mjs +30 -30
- package/src/provider-profiles.mjs +27 -27
- package/src/provider.mjs +30 -30
- package/src/remote-commands.mjs +11 -0
- package/src/remote-control.mjs +180 -0
- package/src/sessions.mjs +3 -3
- package/src/subagents.mjs +7 -3
- package/src/tool-packs.mjs +5 -2
- package/src/tools.mjs +92 -34
- package/src/tui/brand.mjs +2 -1
- package/src/tui/clipboard.mjs +5 -4
- package/src/tui/composer.mjs +77 -11
- package/src/tui/controller.mjs +111 -29
- package/src/tui/discovery.mjs +105 -100
- package/src/tui/markdown.mjs +10 -9
- package/src/tui/message-queue.mjs +27 -9
- package/src/tui/screen.mjs +209 -118
- package/src/vision.mjs +31 -3
- package/src/work-assertions.mjs +7 -7
- package/src/work-plan.mjs +189 -21
- package/src/work-recovery-proof.mjs +13 -1
- package/src/workflows/chrome-task.mjs +24 -7
package/src/tui/screen.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t, formatNumber } from '../i18n/index.mjs';
|
|
1
2
|
import { workProjection } from '../work-plan.mjs';
|
|
2
3
|
import { Composer } from './composer.mjs';
|
|
3
4
|
import { MarkdownRenderer, DataTableParser } from './markdown.mjs';
|
|
@@ -10,51 +11,20 @@ import { commandCatalog, commandSuggestions, applySuggestion, contextualTip } fr
|
|
|
10
11
|
const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
11
12
|
const MOUSE_ON = '\x1b[?1000h\x1b[?1006h';
|
|
12
13
|
const MOUSE_OFF = '\x1b[?1000l\x1b[?1006l';
|
|
13
|
-
const HELP =
|
|
14
|
-
|
|
15
|
-
| Acción | Tecla |
|
|
16
|
-
| --- | --- |
|
|
17
|
-
| Descubrir comandos y herramientas | F6 |
|
|
18
|
-
| Completar comando o argumento | / y Tab; ↑/↓ elige; Esc cierra |
|
|
19
|
-
| Enviar / encolar | Enter (las sugerencias sólo completan) |
|
|
20
|
-
| Nueva línea | Alt+Enter o Ctrl+J |
|
|
21
|
-
| Cambiar modo | Shift+Tab |
|
|
22
|
-
| Editar con $VISUAL/$EDITOR | Ctrl+G |
|
|
23
|
-
| Tablero de trabajo | F3 o /board |
|
|
24
|
-
| Continuar desde checkpoint | /work resume |
|
|
25
|
-
| Sesiones / reanudar | F2 o Ctrl+S |
|
|
26
|
-
| Cola de mensajes | F4 o Ctrl+Q |
|
|
27
|
-
| Desplegar resultados de herramientas | Ctrl+O |
|
|
28
|
-
| Scroll del historial | Rueda del mouse / PgUp / PgDn |
|
|
29
|
-
| Seleccionar texto con el mouse | F5 o /select; F5 o Esc vuelve |
|
|
30
|
-
| Copiar última respuesta completa | Alt+C o /copy |
|
|
31
|
-
| Ir al final | Ctrl+End |
|
|
32
|
-
| Historial del composer | ↑/↓ en primera/última línea |
|
|
33
|
-
| Inicio / final de línea | Home / End o Ctrl+A / Ctrl+E |
|
|
34
|
-
| Borrar palabra / inicio / final | Ctrl+W / Ctrl+U / Ctrl+K |
|
|
35
|
-
| Cancelar turno / pausar goal | Ctrl+C o Esc mientras trabaja |
|
|
36
|
-
| Salir | Ctrl+D con composer vacío o /exit |
|
|
37
|
-
|
|
38
|
-
Puedes seguir escribiendo durante el streaming. Los mensajes enviados se ejecutan en orden al terminar el turno actual. F4 permite pausar, editar, quitar y reordenar la cola.
|
|
39
|
-
|
|
40
|
-
/context muestra el presupuesto de contexto. /compact deriva el estado sin LLM y espera su turno si hay trabajo activo. /state muestra petición y estado; /audit verifica el libro. El historial original se conserva.
|
|
41
|
-
/house muestra recibos de ejecución y operaciones interrumpidas; /operations muestra contratos y efectos declarados. El éxito de ejecución no certifica el objetivo humano.
|
|
42
|
-
/plugins muestra plugins, dependencias y operaciones; enable/disable/reload esperan su turno y sólo el usuario puede cambiar su ciclo de vida.
|
|
43
|
-
/model ID y /effort NIVEL cambian la siguiente llamada; durante trabajo quedan en cola. /models lista modelos y /capabilities muestra soporte, procedencia y niveles. E indica esfuerzo; Ctx muestra la ventana efectiva (k/M tokens); ~ indica un límite configurado, ? capacidad desconocida/fallback. /context muestra valores exactos, reserva y fuente. /login PROVEEDOR autentica; /logout PROVEEDOR retira credenciales de Comal.
|
|
44
|
-
|
|
45
|
-
**La ayuda mantiene activo el composer.** Puedes escribir mientras la consultas; PgUp/PgDn la desplaza y Esc/F1 la cierra. Al enviar vuelve la conversación. La rueda desplaza la conversación; las flechas editan el borrador. F5 libera el mouse y congela la vista: arrastra para seleccionar y usa la acción de copiar de tu terminal (normalmente Ctrl+Shift+C). El trabajo continúa y la vista se actualiza al volver. /copy conserva Markdown y bloques de código, sin el ajuste de líneas de la pantalla. El pegado multilínea no se envía automáticamente.`;
|
|
14
|
+
const HELP = "# Comal · keyboard\n\n| Action | Key |\n| --- | --- |\n| Discover commands and tools | F6 |\n| Complete command or argument | / and Tab; ↑/↓ chooses; Esc closes |\n| Send / enqueue | Enter (suggestions only complete) |\n| New line | Alt+Enter or Ctrl+J |\n| Switch mode | Shift+Tab |\n| Edit with $VISUAL/$EDITOR | Ctrl+G |\n| Work board | F3 or /board |\n| Continue from checkpoint | /work resume |\n| Sessions / resume | F2 or Ctrl+S |\n| Message queue | F4 or Ctrl+Q |\n| Expand tool results | Ctrl+O |\n| Scroll history | Mouse wheel / PgUp / PgDn |\n| Select text with mouse | F5 or /select; F5 or Esc returns |\n| Copy last full response | Alt+C or /copy |\n| Go to end | Ctrl+End |\n| Composer history | ↑/↓ on first/last line |\n| Start / end of line | Home / End or Ctrl+A / Ctrl+E |\n| Delete word / start / end | Ctrl+W / Ctrl+U / Ctrl+K |\n| Cancel turn / pause goal | Ctrl+C or Esc while working |\n| Exit | Ctrl+D with empty composer or /exit |\n\nYou can keep typing during streaming. Sent messages run in order when the current turn ends. F4 lets you pause, edit, remove, and reorder the queue.\n\n/context shows the context budget. /compact derives state without LLM and waits its turn if there is active work. /state shows request and state; /audit verifies the ledger. The original history is preserved.\n/house shows execution receipts and interrupted operations; /operations shows contracts and declared effects. Execution success does not certify the human goal.\n/plugins shows plugins, dependencies, and operations; enable/disable/reload wait their turn and only the user can change their lifecycle.\n/model ID and /effort LEVEL change the next call; during work they stay queued. /models lists models and /capabilities shows support, provenance, and levels. E indicates effort; Ctx shows the effective window (k/M tokens); ~ indicates a configured limit, ? unknown/fallback capability. /context shows exact values, reserve, and source. /login PROVIDER authenticates; /logout PROVIDER removes credentials from Comal.\n\n**Help keeps the composer active.** You can type while viewing it; PgUp/PgDn scrolls it and Esc/F1 closes it. Sending returns to the conversation. The wheel scrolls the conversation; the arrows edit the draft. F5 releases the mouse and freezes the view: drag to select and use your terminal's copy action (usually Ctrl+Shift+C). Work continues and the view updates when you return. /copy preserves Markdown and code blocks, without the screen line wrapping. Multi-line paste is not sent automatically.";
|
|
46
15
|
|
|
47
16
|
export class TerminalScreen {
|
|
48
17
|
constructor({ input = process.stdin, output = process.stdout, metadata = {}, clipboard = copyText, env = process.env } = {}) {
|
|
49
18
|
Object.assign(this, { input, output, metadata, env }); this.composer = new Composer(); this.markdown = new MarkdownRenderer(); this.tables = new DataTableParser();
|
|
50
19
|
this.clipboard = clipboard; this.selecting = false;
|
|
51
20
|
this.blocks = []; this.toolBlocks = new Map(); this.subagentBlocks = new Map(); this.countedChildResults=new Set(); this.queue = { items: [], paused: false, interrupted: [] }; this.busy = false; this.ready = false;
|
|
52
|
-
this.mode = metadata.mode || 'default'; this.phase =
|
|
21
|
+
this.mode = metadata.mode || 'default'; this.phase = t("Connecting to the model"); this.frame = 0; this.scroll = 0; this.lastContentLength = 0; this.expandedTools = false;
|
|
53
22
|
this.discovery = {}; this.completionIndex = 0; this.brandStarted = Date.now();
|
|
54
23
|
this.callbacks = {}; this.closed = false; this.suspended = false; this.previous = []; this.goal = null; this.usage = 0; this.turn = 0; this.maxTurns = 12;
|
|
55
24
|
this.finished = new Promise(resolve => this.resolveFinished = resolve);
|
|
56
25
|
}
|
|
57
26
|
start() {
|
|
27
|
+
if (this.started || this.closed) return this;
|
|
58
28
|
this.started = true;
|
|
59
29
|
this.originalRaw = this.input.isRaw || false;
|
|
60
30
|
this.input.setRawMode?.(true); this.input.resume();
|
|
@@ -65,11 +35,113 @@ export class TerminalScreen {
|
|
|
65
35
|
this.output.write('\x1b[?1049h\x1b[?2004h\x1b[?25l' + MOUSE_ON);
|
|
66
36
|
this.timer = setInterval(() => {
|
|
67
37
|
const welcome = !this.blocks.length && this.motionEnabled() && this.brandFrame() < BRAND_WELCOME_FRAMES;
|
|
68
|
-
if ((this.busy || !this.ready || welcome) && !this.suspended && !this.selecting) { this.frame++; this.render(); }
|
|
38
|
+
if ((this.busy || !this.ready || welcome || this.setup?.kind === 'status' || this.setup?.validating) && !this.suspended && !this.selecting) { this.frame++; this.render(); }
|
|
69
39
|
}, 120);
|
|
70
40
|
this.render(); return this;
|
|
71
41
|
}
|
|
72
|
-
configure(callbacks) { this.callbacks = callbacks; this.ready = true; this.phase =
|
|
42
|
+
configure(callbacks) { this.clearSetup(); this.callbacks = callbacks; this.ready = true; this.phase = t("Ready"); this.schedule(); }
|
|
43
|
+
// Setup owns the existing terminal surface before a provider/session exists.
|
|
44
|
+
// Its editor is isolated from conversation drafts, history and callbacks.
|
|
45
|
+
beginSetup(state, signal, resolve = () => {}) {
|
|
46
|
+
this.clearSetup();
|
|
47
|
+
if (this.closed || signal?.aborted) { resolve(null); return null; }
|
|
48
|
+
let settled = false;
|
|
49
|
+
state.finish = value => {
|
|
50
|
+
if (settled) return; settled = true;
|
|
51
|
+
signal?.removeEventListener('abort', abort);
|
|
52
|
+
state.editor?.clear(); state.editor = null;
|
|
53
|
+
if (this.setup === state) this.setup = null;
|
|
54
|
+
resolve(value); this.schedule();
|
|
55
|
+
};
|
|
56
|
+
const abort = () => state.finish(null);
|
|
57
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
58
|
+
this.setup = state; this.schedule(); return state;
|
|
59
|
+
}
|
|
60
|
+
clearSetup() { this.setup?.finish(null); }
|
|
61
|
+
setupChoice({ title, description = '', items, signal } = {}) {
|
|
62
|
+
if (!Array.isArray(items) || !items.length || items.length > 100 || items.some(item => typeof item?.id !== 'string' || !item.id || typeof item.label !== 'string') || new Set(items.map(item => item.id)).size !== items.length) throw new Error(t('Setup requires between 1 and 100 distinct choices.'));
|
|
63
|
+
return new Promise(resolve => this.beginSetup({ kind: 'choice', title: safeText(title || t('Choose a connection')), description: safeText(description), items: items.map(item => ({ id: item.id, label: safeText(item.label), description: safeText(item.description || '') })), index: 0 }, signal, resolve));
|
|
64
|
+
}
|
|
65
|
+
setupInput({ title, description = '', value = '', secret = false, signal, validate } = {}) {
|
|
66
|
+
const clean = safeText(value).replace(/[\r\n\t]+/g, ' ');
|
|
67
|
+
if (Buffer.byteLength(clean) > 4096) throw new Error(t('Setup values support up to 4096 bytes.'));
|
|
68
|
+
return new Promise(resolve => this.beginSetup({ kind: 'input', title: safeText(title || t('Connection settings')), description: safeText(description), editor: new Composer(clean), secret: Boolean(secret), validate }, signal, resolve));
|
|
69
|
+
}
|
|
70
|
+
setupStatus({ title, description = '', signal } = {}) {
|
|
71
|
+
this.beginSetup({ kind: 'status', title: safeText(title || t('Connecting')), description: safeText(description) }, signal);
|
|
72
|
+
}
|
|
73
|
+
insertSetupText(text) {
|
|
74
|
+
const state = this.setup; if (state?.kind !== 'input' || state.validating) return;
|
|
75
|
+
const clean = safeText(text, { preserveTabs: true }).replace(/[\r\n\t]+/g, ' ');
|
|
76
|
+
if (Buffer.byteLength(state.editor.text + clean) > 4096) state.error = t('Use at most 4096 bytes.');
|
|
77
|
+
else { state.editor.insert(clean); state.error = null; }
|
|
78
|
+
this.schedule();
|
|
79
|
+
}
|
|
80
|
+
async handleSetupKey(text, key) {
|
|
81
|
+
const state = this.setup, name = key.name;
|
|
82
|
+
if (name === 'escape' || key.ctrl && name === 'c') {
|
|
83
|
+
state.finish(null);
|
|
84
|
+
if (state.kind === 'status') await this.callbacks.onSetupCancel?.();
|
|
85
|
+
if (key.ctrl && name === 'c') { if (this.callbacks.onInterrupt) await this.callbacks.onInterrupt(); else await this.callbacks.onAction?.('interrupt'); }
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (state.kind === 'status' || state.validating) return;
|
|
89
|
+
if (state.kind === 'choice') {
|
|
90
|
+
if (name === 'up') state.index = Math.max(0, state.index - 1);
|
|
91
|
+
else if (name === 'down') state.index = Math.min(state.items.length - 1, state.index + 1);
|
|
92
|
+
else if (name === 'home') state.index = 0;
|
|
93
|
+
else if (name === 'end') state.index = state.items.length - 1;
|
|
94
|
+
else if (name === 'return' && !key.meta && !key.shift) state.finish(state.items[state.index].id);
|
|
95
|
+
this.schedule(); return;
|
|
96
|
+
}
|
|
97
|
+
if (name === 'return' && !key.meta && !key.shift) {
|
|
98
|
+
const value = state.editor.text; state.validating = true; state.error = null; this.schedule();
|
|
99
|
+
try {
|
|
100
|
+
const result = await state.validate?.(value);
|
|
101
|
+
if (this.setup !== state) return;
|
|
102
|
+
if (result === false || typeof result === 'string' && result.length) state.error = state.secret ? t('Check the value and try again.') : safeText(typeof result === 'string' ? result : t('Check the value and try again.'));
|
|
103
|
+
else state.finish(value);
|
|
104
|
+
} catch (error) { if (this.setup === state) state.error = state.secret ? t('Check the value and try again.') : safeText(error?.message || t('Check the value and try again.')); }
|
|
105
|
+
finally { state.validating = false; this.schedule(); }
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (name === 'return' || name === 'linefeed' || key.ctrl && name === 'j') return;
|
|
109
|
+
if (!key.ctrl && !key.meta && text && !text.startsWith('\x1b')) this.insertSetupText(text);
|
|
110
|
+
else { state.editor.key('', key); state.error = null; this.schedule(); }
|
|
111
|
+
}
|
|
112
|
+
renderSetup(columns, rows) {
|
|
113
|
+
const state = this.setup, inner = Math.max(1, columns - 4);
|
|
114
|
+
const header = [tone.bold(' COMAL') + tone.accent(t(' · setup')), tone.muted('─'.repeat(columns))];
|
|
115
|
+
const title = wrap(state.title, inner).map(line => ' ' + tone.accent(line));
|
|
116
|
+
const description = wrap(state.description, inner).map(line => ' ' + tone.muted(line));
|
|
117
|
+
const prefix = [...header, '', ...title, '', ...description, ''];
|
|
118
|
+
const cancellation = this.ready ? t('Ctrl+C cancels') : t('Ctrl+C exits');
|
|
119
|
+
const hint = (state.kind === 'choice' ? t('↑/↓ choose · Enter continues · Esc back') : state.kind === 'input' ? t('Enter continues · Esc back') : t('Esc back')) + ' · ' + cancellation;
|
|
120
|
+
let body = [], cursor = null;
|
|
121
|
+
const budget = Math.max(1, rows - Math.min(prefix.length, rows - 4) - 2);
|
|
122
|
+
if (state.kind === 'choice') {
|
|
123
|
+
const start = Math.max(0, state.index - Math.max(1, Math.floor(budget / 2)) + 1);
|
|
124
|
+
for (let index = start; index < state.items.length && body.length < budget; index++) {
|
|
125
|
+
const item = state.items[index], selected = index === state.index;
|
|
126
|
+
body.push((selected ? tone.accent : tone.muted)(` ${selected ? '❯' : ' '} ${clip(item.label, Math.max(1, columns - 5))}`));
|
|
127
|
+
if (item.description && body.length < budget) body.push(' ' + tone.muted(clip(item.description, Math.max(1, columns - 5))));
|
|
128
|
+
}
|
|
129
|
+
} else if (state.kind === 'input') {
|
|
130
|
+
const editor = state.editor, display = state.secret ? new Composer('•'.repeat(editor.buffer.length)) : editor;
|
|
131
|
+
if (state.secret) display.cursor = editor.cursor;
|
|
132
|
+
const layout = display.layout(Math.max(2, columns - 6)), height = Math.max(1, budget - (state.error ? 1 : 0)), offset = Math.max(0, layout.cursorRow - height + 1);
|
|
133
|
+
body = layout.lines.slice(offset, offset + height).map((line, index) => tone.muted(index === 0 ? ' ❯ ' : ' │ ') + line);
|
|
134
|
+
cursor = { row: layout.cursorRow - offset + 1, col: layout.cursorColumn + 5 };
|
|
135
|
+
if (state.error) body.push(' ' + tone.warning(clip(state.error, inner)));
|
|
136
|
+
} else body.push(' ' + tone.accent(this.spinner() + ' ' + t('Working…')));
|
|
137
|
+
if (state.validating) body.push(' ' + tone.muted(this.spinner() + ' ' + t('Checking…')));
|
|
138
|
+
const visiblePrefix = prefix.slice(0, Math.max(0, rows - Math.min(body.length, budget) - 2));
|
|
139
|
+
const lines = [...visiblePrefix, ...body.slice(0, budget)];
|
|
140
|
+
if (cursor) cursor.row += visiblePrefix.length;
|
|
141
|
+
while (lines.length < rows - 1) lines.push(''); lines.push(' ' + tone.muted(clip(hint, columns - 1)));
|
|
142
|
+
if (state.validating) cursor = null;
|
|
143
|
+
this.draw(lines, cursor, columns, rows);
|
|
144
|
+
}
|
|
73
145
|
schedule() {
|
|
74
146
|
if (this.closed || this.suspended || this.selecting || this.renderTimer) return;
|
|
75
147
|
this.renderTimer = setTimeout(() => { this.renderTimer = null; this.render(); }, 24);
|
|
@@ -80,7 +152,7 @@ export class TerminalScreen {
|
|
|
80
152
|
setDiscovery(context = {}) { this.discovery = structuredClone(context); this.schedule(); }
|
|
81
153
|
discoveryContext() { return { ...this.discovery, effortLevels: this.metadata.modelCapabilities?.effort?.levels || this.discovery.effortLevels, busy: this.busy, queue: this.queue, mode: this.mode, selecting: this.selecting, editingQueue: this.editingQueue }; }
|
|
82
154
|
refreshDiscovery() { this.callbacks.onDiscover?.(); }
|
|
83
|
-
hasModalOverlay() { return Boolean(this.overlay && this.overlay.kind !== 'help'); }
|
|
155
|
+
hasModalOverlay() { return Boolean(this.setup || this.overlay && this.overlay.kind !== 'help'); }
|
|
84
156
|
completion() {
|
|
85
157
|
const signature = JSON.stringify([this.composer.text, this.composer.cursor]);
|
|
86
158
|
if (signature !== this.completionSignature) { this.completionSignature = signature; this.completionIndex = 0; }
|
|
@@ -98,7 +170,7 @@ export class TerminalScreen {
|
|
|
98
170
|
if (!item) return false;
|
|
99
171
|
if (result.exact === item) result.items = [item];
|
|
100
172
|
const next = applySuggestion(this.composer.text, result, item);
|
|
101
|
-
this.composer.setText(next.text, next.cursor); this.draftChanged(); return true;
|
|
173
|
+
this.composer.setText(next.text, next.cursor, this.composer.attachments.map(({start,end,...item})=>item)); this.draftChanged(); return true;
|
|
102
174
|
}
|
|
103
175
|
showPalette() { this.refreshDiscovery(); this.overlay = { kind: 'palette', filter: '', index: 0, tab: 'command', detail: null, scroll: 0 }; this.schedule(); }
|
|
104
176
|
paletteItems() {
|
|
@@ -109,17 +181,18 @@ export class TerminalScreen {
|
|
|
109
181
|
paletteTool() { return this.discovery.tools?.find(tool => tool.name === this.overlay?.detail); }
|
|
110
182
|
preparePaletteItem(item) {
|
|
111
183
|
if (item.inspectOnly) { this.overlay.detail = item.label; this.overlay.scroll = 0; this.schedule(); return; }
|
|
112
|
-
if (!this.preparedDraft) this.preparedDraft = {
|
|
184
|
+
if (!this.preparedDraft) this.preparedDraft = { ...this.composer.snapshot(), sessionId: this.metadata.sessionId };
|
|
113
185
|
this.composer.setText(item.value);
|
|
114
186
|
this.closeOverlay(); this.draftChanged();
|
|
115
187
|
}
|
|
116
|
-
updateMetadata(metadata) {
|
|
188
|
+
updateMetadata(metadata) { const changed=['model','provider','profile','effort'].some(key=>metadata[key]!==undefined && metadata[key]!==this.metadata[key]); if(changed)this.cacheReadTokens=null; Object.assign(this.metadata, metadata); if (metadata.mode) this.mode = metadata.mode; if(changed)this.callbacks.onCapabilitiesChanged?.(); this.schedule(); }
|
|
117
189
|
setMode(mode) { this.mode = mode; this.schedule(); }
|
|
118
190
|
setQueue(snapshot) { this.queue = snapshot; this.schedule(); }
|
|
119
|
-
setBusy(value) { this.busy = value; if (value) { this.startedAt = Date.now(); this.turn = 0; this.phase =
|
|
191
|
+
setBusy(value) { this.busy = value; if (value) { this.startedAt = Date.now(); this.turn = 0; this.phase = t("Preparing turn"); } else this.phase = t("Ready"); this.schedule(); }
|
|
120
192
|
notice(text, error = false) { this.blocks.push({ role: 'notice', text: safeText(text), error }); if (error && this.overlay) this.overlay.error = safeText(text); this.schedule(); }
|
|
121
|
-
setDraft(text) { this.composer.setText(text); this.schedule(); }
|
|
122
|
-
|
|
193
|
+
setDraft(text, attachments = []) { this.composer.setText(text, undefined, attachments); this.schedule(); }
|
|
194
|
+
setAttachmentStatus(status) { this.attachmentStatus = status; this.schedule(); }
|
|
195
|
+
draftChanged() { if (!this.composer.attachments.length) this.attachmentStatus = null; this.callbacks.onDraft?.(this.composer.text, this.composer.attachments); this.schedule(); }
|
|
123
196
|
addTable(text, format, title) { const data = this.tables.parse(text, format); this.blocks.push({ role: 'table', data, text: title }); this.schedule(); }
|
|
124
197
|
loadSession(session) {
|
|
125
198
|
this.cacheReadTokens=null;
|
|
@@ -128,16 +201,19 @@ export class TerminalScreen {
|
|
|
128
201
|
for (const [responseId, row] of rows.entries()) {
|
|
129
202
|
const message = row.message;
|
|
130
203
|
if (message.role === 'user' && typeof message.content === 'string' && row.source !== 'goal') this.blocks.push({ role: 'user', text: message.content });
|
|
204
|
+
if (message.role === 'user' && row.source === 'human_composer' && Array.isArray(message.content)) this.blocks.push({role:'user',text:message.content.filter(block=>block.type==='text').map(block=>block.text).join('\n')});
|
|
131
205
|
for (const block of Array.isArray(message.content) ? message.content : []) {
|
|
132
206
|
if (message.role === 'assistant' && block.type === 'text') this.blocks.push({ role: 'assistant', text: block.text, responseId });
|
|
133
207
|
if (message.role === 'assistant' && block.type === 'tool_use') { const item = { role: 'tool', id: block.id, name: block.name, input: block.input, state: 'interrupted' }; this.blocks.push(item); this.toolBlocks.set(block.id, item); }
|
|
134
208
|
if (message.role === 'user' && block.type === 'tool_result') { const item = this.toolBlocks.get(block.tool_use_id); if (item) { item.state = block.is_error ? 'error' : 'done'; item.output = block.content; } }
|
|
135
209
|
}
|
|
136
210
|
}
|
|
137
|
-
|
|
138
|
-
this.composer.
|
|
211
|
+
const history = rows.filter(row => row.message.role === 'user' && row.source !== 'goal' && (typeof row.message.content === 'string' || row.source === 'human_composer')).slice(-100);
|
|
212
|
+
this.composer.history = history.map(row => typeof row.message.content === 'string' ? row.message.content : row.message.content.filter(block=>block.type==='text').map(block=>block.text).join('\n'));
|
|
213
|
+
this.composer.historyAttachments = history.map(row=>row.attachments || []);
|
|
214
|
+
this.composer.setText(session.draft || '', undefined, session.draftAttachments || []); this.attachmentStatus = null; this.metadata.sessionId = session.id; this.metadata.model = session.model; this.metadata.name = session.name;
|
|
139
215
|
this.context = null; this.work = workProjection(session);
|
|
140
|
-
if (session.compaction) this.notice(
|
|
216
|
+
if (session.compaction) this.notice(t("Context resumed from the ledger: ~{v0} tokens. Original history preserved.", {v0: session.compaction.after_tokens}));
|
|
141
217
|
this.countedChildResults.clear();let childUsage=0;
|
|
142
218
|
for (const row of session.rows.filter(row => row.type === 'subagent')) {
|
|
143
219
|
if(this.countChildResult(row))childUsage+=(row.usage?.input_tokens || 0)+(row.usage?.output_tokens || 0);
|
|
@@ -157,28 +233,28 @@ export class TerminalScreen {
|
|
|
157
233
|
handleEvent(event) {
|
|
158
234
|
if(event.type==='usage_accounting')this.cacheReadTokens=Number.isSafeInteger(event.accounting?.cache_read_tokens)?event.accounting.cache_read_tokens:null;
|
|
159
235
|
if (event.type === 'model_stream_progress') {
|
|
160
|
-
if (event.tool_argument_chars) this.phase =
|
|
161
|
-
else if (event.reasoning_chars && !event.text_chars) this.phase =
|
|
236
|
+
if (event.tool_argument_chars) this.phase = t("Preparing tools · {v0} characters", {v0: formatNumber(event.tool_argument_chars)});
|
|
237
|
+
else if (event.reasoning_chars && !event.text_chars) this.phase = t("Model reasoning · {v0} characters", {v0: formatNumber(event.reasoning_chars)});
|
|
162
238
|
}
|
|
163
239
|
if (event.type === 'context_usage') this.context = event;
|
|
164
240
|
if (event.type === 'vision_status') {
|
|
165
241
|
const stamp = JSON.stringify([event.supported, event.source, event.images, event.references?.map(ref => ref.sha256)]);
|
|
166
|
-
if (stamp !== this.lastVisionStamp) { this.lastVisionStamp = stamp; this.notice(event.images ?
|
|
242
|
+
if (stamp !== this.lastVisionStamp) { this.lastVisionStamp = stamp; this.notice(event.images ? t("Vision · {v0} images in the request · reserve ~{v1} tokens.", {v0: event.images, v1: event.token_reserve}) : t("Screenshot saved as a reference · {v0}.", {v0: event.supported ? t("no space for pixels in the context") : t("the provider does not advertise vision")})); }
|
|
167
243
|
}
|
|
168
|
-
if (event.type === 'context_projection_start') this.phase =
|
|
169
|
-
if (event.type === 'context_projection') this.notice(
|
|
244
|
+
if (event.type === 'context_projection_start') this.phase = t("Deriving state from the ledger");
|
|
245
|
+
if (event.type === 'context_projection') this.notice(t("State derived \u00b7 ~{v0} tokens \u00b7 0 LLM summary calls \u00b7 evidence saved.", {v0: event.after_tokens}));
|
|
170
246
|
if (['model_retry', 'response_discarded'].includes(event.type)) {
|
|
171
|
-
if (this.streaming) { this.streaming.text +=
|
|
172
|
-
if (event.type === 'model_retry') this.notice(
|
|
247
|
+
if (this.streaming) { this.streaming.text += t("\n\n[Incomplete response discarded; its tools were not executed.]"); this.streaming = null; }
|
|
248
|
+
if (event.type === 'model_retry') this.notice(t("Response discarded ({v0}) · bounded retry · maximum output {v1} tokens.", {v0: event.reason, v1: event.max_tokens}));
|
|
173
249
|
}
|
|
174
250
|
if (['mcp_reload', 'plugin_status'].includes(event.type)) this.refreshDiscovery();
|
|
175
|
-
if (event.type === 'mcp_reload') this.notice(
|
|
251
|
+
if (event.type === 'mcp_reload') this.notice(t("MCP updated · {v0} servers connected · {v1} tools available in packs.", {v0: event.servers.filter(server => server.status === 'connected').length, v1: event.tools.length}));
|
|
176
252
|
if (event.type === 'plugin_status') {
|
|
177
|
-
if (event.state === 'loading') this.phase =
|
|
178
|
-
if (['active', 'disabled'].includes(event.state)) this.notice(
|
|
253
|
+
if (event.state === 'loading') this.phase = t("Enabling plugin {v0}", {v0: event.id});
|
|
254
|
+
if (['active', 'disabled'].includes(event.state)) this.notice(t("Plugin {v0} · {v1}{v2}", {v0: event.id, v1: event.state, v2: event.operations ? t(" · {v0} operations", {v0: event.operations.length}) : ''}));
|
|
179
255
|
if (['failed', 'cleanup_failed', 'observer_failed'].includes(event.state)) this.notice(`Plugin ${event.id}: ${event.error}`, true);
|
|
180
256
|
}
|
|
181
|
-
if (event.type === 'mcp_reload_error') this.notice(
|
|
257
|
+
if (event.type === 'mcp_reload_error') this.notice(t("MCP reload not applied: {v0}", {v0: event.error}), true);
|
|
182
258
|
if (event.type === 'mcp_progress') {
|
|
183
259
|
const tool = [...this.toolBlocks.values()].findLast(block => block.name === event.name && block.state === 'running');
|
|
184
260
|
if (tool) { tool.progress = event; tool.cacheKey = null; }
|
|
@@ -203,19 +279,19 @@ export class TerminalScreen {
|
|
|
203
279
|
if (event.type === 'user' && event.source !== 'goal') this.blocks.push({ role: 'user', text: event.userIntent || event.text });
|
|
204
280
|
if (event.type === 'text_delta') {
|
|
205
281
|
if (!this.streaming) { this.streaming = { role: 'assistant', text: '' }; this.blocks.push(this.streaming); }
|
|
206
|
-
this.streaming.text += event.text; this.phase =
|
|
282
|
+
this.streaming.text += event.text; this.phase = t("Receiving response");
|
|
207
283
|
}
|
|
208
284
|
if (event.type === 'assistant') {
|
|
209
285
|
if (!this.streaming) { const text = event.message.content.filter(b => b.type === 'text').map(b => b.text).join(''); if (text) this.blocks.push({ role: 'assistant', text }); }
|
|
210
286
|
this.streaming = null;
|
|
211
287
|
}
|
|
212
|
-
if (event.type === 'tool_use') { const item = { role: 'tool', id: event.id, name: event.name, input: event.input, state: 'running' }; this.blocks.push(item); this.toolBlocks.set(event.id, item); this.phase =
|
|
288
|
+
if (event.type === 'tool_use') { const item = { role: 'tool', id: event.id, name: event.name, input: event.input, state: 'running' }; this.blocks.push(item); this.toolBlocks.set(event.id, item); this.phase = t("Executing {v0}", {v0: event.name}); }
|
|
213
289
|
if (event.type === 'tool_result') { const item = this.toolBlocks.get(event.tool_use_id); if (item) { item.state = event.is_error ? 'error' : 'done'; item.output = event.content; } }
|
|
214
290
|
if (event.type === 'auto_decision') this.blocks.push({ role: 'notice', text: `Auto · ${event.decision}: ${event.reason}`, error: event.decision === 'deny' });
|
|
215
291
|
if (event.type === 'model_progress') { this.turn = event.turn; this.maxTurns = event.maxTurns; }
|
|
216
292
|
if (event.type === 'phase') {
|
|
217
|
-
if (event.state === 'start') this.phase = { worker:
|
|
218
|
-
else if (this.busy) this.phase =
|
|
293
|
+
if (event.state === 'start') this.phase = { worker: t("Waiting for the model"), classifier: t("Checking auto permission"), 'goal-evaluation': t("Checking /goal") }[event.phase] || event.phase;
|
|
294
|
+
else if (this.busy) this.phase = t("Processing turn");
|
|
219
295
|
}
|
|
220
296
|
if (event.type === 'goal_status') {
|
|
221
297
|
const key = `${event.goal.id}:${event.goal.status}:${event.goal.lastReason}`;
|
|
@@ -223,10 +299,10 @@ export class TerminalScreen {
|
|
|
223
299
|
this.goal = event.goal;
|
|
224
300
|
}
|
|
225
301
|
if (event.type === 'result' && !event.usage_includes_subagents) this.usage += (event.usage?.input_tokens || 0) + (event.usage?.output_tokens || 0);
|
|
226
|
-
if (event.type === 'work_continuation') this.notice(
|
|
227
|
-
if(event.type==='peer_reply')this.notice(event.status==='failed'
|
|
228
|
-
if (event.type === 'result' && event.work_stop_reason) this.notice(
|
|
229
|
-
if (event.type === 'work_open') this.notice(
|
|
302
|
+
if (event.type === 'work_continuation') this.notice(t("Continues {v0} · {v1}/{v2} calls · checkpoint preserved.", {v0: event.task_id, v1: event.calls, v2: event.limit}));
|
|
303
|
+
if(event.type==='peer_reply')this.notice(event.status==='failed'?t("The house could not send the report: {v0}", {v0: event.error}):t("The house returned the report to mailbox {v0} · {v1}.", {v0: event.to.slice(0,8), v1: event.status}),event.status==='failed');
|
|
304
|
+
if (event.type === 'result' && event.work_stop_reason) this.notice(t("Work: {v0}.", {v0: event.work_stop_reason}));
|
|
305
|
+
if (event.type === 'work_open') this.notice(t("{v0} open tasks remain · /work resume continues from the checkpoint.", {v0: event.remaining.length}));
|
|
230
306
|
if (event.type === 'work_board' && (!event.session_id || event.session_id === this.metadata.sessionId)) this.work = event.board;
|
|
231
307
|
if (event.type === 'result' && event.work) this.work = event.work;
|
|
232
308
|
if (event.type === 'init') this.metadata.model = event.model;
|
|
@@ -237,9 +313,11 @@ export class TerminalScreen {
|
|
|
237
313
|
if (signal?.aborted || this.closed) return false;
|
|
238
314
|
if (this.selecting) this.setSelection(false);
|
|
239
315
|
return new Promise(resolve => {
|
|
240
|
-
|
|
316
|
+
let settled=false;
|
|
317
|
+
const finish = allowed => { if(settled)return;settled=true;signal?.removeEventListener('abort', abort);if(this.permission===question)this.permission = null;this.schedule();resolve(allowed); };
|
|
241
318
|
const abort = () => finish(false);
|
|
242
|
-
|
|
319
|
+
const question = { name, input, allowed: false, scroll: 0, finish };
|
|
320
|
+
this.permission = question;signal?.addEventListener('abort', abort, { once: true });if(signal?.aborted)abort();else this.schedule();
|
|
243
321
|
});
|
|
244
322
|
}
|
|
245
323
|
showSessions(items) { this.overlay = { kind: 'sessions', items, filter: '', index: Math.max(0, items.findIndex(item => item.id !== this.metadata.sessionId)) }; this.schedule(); }
|
|
@@ -248,6 +326,7 @@ export class TerminalScreen {
|
|
|
248
326
|
closeOverlay() { this.overlay = null; this.callbacks.onAction?.('queueKick'); this.schedule(); }
|
|
249
327
|
filteredSessions() { const query = this.overlay?.filter?.toLowerCase() || ''; return (this.overlay?.items || []).filter(item => `${item.name || ''} ${item.prompt} ${item.id} ${item.model}`.toLowerCase().includes(query)); }
|
|
250
328
|
paste(text) {
|
|
329
|
+
if (this.setup) { this.insertSetupText(text); return; }
|
|
251
330
|
if (this.selecting) return;
|
|
252
331
|
if (this.permission) return;
|
|
253
332
|
if (this.overlay?.kind === 'palette' && !this.overlay.detail) { this.overlay.filter += safeText(text).replace(/\n/g, ' ').slice(0, 150); this.overlay.index = 0; this.schedule(); return; }
|
|
@@ -264,14 +343,15 @@ export class TerminalScreen {
|
|
|
264
343
|
}
|
|
265
344
|
async copyResponse() {
|
|
266
345
|
const block = this.blocks.findLast(block => block.role === 'assistant' && block.text?.trim());
|
|
267
|
-
if (!block) throw new Error(
|
|
346
|
+
if (!block) throw new Error(t("There is no response to copy yet."));
|
|
268
347
|
const partial = block === this.streaming;
|
|
269
348
|
const text = block.responseId === undefined ? block.text : this.blocks.filter(item => item.role === 'assistant' && item.responseId === block.responseId).map(item => item.text).join('');
|
|
270
349
|
await this.clipboard(text);
|
|
271
|
-
this.notice(partial ?
|
|
350
|
+
this.notice(partial ? t("Text received so far copied to clipboard; the response is still arriving.") : t("Full response copied to clipboard (original Markdown)."));
|
|
272
351
|
}
|
|
273
352
|
handleMouse(event) {
|
|
274
353
|
if (this.closed || this.suspended || this.selecting || event.type !== 'wheel') return;
|
|
354
|
+
if (this.setup) { if (this.setup.kind === 'choice') this.setup.index = Math.max(0, Math.min(this.setup.items.length - 1, this.setup.index + event.delta)); this.schedule(); return; }
|
|
275
355
|
const lines = event.delta * 3;
|
|
276
356
|
if (this.permission) this.permission.scroll = Math.max(0, this.permission.scroll + lines);
|
|
277
357
|
else if (['help', 'board'].includes(this.overlay?.kind)) this.overlay.scroll = Math.max(0, this.overlay.scroll + lines);
|
|
@@ -286,6 +366,7 @@ export class TerminalScreen {
|
|
|
286
366
|
}
|
|
287
367
|
async handleKey(text, key = {}) {
|
|
288
368
|
if (this.closed || this.suspended) return;
|
|
369
|
+
if (this.setup) { await this.handleSetupKey(text, key); return; }
|
|
289
370
|
const name = key.name;
|
|
290
371
|
if (this.selecting) {
|
|
291
372
|
if (name === 'f5' || name === 'escape' || (key.ctrl && name === 'c')) this.setSelection(false);
|
|
@@ -312,10 +393,10 @@ export class TerminalScreen {
|
|
|
312
393
|
else if (name === 'pagedown' || name === 'down') this.overlay.scroll += 3;
|
|
313
394
|
else if (name === 'tab' && !key.shift) {
|
|
314
395
|
const tool = this.paletteTool();
|
|
315
|
-
if (!tool) { this.notice(
|
|
316
|
-
if (tool.audience === 'human') { this.notice(
|
|
317
|
-
if (!this.preparedDraft) this.preparedDraft = {
|
|
318
|
-
this.composer.setText(
|
|
396
|
+
if (!tool) { this.notice(t("That operation is no longer published. Return to the catalog."), true); return; }
|
|
397
|
+
if (tool.audience === 'human') { this.notice(t("Human control: use the corresponding command or /plugins run; the model is not asked."), true); return; }
|
|
398
|
+
if (!this.preparedDraft) this.preparedDraft = { ...this.composer.snapshot(), sessionId: this.metadata.sessionId };
|
|
399
|
+
this.composer.setText(t("Use the tool {v0} to {v1}", {v0: safeText(tool.name), v1: this.composer.text}), undefined, this.composer.attachments.map(({start,end,...item})=>item));
|
|
319
400
|
this.closeOverlay(); this.draftChanged(); return;
|
|
320
401
|
}
|
|
321
402
|
} else if (name === 'tab') { this.overlay.tab = this.overlay.tab === 'tool' ? 'command' : 'tool'; this.overlay.index = 0; }
|
|
@@ -363,7 +444,7 @@ export class TerminalScreen {
|
|
|
363
444
|
if (key.ctrl && name === 'g') { await this.callbacks.onAction?.('editor'); return; }
|
|
364
445
|
if (key.ctrl && name === 'd' && !this.composer.text) { await this.callbacks.onAction?.('exit'); return; }
|
|
365
446
|
if (name === 'escape' && this.completion().items.length) { this.dismissedCompletion = this.completionSignature; this.schedule(); return; }
|
|
366
|
-
if (name === 'escape' && this.preparedDraft) { const draft = this.preparedDraft; this.preparedDraft = null; this.composer.setText(draft.text, draft.cursor); this.draftChanged(); return; }
|
|
447
|
+
if (name === 'escape' && this.preparedDraft) { const draft = this.preparedDraft; this.preparedDraft = null; this.composer.setText(draft.text, draft.cursor, draft.attachments); this.draftChanged(); return; }
|
|
367
448
|
if (name === 'escape') { if (this.editingQueue) await this.callbacks.onAction?.('queueEditCancel'); else if (this.busy) await this.callbacks.onAction?.('interrupt'); return; }
|
|
368
449
|
if (name === 'pageup') { this.scroll += Math.max(5, (this.output.rows || 24) - 10); this.schedule(); return; }
|
|
369
450
|
if (name === 'pagedown') { this.scroll = Math.max(0, this.scroll - Math.max(5, (this.output.rows || 24) - 10)); this.schedule(); return; }
|
|
@@ -379,15 +460,23 @@ export class TerminalScreen {
|
|
|
379
460
|
}
|
|
380
461
|
if (name === 'return' && !key.shift && !key.meta && completion.items.length) { this.acceptCompletion(); return; }
|
|
381
462
|
if (name === 'return' && !key.shift && !key.meta) {
|
|
463
|
+
if (this.submitting) return;
|
|
382
464
|
const value = this.composer.text; if (!value.trim()) return;
|
|
383
|
-
if (!this.ready) { this.notice(
|
|
465
|
+
if (!this.ready) { this.notice(t("Still connecting; the draft is preserved.")); return; }
|
|
384
466
|
if (this.overlay?.kind === 'help') this.closeOverlay();
|
|
385
467
|
const draft = this.preparedDraft;
|
|
386
|
-
|
|
468
|
+
let prepared = this.composer.snapshot(), submitted = false;
|
|
469
|
+
this.submitting = true;
|
|
387
470
|
try {
|
|
388
|
-
|
|
389
|
-
if (
|
|
390
|
-
|
|
471
|
+
const original = JSON.stringify(prepared), sessionId = this.metadata.sessionId;
|
|
472
|
+
if (this.callbacks.onPrepareSubmit) prepared = await this.callbacks.onPrepareSubmit(prepared) || prepared;
|
|
473
|
+
const current = JSON.stringify(this.composer.snapshot());
|
|
474
|
+
if (sessionId !== this.metadata.sessionId || (current !== original && current !== JSON.stringify(prepared))) throw new Error(t('The draft changed while preparing the image. Press Enter again to send the current draft.'));
|
|
475
|
+
this.composer.remember(prepared.text, prepared.attachments); this.composer.clear(); this.draftChanged(); submitted = true;
|
|
476
|
+
await this.callbacks.onSubmit?.(prepared.text, prepared.attachments);
|
|
477
|
+
if (draft && draft.sessionId === this.metadata.sessionId && !this.composer.text) { this.preparedDraft = null; this.composer.setText(draft.text, draft.cursor, draft.attachments); this.draftChanged(); }
|
|
478
|
+
} catch (e) { if (submitted && !this.composer.text) this.composer.setText(prepared.text, prepared.cursor, prepared.attachments); this.preparedDraft = draft; this.notice(e.message, true); this.draftChanged(); }
|
|
479
|
+
finally { this.submitting = false; }
|
|
391
480
|
return;
|
|
392
481
|
}
|
|
393
482
|
this.composer.key(text, key, Math.max(2, (this.output.columns || 80) - 7)); this.draftChanged();
|
|
@@ -396,22 +485,22 @@ export class TerminalScreen {
|
|
|
396
485
|
const output = [];
|
|
397
486
|
if (!this.blocks.length) {
|
|
398
487
|
const mark = brandingWelcome({ columns: Math.max(0, columns - 4), frame: this.brandFrame(), active: this.busy, env: this.env, stream: this.output });
|
|
399
|
-
const intro = [tone.bold(
|
|
488
|
+
const intro = [tone.bold(t(" What are we building today?")), '', ...wrap(t(" Type a task; / or F6 show you what you can do."), columns), '', tone.muted(t(" F6 discover · F2 sessions · Shift+Tab mode"))];
|
|
400
489
|
const branded = ['', ...mark.map(line => ' ' + line), '', ...intro];
|
|
401
490
|
if (branded.length <= this.bodyHeight) output.push(...branded);
|
|
402
|
-
else output.push('', tone.accent(
|
|
491
|
+
else output.push('', tone.accent(t(" COMAL · on the comal one cooks")), '', ...intro);
|
|
403
492
|
}
|
|
404
493
|
for (const block of this.blocks) {
|
|
405
494
|
const cacheKey = `${columns}:${this.expandedTools}:${block.text || ''}:${block.state || ''}:${block.output || ''}`;
|
|
406
495
|
if (block.cacheKey === cacheKey) { output.push(...block.cached); continue; }
|
|
407
496
|
let lines;
|
|
408
|
-
if (block.role === 'user') lines = [tone.accent(
|
|
497
|
+
if (block.role === 'user') lines = [tone.accent(t(" You")), ...wrap(safeText(block.text), columns - 4).map(line => ' ' + line), ''];
|
|
409
498
|
else if (block.role === 'assistant') lines = [tone.bold(' Comal'), ...this.markdown.render(block.text, columns - 4).map(line => ' ' + line), ''];
|
|
410
499
|
else if (block.role === 'notice') lines = wrap((block.error ? tone.bad : tone.muted)(safeText(` ${block.error ? '✗' : '·'} ${block.text}`)), columns);
|
|
411
500
|
else if (block.role === 'table') lines = [tone.accent(' ' + block.text), ...this.tables.render(block.data, columns - 4).map(line => ' ' + line), ''];
|
|
412
501
|
else if (block.role === 'subagent') {
|
|
413
502
|
const icon = ['running','queued','waiting'].includes(block.runner_state || block.state) ? this.spinner() : block.work_state==='checks_verified' ? '✓' : block.state==='paused' ? '◇' : block.state==='completed' ? '·' : '✗';
|
|
414
|
-
lines = [' ' + tone.accent(clip(safeText(
|
|
503
|
+
lines = [' ' + tone.accent(clip(safeText(t("{v0} Subagent {v1} · {v2} · {v3}", {v0: icon, v1: block.id.slice(0, 8), v2: block.subagent_type, v3: block.description})), columns - 4)), ' ' + tone.muted(clip(safeText(`${block.runner_state || block.state} · ${block.turn_state || block.status || block.state} · ${block.work_state || 'unjudged'} · calls ${block.turns || 0}/${block.max_turns || 6}${block.lastTool ? ' · ' + block.lastTool : ''}`), columns - 6))];
|
|
415
504
|
if (this.expandedTools || ['failed', 'cancelled', 'interrupted','paused'].includes(block.state)) lines.push(...wrap(safeText(block.error || block.result || block.text || ''), columns - 6).slice(0, 15).map(line => ' ' + line));
|
|
416
505
|
}
|
|
417
506
|
else {
|
|
@@ -432,62 +521,63 @@ export class TerminalScreen {
|
|
|
432
521
|
if (this.overlay?.kind === 'palette' && !this.permission) {
|
|
433
522
|
if (this.overlay.detail) {
|
|
434
523
|
const tool = this.paletteTool();
|
|
435
|
-
if (!tool) return [tone.warning(
|
|
436
|
-
const lines = [tone.accent(' ' + safeText(tool.name)), ...wrap(safeText(tool.description), columns - 4).map(line => ' ' + line), '', tone.muted(
|
|
437
|
-
for (const [name, schema] of Object.entries(tool.input_schema?.properties || {})) lines.push(...wrap(safeText(`${tool.input_schema?.required?.includes(name) ? '* ' : ' '}${name} · ${schema.type ||
|
|
438
|
-
if (!Object.keys(tool.input_schema?.properties || {}).length) lines.push(tone.muted(
|
|
439
|
-
lines.push('', tone.muted(tool.audience === 'human' ?
|
|
524
|
+
if (!tool) return [tone.warning(t(" Operation removed from the catalog")), tone.muted(t(" Esc returns; nothing was executed."))];
|
|
525
|
+
const lines = [tone.accent(' ' + safeText(tool.name)), ...wrap(safeText(tool.description), columns - 4).map(line => ' ' + line), '', tone.muted(t(" Audience: {v0} · source: {v1}", {v0: safeText(tool.audience || 'any'), v1: safeText(tool.origin?.plugin || tool.origin?.kind || t("core/MCP"))})), tone.muted(t(" Consulting does not authorize. Execution preserves the house's judgment.")), ...wrap(safeText(t("Declared effects: ") + Object.entries(tool.effects || {}).map(([key,value]) => `${key}=${value}`).join(' · ')), columns - 4).map(line => ' ' + tone.muted(line)), '', tone.bold(t(" Arguments · * required"))];
|
|
526
|
+
for (const [name, schema] of Object.entries(tool.input_schema?.properties || {})) lines.push(...wrap(safeText(`${tool.input_schema?.required?.includes(name) ? '* ' : ' '}${name} · ${schema.type || t("value")}${schema.enum ? ' · ' + schema.enum.join(' | ') : ''}${schema.description ? ' · ' + schema.description : ''}`), columns - 6).map(line => ' ' + line));
|
|
527
|
+
if (!Object.keys(tool.input_schema?.properties || {}).length) lines.push(tone.muted(t(" No fields declared.")));
|
|
528
|
+
lines.push('', tone.muted(tool.audience === 'human' ? t(" Human control · Esc returns to catalog") : t(" Tab prepares a request · does not send · Esc returns")));
|
|
440
529
|
this.overlay.scroll = Math.min(this.overlay.scroll, Math.max(0, lines.length - height)); return lines.slice(this.overlay.scroll, this.overlay.scroll + height);
|
|
441
530
|
}
|
|
442
531
|
const items = this.paletteItems(); this.overlay.index = Math.max(0, Math.min(this.overlay.index, items.length - 1));
|
|
443
532
|
const visible = Math.max(1, height - 5), start = Math.max(0, this.overlay.index - visible + 1);
|
|
444
|
-
return [tone.accent(
|
|
533
|
+
return [tone.accent(t(" Discover · {v0} · {v1}", {v0: this.overlay.tab === 'tool' ? t("TOOLS") : t("COMMANDS"), v1: items.length})), tone.muted(t(" Tab changes section · ↑/↓ chooses · Enter prepares/inspects · Esc returns")), t(" Search: {v0}", {v0: safeText(this.overlay.filter) || tone.muted(t("type to filter"))}), '', ...items.slice(start, start + visible).map((item, i) => {
|
|
445
534
|
const selected = start + i === this.overlay.index, labelWidth = Math.max(10, Math.floor(columns * .36));
|
|
446
535
|
const label = ` ${selected ? '❯' : ' '} ${safeText(item.label)}`;
|
|
447
536
|
const line = columns >= 55 ? pad(clip(label, labelWidth), labelWidth) + ' ' + safeText(item.description) : label;
|
|
448
537
|
return (selected ? tone.accent : String)(clip(line, columns));
|
|
449
|
-
}), ...(!items.length ? [tone.muted(
|
|
538
|
+
}), ...(!items.length ? [tone.muted(t(" No matches. Change section with Tab."))] : [])];
|
|
450
539
|
}
|
|
451
540
|
if (this.overlay?.kind === 'board' && !this.permission) {
|
|
452
|
-
const board = this.work, lines = [tone.accent(
|
|
453
|
-
const labels = { pending:
|
|
454
|
-
for (const [status, label] of Object.entries(labels)) { const cards = board?.cards.filter(card => card.status === status) || []; if (!cards.length) continue; lines.push(tone.accent(` ${label} (${cards.length})`)); for (const card of cards) { lines.push(...wrap(safeText(`${card.id} · ${card.title}`), columns - 6).map(line => ' ' + line)); if (card.held_by?.length || card.reason) lines.push(...wrap(safeText(card.reason ||
|
|
455
|
-
if (board?.next_action) lines.push(...wrap(safeText(
|
|
541
|
+
const board = this.work, lines = [tone.accent(t(" Work · plan v{v0}", {v0: board?.plan?.version || 0})), ...wrap(safeText(board?.plan?.objective || t("No plan. Ask for a task with steps and checks.")), columns - 4).map(line => ' ' + line), tone.muted(t(" /work resume continues · wheel/PgUp/PgDn scrolls · Esc returns")), ''];
|
|
542
|
+
const labels = { pending: t("PENDING"), in_progress: t("IN PROGRESS"), blocked: t("BLOCKED"), done: t("DONE · checks recorded"), needs_verification: t("TO VERIFY") };
|
|
543
|
+
for (const [status, label] of Object.entries(labels)) { const cards = board?.cards.filter(card => card.status === status) || []; if (!cards.length) continue; lines.push(tone.accent(` ${label} (${cards.length})`)); for (const card of cards) { lines.push(...wrap(safeText(`${card.id} · ${card.title}`), columns - 6).map(line => ' ' + line)); if (card.held_by?.length || card.reason) lines.push(...wrap(safeText(card.reason || t("Waiting: {v0}", {v0: card.held_by.join(', ')})), columns - 8).map(line => ' ' + tone.muted(line))); } lines.push(''); }
|
|
544
|
+
if (board?.next_action) lines.push(...wrap(safeText(t("Next: ") + board.next_action), columns - 4).map(line => ' ' + line));
|
|
456
545
|
this.overlay.scroll = Math.min(this.overlay.scroll, Math.max(0, lines.length - height)); return lines.slice(this.overlay.scroll, this.overlay.scroll + height);
|
|
457
546
|
}
|
|
458
547
|
if (this.permission) {
|
|
459
548
|
const permission = this.permission, details = wrap(safeText(JSON.stringify(permission.input, null, 2)), columns - 6);
|
|
460
549
|
permission.scroll = Math.min(permission.scroll, Math.max(0, details.length - height + 5));
|
|
461
|
-
return [tone.warning(
|
|
550
|
+
return [tone.warning(t(" Allow {v0}", {v0: permission.name})), tone.muted(t(" Detail {v0}/{v1} · PgUp/PgDn to review", {v0: permission.scroll + 1, v1: details.length})), '', ...details.slice(permission.scroll, permission.scroll + height - 5).map(line => ' ' + line), '', t(" {v0} {v1} · Enter confirms · y/n · Esc denies", {v0: permission.allowed ? tone.good(t("❯ Yes")) : t(" Yes"), v1: !permission.allowed ? tone.warning('❯ No') : ' No'})];
|
|
462
551
|
}
|
|
463
552
|
if (this.overlay.kind === 'help') {
|
|
464
|
-
const lines = [tone.muted(
|
|
553
|
+
const lines = [tone.muted(t(" Help · composer active · type below · PgUp/PgDn scrolls · Esc/F1 closes")), '', ...this.markdown.render(t(HELP), columns - 4).map(line => ' ' + line)];
|
|
465
554
|
this.overlay.scroll = Math.min(this.overlay.scroll, Math.max(0, lines.length - height));
|
|
466
555
|
return lines.slice(this.overlay.scroll, this.overlay.scroll + height);
|
|
467
556
|
}
|
|
468
557
|
if (this.overlay.kind === 'sessions') {
|
|
469
558
|
const items = this.filteredSessions(); const visible = Math.max(1, height - 5), start = Math.max(0, this.overlay.index - visible + 1);
|
|
470
|
-
return [tone.accent(
|
|
471
|
-
const selected = start + i === this.overlay.index, title = `${item.invalid ? '✗ ' : ''}${item.name || item.prompt ||
|
|
559
|
+
return [tone.accent(t(" Sessions · ") + this.overlay.items.length), t(" Search: {v0}", {v0: this.overlay.filter || tone.muted(t("type to filter"))}), this.overlay.error ? tone.bad(' ' + this.overlay.error) : tone.muted(t(" ↑/↓ choose · Enter resumes with history · Esc returns")), '', ...items.slice(start, start + visible).map((item, i) => {
|
|
560
|
+
const selected = start + i === this.overlay.index, title = `${item.invalid ? '✗ ' : ''}${item.name || item.prompt || t("(empty)")}`;
|
|
472
561
|
const left = pad(safeText(`${selected ? '❯' : ' '} ${title}`), Math.max(8, columns - 40));
|
|
473
562
|
const right = safeText(`${item.id.slice(0, 8)} ${item.messages} msgs ${item.model}`);
|
|
474
563
|
return ' ' + (selected ? tone.accent : String)(clip(left + right, columns - 4));
|
|
475
|
-
}), ...(!items.length ? [tone.muted(
|
|
564
|
+
}), ...(!items.length ? [tone.muted(t(" No matches."))] : [])];
|
|
476
565
|
}
|
|
477
|
-
const status = this.queue.paused ?
|
|
478
|
-
const lines = [tone.accent(
|
|
479
|
-
if (this.queue.running) lines.push(tone.muted(
|
|
566
|
+
const status = this.queue.paused ? t("paused") : t("active");
|
|
567
|
+
const lines = [tone.accent(t(" Message queue · {v0}", {v0: status})), tone.muted(t(" e edit · d remove · Ctrl+↑/↓ reorder · Space pause/resume · Esc return")), ''];
|
|
568
|
+
if (this.queue.running) lines.push(tone.muted(t(" Running: ") + clip(safeText(this.queue.running.text), columns - 20)), '');
|
|
480
569
|
this.queue.items.forEach((item, index) => { lines.push((index === this.overlay.index ? tone.accent : String)(` ${index === this.overlay.index ? '❯' : ' '} ${index + 1}. ${clip(safeText(item.text).replace(/\n/g, ' ↵ '), columns - 9)}`)); });
|
|
481
|
-
if (!this.queue.items.length) lines.push(tone.muted(
|
|
482
|
-
if (this.queue.interrupted?.length) lines.push('', tone.warning(
|
|
570
|
+
if (!this.queue.items.length) lines.push(tone.muted(t(" Empty queue. Send a message while the agent works.")));
|
|
571
|
+
if (this.queue.interrupted?.length) lines.push('', tone.warning(t(" {v0} interrupted message(s); not resent. R requeues the first.", {v0: this.queue.interrupted.length})));
|
|
483
572
|
return lines.slice(0, height);
|
|
484
573
|
}
|
|
485
574
|
render(selectionEntry = false) {
|
|
486
575
|
if (this.closed || this.suspended || (this.selecting && !selectionEntry)) return;
|
|
487
576
|
const columns = Math.max(1, (this.output.columns || 80) - 1), rows = this.output.rows || 24;
|
|
488
|
-
if (columns < 23 || rows < 10) { this.draw([clip(
|
|
577
|
+
if (columns < 23 || rows < 10) { this.draw([clip(t("Enlarge the terminal to 24×10"), columns)], null, columns, rows); return; }
|
|
578
|
+
if (this.setup) { this.renderSetup(columns, rows); return; }
|
|
489
579
|
const composer = this.composer.layout(columns - 6), composerHeight = Math.min(6, rows - 8, Math.max(2, composer.lines.length));
|
|
490
|
-
const location = ` ${clip(safeText(this.metadata.cwd || ''), Math.max(4, columns - 27))} · ${safeText(this.metadata.sessionId?.slice(0, 8) ||
|
|
580
|
+
const location = ` ${clip(safeText(this.metadata.cwd || ''), Math.max(4, columns - 27))} · ${safeText(this.metadata.sessionId?.slice(0, 8) || t("connecting"))}`;
|
|
491
581
|
const report = this.metadata.modelCapabilities;
|
|
492
582
|
const budget = this.metadata.contextBudget;
|
|
493
583
|
const capacity = budget?.contextWindow;
|
|
@@ -495,13 +585,13 @@ export class TerminalScreen {
|
|
|
495
585
|
const capacityLabel = `Ctx:${amount}${amount === '?' ? '' : budget.kind === 'fallback' ? '?' : budget.kind === 'configured' || budget.source === 'comal_limit' ? '~' : ''}`;
|
|
496
586
|
const indicators = [`E:${safeText(this.metadata.effort || 'auto')}`, capacityLabel];
|
|
497
587
|
if (this.metadata.workflow) indicators.push(safeText(this.metadata.workflow));
|
|
498
|
-
for (const [key, label] of [['thinking', 'think'], ['vision',
|
|
588
|
+
for (const [key, label] of [['thinking', 'think'], ['vision', t("vision")], ['video', 'video'], ['web_search', 'web']]) {
|
|
499
589
|
if (report?.capabilities?.[key] === true) indicators.push(label + (report.provenance?.[key]?.kind === 'configured' ? '~' : ''));
|
|
500
590
|
}
|
|
501
591
|
if (report && Object.values(report.capabilities).some(value => value === null)) indicators.push('caps?');
|
|
502
592
|
const modelLabel = clip(' ' + indicators.join(' · '), Math.max(8, Math.floor(columns / 2)));
|
|
503
593
|
const locationLine = columns >= 50 ? pad(clip(location, columns - width(modelLabel) - 1), columns - width(modelLabel)) + modelLabel : location;
|
|
504
|
-
const header = [tone.bold(' COMAL') + (this.metadata.name ? tone.accent(safeText(` @${this.metadata.name}`)) : '') + tone.muted(safeText(` ${this.metadata.model ||
|
|
594
|
+
const header = [tone.bold(' COMAL') + (this.metadata.name ? tone.accent(safeText(` @${this.metadata.name}`)) : '') + tone.muted(safeText(` ${this.metadata.model || t("local model")} · ${this.metadata.authProvider || this.metadata.provider || 'openai'} · `)) + tone.accent(this.mode), tone.muted(locationLine), tone.muted('─'.repeat(columns))];
|
|
505
595
|
const completions = this.completion();
|
|
506
596
|
const availableBody = Math.max(1, rows - header.length - composerHeight - 4);
|
|
507
597
|
const completionHeight = completions.items.length ? Math.min(5, Math.max(0, availableBody - 1)) : 0;
|
|
@@ -518,19 +608,19 @@ export class TerminalScreen {
|
|
|
518
608
|
}
|
|
519
609
|
while (body.length < bodyHeight) body.push(''); body = body.slice(0, bodyHeight);
|
|
520
610
|
if (sideWidth) {
|
|
521
|
-
const side = [tone.accent(
|
|
522
|
-
if (this.queue.running) side.push(tone.muted(
|
|
611
|
+
const side = [tone.accent(t(" Queue · {v0}{v1}", {v0: this.queue.items.length, v1: this.queue.paused ? t(" paused") : ''})), tone.muted(t(" F4 manage")), ''];
|
|
612
|
+
if (this.queue.running) side.push(tone.muted(t(" Running")), ...wrap(safeText(this.queue.running.text), sideWidth - 2).slice(0, 2).map(line => ' ' + line), '');
|
|
523
613
|
this.queue.items.slice(0, 6).forEach((item, i) => side.push(tone.accent(` ${i + 1}. `) + clip(safeText(item.text).replace(/\n/g, ' ↵ '), sideWidth - 5), ''));
|
|
524
614
|
body = body.map((line, i) => pad(line, bodyWidth) + tone.muted(' │') + clip(side[i] || '', sideWidth));
|
|
525
615
|
}
|
|
526
616
|
const elapsed = this.startedAt ? ((Date.now() - this.startedAt) / 1000).toFixed(1) : '0.0';
|
|
527
617
|
const active = this.busy || !this.ready;
|
|
528
|
-
const contextLabel = this.context ? ` · ctx ${this.context.estimated===false?'':'~'}${this.context.percent}%${this.context.accounting_method==='measured_prefix_estimated_tail'?
|
|
529
|
-
const status = (active ? `${brandingStatus({frame:this.brandFrame(),active:true,env:this.env,stream:this.output})} ${this.spinner()} ${this.phase} · ${elapsed}s` :
|
|
530
|
-
const measure = this.goal?.status === 'active' ? `goal calls ${this.goal.calls}/${this.goal.limits.maxCalls} ${progressBar(this.goal.calls, this.goal.limits.maxCalls, 12)}` : this.busy ? `calls ${this.turn}/${this.maxTurns} ${progressBar(this.turn, this.maxTurns, 12)}` :
|
|
531
|
-
const workLabel = this.work?.plan ?
|
|
532
|
-
const bar = tone.accent(' ' + status + workLabel) + ' ' + tone.muted(measure) + (this.scroll ? tone.warning(
|
|
533
|
-
let composerHeading = this.editingQueue ? tone.warning(
|
|
618
|
+
const contextLabel = this.context ? ` · ctx ${this.context.estimated===false?'':'~'}${this.context.percent}%${this.context.accounting_method==='measured_prefix_estimated_tail'?t(" · measured baseline"):''}${Number.isSafeInteger(this.cacheReadTokens)?t(" · cache ")+formatNumber(this.cacheReadTokens):''}` : '';
|
|
619
|
+
const status = (active ? `${brandingStatus({frame:this.brandFrame(),active:true,env:this.env,stream:this.output})} ${this.spinner()} ${this.phase} · ${elapsed}s` : t("● Ready · {v0} tokens", {v0: formatNumber(this.usage)})) + contextLabel;
|
|
620
|
+
const measure = this.goal?.status === 'active' ? `goal calls ${this.goal.calls}/${this.goal.limits.maxCalls} ${progressBar(this.goal.calls, this.goal.limits.maxCalls, 12)}` : this.busy ? `calls ${this.turn}/${this.maxTurns} ${progressBar(this.turn, this.maxTurns, 12)}` : t("{v0} in queue{v1}", {v0: this.queue.items.length, v1: this.queue.paused ? t(" · paused") : ''});
|
|
621
|
+
const workLabel = this.work?.plan ? t(" · work {v0}/{v1}{v2}", {v0: this.work.counts.done, v1: this.work.cards.length, v2: this.work.current ? ' · ' + this.work.current.id : ''}) : '';
|
|
622
|
+
const bar = tone.accent(' ' + status + workLabel) + ' ' + tone.muted(measure) + (this.scroll ? tone.warning(t(" · history ↑")) : '');
|
|
623
|
+
let composerHeading = this.editingQueue ? tone.warning(t(" Editing queued message · Enter saves · Esc cancels")) : tone.muted(t(" Composer · {v0} · Alt+Enter multiline · Ctrl+G editor", {v0: this.busy ? t("Enter queues") : t("Enter sends")}));
|
|
534
624
|
const sessionName = safeText(this.metadata.name || '').replace(/[\r\n]+/g, ' ').trim();
|
|
535
625
|
if (sessionName) {
|
|
536
626
|
const labelWidth = Math.min(Math.floor(columns / 2), columns - 12);
|
|
@@ -541,10 +631,10 @@ export class TerminalScreen {
|
|
|
541
631
|
const footer = [clip(bar, columns), tone.muted('─'.repeat(columns)), composerHeading];
|
|
542
632
|
const offset = Math.max(0, composer.cursorRow - composerHeight + 1);
|
|
543
633
|
const inputLines = Array.from({ length: composerHeight }, (_, i) => tone.muted(i === 0 ? ' ❯ ' : ' │ ') + (composer.lines[offset + i] || ''));
|
|
544
|
-
const hints = this.selecting ? tone.warning(
|
|
634
|
+
const hints = this.selecting ? tone.warning(t(" SELECTION · Drag and copy from your terminal · F5/Esc returns")) : this.attachmentStatus ? (['unsupported','invalid'].includes(this.attachmentStatus.state) ? tone.warning : tone.muted)(' ' + safeText(this.attachmentStatus.message)) : tone.muted(' ' + (completions.items.length ? t("Tab or Enter completes · ↑/↓ chooses · Esc closes · does not send") : (this.overlay?.kind === 'help' ? t("Help open · composer active · PgUp/PgDn scrolls · Esc/F1 closes") : this.preparedDraft ? t("Action prepared · Esc recovers your draft · Enter sends and restores it") : contextualTip(this.composer.text, this.discoveryContext()))));
|
|
545
635
|
const suggestionLines = [];
|
|
546
636
|
if (completionHeight) {
|
|
547
|
-
suggestionLines.push(tone.muted(
|
|
637
|
+
suggestionLines.push(tone.muted(t(" Suggestions · {v0} · Tab completes", {v0: completions.items.length})));
|
|
548
638
|
const visible = completionHeight - 1, start = Math.max(0, this.completionIndex - visible + 1);
|
|
549
639
|
for (const [i,item] of completions.items.slice(start, start + visible).entries()) {
|
|
550
640
|
const selected = start + i === this.completionIndex;
|
|
@@ -578,6 +668,7 @@ export class TerminalScreen {
|
|
|
578
668
|
resume() { this.input.setRawMode?.(true); this.input.resume(); this.suspended = false; this.previous = []; this.output.write('\x1b[?1049h\x1b[?2004h' + MOUSE_ON); this.render(); }
|
|
579
669
|
close() {
|
|
580
670
|
if (this.closed) return; this.closed = true;
|
|
671
|
+
this.clearSetup();
|
|
581
672
|
this.permission?.finish(false); clearInterval(this.timer); clearTimeout(this.renderTimer); this.decoder?.close();
|
|
582
673
|
if (this.onData) this.input.off('data', this.onData); if (this.onResize) this.output.off('resize', this.onResize);
|
|
583
674
|
if (this.started) { this.input.setRawMode?.(this.originalRaw); this.input.pause(); if (!this.suspended) this.output.write('\x1b[0m' + MOUSE_OFF + '\x1b[?2004l\x1b[?25h\x1b[?1049l'); }
|