@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/discovery.mjs
CHANGED
|
@@ -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
|
-
['
|
|
7
|
-
['
|
|
8
|
-
['
|
|
9
|
-
['
|
|
10
|
-
['
|
|
11
|
-
['
|
|
12
|
-
['
|
|
13
|
-
['
|
|
14
|
-
['
|
|
15
|
-
['
|
|
16
|
-
['
|
|
17
|
-
['
|
|
18
|
-
['
|
|
19
|
-
['
|
|
20
|
-
['
|
|
21
|
-
['
|
|
22
|
-
['
|
|
23
|
-
['
|
|
24
|
-
['
|
|
25
|
-
['
|
|
26
|
-
['
|
|
27
|
-
['
|
|
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:
|
|
35
|
-
Write:
|
|
36
|
-
Edit:
|
|
37
|
-
Glob:
|
|
38
|
-
Grep:
|
|
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:
|
|
41
|
-
FileInfo:
|
|
42
|
-
ReadTable:
|
|
43
|
-
WebSearch:
|
|
44
|
-
WebFetch:
|
|
45
|
-
AnalyzeMedia:
|
|
46
|
-
ProviderSearch:
|
|
47
|
-
ProviderCapabilities:
|
|
48
|
-
ListAgents:
|
|
49
|
-
SendMessage:
|
|
50
|
-
Agent:
|
|
51
|
-
AgentBatch:
|
|
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:
|
|
54
|
-
AgentStop:
|
|
55
|
-
AgentResume:
|
|
56
|
-
AgentContinue:
|
|
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:
|
|
59
|
-
McpStatus:
|
|
60
|
-
McpConfigure:
|
|
61
|
-
McpReload:
|
|
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:
|
|
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:
|
|
68
|
-
WorkSnapshot:
|
|
69
|
-
AuditStatus:
|
|
70
|
-
EvidenceRead:
|
|
71
|
-
HouseSnapshot:
|
|
72
|
-
HouseContext:
|
|
73
|
-
OperationCatalog:
|
|
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:
|
|
77
|
-
operations:
|
|
78
|
-
sessions:
|
|
79
|
-
compact:
|
|
80
|
-
board:
|
|
81
|
-
effort: 'esfuerzo razonamiento pensar thinking', capabilities:
|
|
82
|
-
permissions:
|
|
83
|
-
goal:
|
|
84
|
-
mcp:
|
|
85
|
-
agent:
|
|
86
|
-
search:
|
|
87
|
-
send:
|
|
88
|
-
login:
|
|
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 === '
|
|
119
|
-
if (command === '
|
|
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'],
|
|
124
|
-
if (command === 'send') return entries(context.peers, 'id').map(peer => ({ ...peer, description:
|
|
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'
|
|
127
|
-
if (command === 'goal') return named(['status', 'pause', 'resume', 'clear'],
|
|
128
|
-
if (command === 'queue') return named(['pause', 'resume', 'clear', 'edit'],
|
|
129
|
-
if (command === 'mcp') return named(['status', 'list', 'config', 'reload', 'reconnect', 'add', 'remove'],
|
|
130
|
-
if (command === 'plugins') return named(['status', 'list', 'enable', 'disable', 'reload', 'run'],
|
|
131
|
-
if (command === 'agent') return named(['explore', 'general','background'],
|
|
132
|
-
if (command === 'inbox') return named(['status', 'accept', 'hold', 'refuse', 'release'],
|
|
133
|
-
if (command === 'login' || command === 'logout') return [...(command === 'login' ? named(['status'],
|
|
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'],
|
|
140
|
-
if (command === 'queue' && first === 'edit') return named((context.queue?.items || []).map((_, index) => String(index + 1)),
|
|
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'],
|
|
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 ||
|
|
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
|
|
157
|
-
if (command === 'effort' && !context.effortLevels && !context.capabilities?.effort?.levels) return
|
|
158
|
-
if (command === 'plugins') return
|
|
159
|
-
if (command === 'login') return
|
|
160
|
-
if (skills(context).some(skill => skill.value === command)) return
|
|
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}` :
|
|
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(
|
|
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
|
|
209
|
-
if (context.editingQueue) return
|
|
210
|
-
if (context.queue?.paused) return
|
|
211
|
-
if (context.queue?.interrupted?.length) return
|
|
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
|
|
214
|
-
if (String(text).includes('\n')) return
|
|
215
|
-
if (context.mode === 'plan') return
|
|
216
|
-
return
|
|
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
|
}
|
package/src/tui/markdown.mjs
CHANGED
|
@@ -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
|
|
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(
|
|
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(
|
|
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(
|
|
42
|
-
if (headers.length > 24 || rows.length > 10000) throw new Error(
|
|
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(
|
|
49
|
-
if (rows.length > 200) output.push(tone.muted(
|
|
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(
|
|
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(
|
|
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 {
|
|
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(
|
|
13
|
-
if (this.items.length >= 32 && !excludeId) throw new Error(
|
|
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(
|
|
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);
|
|
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(
|
|
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 {
|