@danieltmn/tamnora 0.25.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +13 -25
- package/SECURITY.md +3 -4
- package/harness/packages/cli/src/cli.js +0 -3
- package/harness/packages/cli/src/commands/bridge.js +0 -1
- package/harness/packages/cli/src/commands/repl.js +20 -4
- package/harness/packages/cli/src/commands/run.js +6 -2
- package/harness/packages/cli/src/commands/server.js +1 -1
- package/harness/packages/cli/src/commands/tui.js +6 -2
- package/harness/packages/cli/src/lib/runtime.js +2 -0
- package/harness/packages/cli/src/lib/util.js +2 -2
- package/harness/packages/cli/src/tui/app.js +24 -4
- package/harness/packages/cli/src/tui/layout.js +1 -1
- package/harness/packages/cli/test/tunnel.test.js +5 -32
- package/harness/packages/core/src/agent/runAgent.js +5 -2
- package/harness/packages/core/src/agents/index.js +113 -0
- package/harness/packages/core/src/config/app.js +0 -4
- package/harness/packages/core/src/config/home.js +1 -20
- package/harness/packages/core/src/config/index.js +6 -8
- package/harness/packages/core/src/extensions/load.js +22 -0
- package/harness/packages/core/src/frontmatter.js +32 -0
- package/harness/packages/core/src/hooks/index.js +2 -16
- package/harness/packages/core/src/index.js +12 -14
- package/harness/packages/core/src/plugins/index.js +2 -16
- package/harness/packages/core/src/procs/project.js +68 -0
- package/harness/packages/core/src/procs/static.js +17 -0
- package/harness/packages/core/src/providers/index.js +3 -1
- package/harness/packages/core/src/providers/ollama.js +4 -1
- package/harness/packages/core/src/providers/openaiCompatible.js +6 -1
- package/harness/packages/core/src/providers/openrouter.js +5 -1
- package/harness/packages/core/src/providers/presets.js +4 -3
- package/harness/packages/core/src/providers/reasoning.js +58 -0
- package/harness/packages/core/src/rules/index.js +8 -27
- package/harness/packages/core/src/skills/index.js +8 -19
- package/harness/packages/core/src/storage/sqlite.js +595 -690
- package/harness/packages/core/src/tunnel.js +9 -147
- package/harness/packages/core/test/agents.test.js +111 -0
- package/harness/packages/core/test/home.test.js +2 -35
- package/harness/packages/core/test/procs.test.js +3 -64
- package/harness/packages/core/test/reasoning.test.js +43 -0
- package/harness/packages/core/test/storage.test.js +1 -12
- package/harness/packages/server/public/app.js +39 -19
- package/harness/packages/server/public/appearance.js +2 -56
- package/harness/packages/server/public/boot.js +2 -2
- package/harness/packages/server/public/cards.js +2 -21
- package/harness/packages/server/public/chat.js +1 -16
- package/harness/packages/server/public/chatFormat.js +0 -11
- package/harness/packages/server/public/chrome.js +95 -5
- package/harness/packages/server/public/commands.js +1 -2
- package/harness/packages/server/public/core.js +1 -1
- package/harness/packages/server/public/explorer.js +7 -10
- package/harness/packages/server/public/icons (1).zip +0 -0
- package/harness/packages/server/public/index.html +1228 -1237
- package/harness/packages/server/public/landing/boot.js +2 -3
- package/harness/packages/server/public/landing/index.html +3 -367
- package/harness/packages/server/public/sessions.js +1 -0
- package/harness/packages/server/public/settings.js +1255 -1155
- package/harness/packages/server/public/settingsNav.js +1 -1
- package/harness/packages/server/public/styles.css +2341 -2867
- package/harness/packages/server/public/system.js +356 -495
- package/harness/packages/server/public/voice.js +6 -22
- package/harness/packages/server/src/auth.js +4 -15
- package/harness/packages/server/src/cli.js +1 -1
- package/harness/packages/server/src/lib/agents.js +60 -0
- package/harness/packages/server/src/lib/appearance.js +3 -22
- package/harness/packages/server/src/lib/channels/telegram/commands.js +1 -1
- package/harness/packages/server/src/lib/channels/telegram/run.js +1 -0
- package/harness/packages/server/src/lib/definitions.js +40 -0
- package/harness/packages/server/src/lib/prompt.js +11 -3
- package/harness/packages/server/src/lib/scheduler.js +4 -0
- package/harness/packages/server/src/lib/sessionView.js +1 -0
- package/harness/packages/server/src/lib/skills.js +27 -21
- package/harness/packages/server/src/lib/tasks.js +71 -23
- package/harness/packages/server/src/lib/ttsSettings.js +2 -2
- package/harness/packages/server/src/lib/workspace.js +9 -11
- package/harness/packages/server/src/routes/agents.js +88 -0
- package/harness/packages/server/src/routes/chat.js +9 -44
- package/harness/packages/server/src/routes/preview.js +5 -22
- package/harness/packages/server/src/routes/procs.js +2 -4
- package/harness/packages/server/src/routes/providers.js +7 -0
- package/harness/packages/server/src/routes/sessions.js +3 -1
- package/harness/packages/server/src/routes/tts.js +1 -1
- package/harness/packages/server/src/server.js +8 -5
- package/harness/packages/server/test/agents.test.js +194 -0
- package/harness/packages/server/test/appearance-route.test.js +3 -3
- package/harness/packages/server/test/appearance.test.js +12 -38
- package/harness/packages/server/test/chatFormat.test.js +0 -8
- package/harness/packages/server/test/consoleTabs.test.js +2 -2
- package/harness/packages/server/test/ops.test.js +8 -56
- package/harness/packages/server/test/preview.test.js +2 -42
- package/harness/packages/server/test/security.test.js +194 -370
- package/harness/packages/server/test/settingsNav.test.js +2 -2
- package/harness/packages/server/test/tts.test.js +4 -4
- package/harness/tamnora.example.json +2 -1
- package/package.json +2 -2
- package/harness/packages/cli/src/commands/eval.js +0 -186
- package/harness/packages/core/src/eval/dataset.js +0 -47
- package/harness/packages/core/src/eval/judge.js +0 -39
- package/harness/packages/core/src/eval/report.js +0 -48
- package/harness/packages/core/src/eval/runTask.js +0 -62
- package/harness/packages/core/src/eval/runner.js +0 -153
- package/harness/packages/core/src/eval/scorers.js +0 -217
- package/harness/packages/core/src/eval/task.js +0 -59
- package/harness/packages/core/src/procs/detect.js +0 -149
- package/harness/packages/core/test/eval.test.js +0 -144
- package/harness/packages/server/src/lib/advisor.js +0 -198
- package/harness/packages/server/src/routes/advisor.js +0 -79
- package/harness/packages/server/src/routes/eval.js +0 -100
- package/harness/packages/server/test/advisor.test.js +0 -249
- package/harness/scripts/example-dataset.jsonl +0 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@ Todos los cambios relevantes de este proyecto se documentan en este archivo.
|
|
|
5
5
|
El formato sigue [Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/)
|
|
6
6
|
y el proyecto usa [Versionado Semantico](https://semver.org/lang/es/).
|
|
7
7
|
|
|
8
|
+
## [0.26.0] - 2026-11-04
|
|
9
|
+
|
|
10
|
+
### Cambios
|
|
11
|
+
|
|
12
|
+
- refactor: retirar el eval del proyecto
|
|
13
|
+
- refactor: retirar el asesor y la evaluacion desde el server
|
|
14
|
+
- refactor(temas): dejar 4 temas y 2 tipografias
|
|
15
|
+
- refactor: quitar la autodeteccion de procesos de desarrollo
|
|
16
|
+
- refactor: unificar registros markdown y carga de extensiones
|
|
17
|
+
- refactor: quitar alias y compatibilidad que ya no compra nada
|
|
18
|
+
- refactor: retirar el tunel publico (tunnelmole)
|
|
19
|
+
|
|
20
|
+
## [0.25.1] - 2026-11-04
|
|
21
|
+
|
|
22
|
+
### Cambios
|
|
23
|
+
|
|
24
|
+
- feat: esfuerzo de razonamiento y subagentes en archivos
|
|
25
|
+
|
|
8
26
|
## [0.25.0] - 2026-11-01
|
|
9
27
|
|
|
10
28
|
### Cambios
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Tamnora Harness
|
|
2
2
|
|
|
3
3
|
Harness local para IAs: un agente **CLI-first** (estilo opencode) en Node puro,
|
|
4
|
-
con TUI, consola web, tools locales
|
|
4
|
+
con TUI, consola web, tools locales y MCP.
|
|
5
5
|
|
|
6
6
|
- **Sin dependencias**: solo modulos de Node (`node:sqlite`, `node:crypto`, ...).
|
|
7
7
|
- **Local**: corre en tu maquina y guarda el estado en tu casa local.
|
|
@@ -14,10 +14,6 @@ con TUI, consola web, tools locales, MCP y evaluacion de modelos.
|
|
|
14
14
|
bloques de codigo ni tablas) y mandar audio bajo pedido (`speak`,
|
|
15
15
|
`/audioresumen`). El **modo Voz** habilita **manos libres**: hablás y responde en
|
|
16
16
|
audio, como una conversacion (y solo escribe notas `.md`/`.json`).
|
|
17
|
-
- **Asesor**: una capa de comentario sobre el run que interpreta lo que hizo el
|
|
18
|
-
agente y, en **un solo mensaje** estilo Jarvis, resume lo hecho y —si aporta—
|
|
19
|
-
avisa de un problema en el codigo, explica un proceso o da un consejo (tarjeta en
|
|
20
|
-
el chat y voz opcional). No lee la respuesta.
|
|
21
17
|
- **Actualizar desde la consola**: la consola avisa cuando hay una version nueva
|
|
22
18
|
y la instala con un click (el server se reinicia solo).
|
|
23
19
|
- **Memoria y reglas**: memoria durable por proyecto (`memory`) y reglas por path
|
|
@@ -48,7 +44,6 @@ Requiere **Node.js >= 22.13** (por `node:sqlite`).
|
|
|
48
44
|
tamnora init # crea la configuracion (proveedor, modelo)
|
|
49
45
|
tamnora # consola TUI full-screen (REPL si no hay TTY)
|
|
50
46
|
tamnora run "hola" # tarea headless
|
|
51
|
-
tamnora eval <dataset.jsonl> # evaluacion de modelos
|
|
52
47
|
tamnora mcp [--json] # servidores MCP: tools, recursos y prompts
|
|
53
48
|
tamnora serve # consola web
|
|
54
49
|
```
|
|
@@ -103,8 +98,7 @@ Tamnora (override con `--home` o `TAMNORA_HOME`):
|
|
|
103
98
|
### Consola web
|
|
104
99
|
|
|
105
100
|
`tamnora serve` sirve la consola en `http://127.0.0.1:4517`. Las pestanas
|
|
106
|
-
principales son `chat`, `proveedores` y `ajustes
|
|
107
|
-
menu "mas" (los tres puntitos). En el chat:
|
|
101
|
+
principales son `chat`, `proveedores` y `ajustes`. En el chat:
|
|
108
102
|
|
|
109
103
|
- **Paneles laterales** de sesiones y archivos, colapsables desde cada panel (con
|
|
110
104
|
un tirador en el borde para reabrirlos).
|
|
@@ -131,11 +125,9 @@ menu "mas" (los tres puntitos). En el chat:
|
|
|
131
125
|
- **Solo lo pedido (estricto)**: el toggle de la pestaña Agente (o `PUT
|
|
132
126
|
/api/settings` con `strictScope`) hace que el agente se limite a lo solicitado,
|
|
133
127
|
sin mejoras ni cambios extra; se combina con Plan o Build.
|
|
134
|
-
- **Audio**: un unico reproductor encola la narracion, el tool `speak
|
|
135
|
-
respuesta final
|
|
128
|
+
- **Audio**: un unico reproductor encola la narracion, el tool `speak` y la
|
|
129
|
+
respuesta final, y reproduce de a uno con pausa y
|
|
136
130
|
detener (Ajustes -> Voz).
|
|
137
|
-
- **Asesor**: en Ajustes -> Asesor se activa una capa que comenta el turno
|
|
138
|
-
(problemas de codigo / explicaciones / consejos) con proveedor y modelo activos.
|
|
139
131
|
- **Actualizar**: cuando npm tiene una version nueva aparece un banner para
|
|
140
132
|
actualizar con un click; el server se reinicia solo.
|
|
141
133
|
- **Comandos**: el input muestra un menu al escribir `/` (`/new`, `/init`, `/goal`,
|
|
@@ -191,7 +183,7 @@ Los tres se renderizan en vivo y se reconstruyen al recargar la consola.
|
|
|
191
183
|
|
|
192
184
|
- **Hooks** (`hooks[]` en `tamnora.json`, p. ej. `[".tamnora/hooks/*.mjs"]`):
|
|
193
185
|
observan el ciclo de vida (`run.start/end`, `tool.before/after`,
|
|
194
|
-
`session.compacting/compacted
|
|
186
|
+
`session.compacting/compacted`). Corren aislados y con los datos
|
|
195
187
|
redactados. Es **opt-in explícito**: la presencia de archivos en el proyecto no
|
|
196
188
|
ejecuta nada.
|
|
197
189
|
- **Plugins** (`plugins[]`, p. ej. `[".tamnora/plugins/*.mjs"]`): agregan **tools
|
|
@@ -200,15 +192,11 @@ Los tres se renderizan en vivo y se reconstruyen al recargar la consola.
|
|
|
200
192
|
|
|
201
193
|
### Preview
|
|
202
194
|
|
|
203
|
-
La consola puede levantar un dev server
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
En un **monorepo** donde cada parte arranca distinto, el proyecto declara sus
|
|
209
|
-
procesos en `.tamnora/procs.json` (relativo al proyecto seleccionado); salen
|
|
210
|
-
primero, se suman a los detectados y **solo** se pueden lanzar los que pasan por
|
|
211
|
-
la allowlist `processes.allow`:
|
|
195
|
+
La consola puede levantar un dev server declarado por el proyecto en
|
|
196
|
+
`.tamnora/procs.json` y abrir un tunel al puerto para verlo desde el celular.
|
|
197
|
+
No autodetecta nada: el proyecto declara **solo** los procesos que quiere ofrecer
|
|
198
|
+
(relativo al proyecto seleccionado) y solo se pueden lanzar los que pasan por la
|
|
199
|
+
allowlist `processes.allow`:
|
|
212
200
|
|
|
213
201
|
```jsonc
|
|
214
202
|
{ "processes": [
|
|
@@ -259,8 +247,8 @@ tamnora bridge --stop # tailscale serve reset
|
|
|
259
247
|
Sigue protegido por el login del server: configura un usuario antes de exponerlo
|
|
260
248
|
(ver [`SECURITY.md`](SECURITY.md)).
|
|
261
249
|
|
|
262
|
-
|
|
263
|
-
|
|
250
|
+
El acceso remoto es solo por Tailscale: no hay tuneles publicos ni exposicion a
|
|
251
|
+
Internet.
|
|
264
252
|
|
|
265
253
|
### Telegram (canales de mensajeria)
|
|
266
254
|
|
|
@@ -337,7 +325,7 @@ tambien esta disponible en modo `plan` (exploracion delegada).
|
|
|
337
325
|
## Documentacion
|
|
338
326
|
|
|
339
327
|
- [`CHANGELOG.md`](CHANGELOG.md) — historial de versiones.
|
|
340
|
-
- [`AGENTS.md`](AGENTS.md) — arquitectura del harness, tools, MCP
|
|
328
|
+
- [`AGENTS.md`](AGENTS.md) — arquitectura del harness, tools, MCP y
|
|
341
329
|
convenciones.
|
|
342
330
|
- [`harness/tamnora.example.json`](harness/tamnora.example.json) — plantilla de
|
|
343
331
|
configuracion.
|
package/SECURITY.md
CHANGED
|
@@ -20,10 +20,9 @@ asi que se lo debe tratar como codigo con acceso a tu usuario.
|
|
|
20
20
|
**usuario** con contrasena fuerte (defensa en profundidad). Como entra por
|
|
21
21
|
loopback, una vez autenticado se alcanza toda la consola y las tools
|
|
22
22
|
(explorador de archivos, shell, git): tratalo como acceso completo a tu PC.
|
|
23
|
-
- **
|
|
24
|
-
|
|
25
|
-
remoto
|
|
26
|
-
feature de *preview* de dev servers, que no expone la consola.
|
|
23
|
+
- **Sin tuneles publicos**: las versiones anteriores usaban tunnelmole y un relay
|
|
24
|
+
propio para exponer la consola desde Internet. Se retiraron por completo; el
|
|
25
|
+
acceso remoto es solo por Tailscale, que no expone nada a Internet.
|
|
27
26
|
- **Credenciales**: las API keys de proveedores se guardan en la base SQLite de
|
|
28
27
|
la casa local (fuera del repo) **en texto plano**; protege el archivo con los
|
|
29
28
|
permisos del sistema. Para no persistirlas, usa `"apiKey": "env:VAR"` y
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { runCommand } from './commands/run.js';
|
|
2
2
|
import { replCommand } from './commands/repl.js';
|
|
3
|
-
import { evalCommand } from './commands/eval.js';
|
|
4
3
|
import { initCommand } from './commands/init.js';
|
|
5
4
|
import { mcpCommand } from './commands/mcp.js';
|
|
6
5
|
import { serveCommand } from './commands/serve.js';
|
|
@@ -20,7 +19,6 @@ const COMMANDS = {
|
|
|
20
19
|
run: runCommand,
|
|
21
20
|
repl: replCommand,
|
|
22
21
|
tui: tuiCommand,
|
|
23
|
-
eval: evalCommand,
|
|
24
22
|
init: initCommand,
|
|
25
23
|
mcp: mcpCommand,
|
|
26
24
|
serve: serveCommand,
|
|
@@ -70,7 +68,6 @@ function printHelp() {
|
|
|
70
68
|
Uso:
|
|
71
69
|
tamnora Consola full-screen (REPL si no hay TTY)
|
|
72
70
|
tamnora run "<prompt>" Ejecutar una tarea headless
|
|
73
|
-
tamnora eval <dataset.jsonl> Correr un dataset de evaluación
|
|
74
71
|
tamnora mcp Listar servidores y herramientas MCP
|
|
75
72
|
tamnora server [--init|--start|--reload|--stop|--status|--logs] Server en segundo plano (abre el navegador)
|
|
76
73
|
tamnora user Usuario local (login del server)
|
|
@@ -73,7 +73,6 @@ async function cmdStart(values) {
|
|
|
73
73
|
}
|
|
74
74
|
if (!isTailscale(provider)) {
|
|
75
75
|
console.error(`Proveedor no soportado: "${provider}". El acceso remoto usa Tailscale.`);
|
|
76
|
-
console.error('Los tuneles publicos (tunnelmole/relay) fueron retirados.');
|
|
77
76
|
return 1;
|
|
78
77
|
}
|
|
79
78
|
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
createTools,
|
|
8
8
|
buildMcpRegistry,
|
|
9
9
|
scopeDirective,
|
|
10
|
+
normalizeEffort,
|
|
10
11
|
} from '../lib/core.js';
|
|
11
12
|
import { parse, databasePath, num } from '../lib/util.js';
|
|
12
13
|
import { buildSystemPrompt, runTurn, createPrinter, resolveCliProvider, mcpSettings, mcpIndexBlock } from '../lib/runtime.js';
|
|
@@ -21,6 +22,7 @@ const OPTIONS = {
|
|
|
21
22
|
'no-tools': { type: 'boolean' },
|
|
22
23
|
strict: { type: 'boolean' },
|
|
23
24
|
'no-strict': { type: 'boolean' },
|
|
25
|
+
effort: { type: 'string' },
|
|
24
26
|
yes: { type: 'boolean', short: 'y' },
|
|
25
27
|
};
|
|
26
28
|
|
|
@@ -33,8 +35,9 @@ export async function replCommand(argv) {
|
|
|
33
35
|
let systemExtra = '';
|
|
34
36
|
let mcpBlock = '';
|
|
35
37
|
const maxRounds = num(values['max-steps'], config.maxRounds);
|
|
36
|
-
const store = new Store(databasePath(
|
|
38
|
+
const store = new Store(databasePath());
|
|
37
39
|
let strict = values.strict ? true : values['no-strict'] ? false : store.getSetting('strictScope') === true;
|
|
40
|
+
let effort = normalizeEffort(values.effort ?? store.getSetting('activeEffort') ?? config.effort);
|
|
38
41
|
const buildPrompt = () =>
|
|
39
42
|
buildSystemPrompt(instructions, [systemExtra, mcpBlock, scopeDirective(strict)].filter(Boolean).join('\n\n'));
|
|
40
43
|
const resolved = resolveCliProvider(store, config, { provider: values.provider, model: values.model });
|
|
@@ -97,7 +100,7 @@ export async function replCommand(argv) {
|
|
|
97
100
|
mcpPinned,
|
|
98
101
|
});
|
|
99
102
|
|
|
100
|
-
process.stdout.write(color.dim(`Tamnora REPL · ${providerId}/${model}\n`));
|
|
103
|
+
process.stdout.write(color.dim(`Tamnora REPL · ${providerId}/${model}${effort ? ` · esfuerzo ${effort}` : ''}\n`));
|
|
101
104
|
process.stdout.write(color.dim(`workspace ${cwd}\n`));
|
|
102
105
|
process.stdout.write(color.dim(`session ${sessionId} · /help para comandos · Ctrl+C para salir\n\n`));
|
|
103
106
|
rl.prompt();
|
|
@@ -120,7 +123,7 @@ export async function replCommand(argv) {
|
|
|
120
123
|
} else if (cmd === 'help') {
|
|
121
124
|
process.stdout.write(
|
|
122
125
|
`${color.dim(
|
|
123
|
-
'/model <id> /provider <id> /system <texto> /
|
|
126
|
+
'/model <id> /provider <id> /system <texto> /estricto /esfuerzo <nivel> /clear /session /exit'
|
|
124
127
|
)}\n`
|
|
125
128
|
);
|
|
126
129
|
} else if (cmd === 'model') {
|
|
@@ -132,11 +135,23 @@ export async function replCommand(argv) {
|
|
|
132
135
|
} else if (cmd === 'system') {
|
|
133
136
|
if (arg) systemExtra = arg;
|
|
134
137
|
process.stdout.write(color.dim(`system prompt (${buildPrompt().length} chars)\n`));
|
|
135
|
-
} else if (cmd === '
|
|
138
|
+
} else if (cmd === 'estricto') {
|
|
136
139
|
if (arg) strict = /^(on|si|sí|1|true)$/i.test(arg);
|
|
137
140
|
else strict = !strict;
|
|
138
141
|
store.setSetting('strictScope', strict);
|
|
139
142
|
process.stdout.write(color.dim(`solo lo pedido: ${strict ? 'activado' : 'desactivado'}\n`));
|
|
143
|
+
} else if (cmd === 'effort' || cmd === 'esfuerzo') {
|
|
144
|
+
if (arg) {
|
|
145
|
+
const level = normalizeEffort(arg);
|
|
146
|
+
if (!level && !/^(auto|default)$/i.test(arg)) {
|
|
147
|
+
process.stdout.write(color.red('nivel inválido: off | low | medium | high | max | auto\n'));
|
|
148
|
+
rl.prompt();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
effort = level;
|
|
152
|
+
store.setSetting('activeEffort', level || null);
|
|
153
|
+
}
|
|
154
|
+
process.stdout.write(color.dim(`esfuerzo: ${effort || 'auto'}\n`));
|
|
140
155
|
} else if (cmd === 'clear') {
|
|
141
156
|
history = [];
|
|
142
157
|
process.stdout.write(color.dim('historial limpiado\n'));
|
|
@@ -167,6 +182,7 @@ export async function replCommand(argv) {
|
|
|
167
182
|
maxDurationMs: config.maxDurationMs,
|
|
168
183
|
maxOutputTokens: config.maxOutputTokens,
|
|
169
184
|
promptCaching: config.promptCaching === true,
|
|
185
|
+
reasoningEffort: effort,
|
|
170
186
|
tools: toolDefs,
|
|
171
187
|
execute,
|
|
172
188
|
onEvent: printer.onEvent,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { loadConfig, loadInstructions, Store, createTools, buildMcpRegistry, scopeDirective } from '../lib/core.js';
|
|
2
|
+
import { loadConfig, loadInstructions, Store, createTools, buildMcpRegistry, scopeDirective, normalizeEffort } from '../lib/core.js';
|
|
3
3
|
import { parse, databasePath, num } from '../lib/util.js';
|
|
4
4
|
import { buildSystemPrompt, runTurn, createPrinter, resolveCliProvider, mcpSettings, mcpIndexBlock } from '../lib/runtime.js';
|
|
5
5
|
import { color } from '../ui/color.js';
|
|
@@ -17,6 +17,7 @@ const OPTIONS = {
|
|
|
17
17
|
'no-tools': { type: 'boolean' },
|
|
18
18
|
strict: { type: 'boolean' },
|
|
19
19
|
'no-strict': { type: 'boolean' },
|
|
20
|
+
effort: { type: 'string' },
|
|
20
21
|
yes: { type: 'boolean', short: 'y' },
|
|
21
22
|
help: { type: 'boolean', short: 'h' },
|
|
22
23
|
};
|
|
@@ -37,7 +38,7 @@ export async function runCommand(argv) {
|
|
|
37
38
|
const cwd = values.dir ? path.resolve(values.dir) : process.cwd();
|
|
38
39
|
const config = await loadConfig(cwd);
|
|
39
40
|
const instructions = await loadInstructions(cwd, config.instructions);
|
|
40
|
-
const store = new Store(databasePath(
|
|
41
|
+
const store = new Store(databasePath());
|
|
41
42
|
const { providerId, model, baseUrl, apiKey, customProviders } = resolveCliProvider(store, config, {
|
|
42
43
|
provider: values.provider,
|
|
43
44
|
model: values.model,
|
|
@@ -101,6 +102,7 @@ export async function runCommand(argv) {
|
|
|
101
102
|
process.once('SIGINT', onSigint);
|
|
102
103
|
|
|
103
104
|
const strict = values.strict ? true : values['no-strict'] ? false : store.getSetting('strictScope') === true;
|
|
105
|
+
const effort = normalizeEffort(values.effort ?? store.getSetting('activeEffort') ?? config.effort);
|
|
104
106
|
|
|
105
107
|
try {
|
|
106
108
|
const { result, newMessages } = await runTurn({
|
|
@@ -120,6 +122,7 @@ export async function runCommand(argv) {
|
|
|
120
122
|
maxDurationMs: config.maxDurationMs,
|
|
121
123
|
maxOutputTokens: config.maxOutputTokens,
|
|
122
124
|
promptCaching: config.promptCaching === true,
|
|
125
|
+
reasoningEffort: effort,
|
|
123
126
|
signal: controller.signal,
|
|
124
127
|
tools: toolDefs,
|
|
125
128
|
execute,
|
|
@@ -176,6 +179,7 @@ Opciones:
|
|
|
176
179
|
--tools <lista> Tools habilitadas (coma), ej. read,list,grep
|
|
177
180
|
--no-tools Deshabilitar todas las tools
|
|
178
181
|
--strict Hacer solo lo pedido (estricto) --no-strict
|
|
182
|
+
--effort <nivel> Esfuerzo de razonamiento: off | low | medium | high | max
|
|
179
183
|
-y, --yes Aprobar automáticamente los permisos "ask"
|
|
180
184
|
--json Salida JSON (sin streaming)`);
|
|
181
185
|
}
|
|
@@ -149,7 +149,7 @@ if (!foreground) {
|
|
|
149
149
|
async function runForeground({ values, port, host, cwd }) {
|
|
150
150
|
let instance;
|
|
151
151
|
try {
|
|
152
|
-
instance = await createTamnoraServer({ cwd, port, host, dbPath: databasePath(
|
|
152
|
+
instance = await createTamnoraServer({ cwd, port, host, dbPath: databasePath(), yes: Boolean(values.yes) });
|
|
153
153
|
} catch (err) {
|
|
154
154
|
log(`ERROR: ${err.message}`);
|
|
155
155
|
return 1;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { loadConfig, loadInstructions, Store, buildMcpRegistry, resolveMode, scopeDirective } from '../lib/core.js';
|
|
2
|
+
import { loadConfig, loadInstructions, Store, buildMcpRegistry, resolveMode, scopeDirective, normalizeEffort } from '../lib/core.js';
|
|
3
3
|
import { parse, databasePath, num } from '../lib/util.js';
|
|
4
4
|
import { buildSystemPrompt, resolveCliProvider, mcpSettings, mcpIndexBlock } from '../lib/runtime.js';
|
|
5
5
|
import { runTui } from '../tui/app.js';
|
|
@@ -16,6 +16,7 @@ const OPTIONS = {
|
|
|
16
16
|
'no-tools': { type: 'boolean' },
|
|
17
17
|
strict: { type: 'boolean' },
|
|
18
18
|
'no-strict': { type: 'boolean' },
|
|
19
|
+
effort: { type: 'string' },
|
|
19
20
|
yes: { type: 'boolean', short: 'y' },
|
|
20
21
|
help: { type: 'boolean', short: 'h' },
|
|
21
22
|
};
|
|
@@ -29,7 +30,7 @@ export async function tuiCommand(argv) {
|
|
|
29
30
|
|
|
30
31
|
const cwd = values.dir ? path.resolve(values.dir) : process.cwd();
|
|
31
32
|
const config = await loadConfig(cwd);
|
|
32
|
-
const store = new Store(databasePath(
|
|
33
|
+
const store = new Store(databasePath());
|
|
33
34
|
const { providerId, model, baseUrl, apiKey, customProviders } = resolveCliProvider(store, config, {
|
|
34
35
|
provider: values.provider,
|
|
35
36
|
model: values.model,
|
|
@@ -68,6 +69,7 @@ export async function tuiCommand(argv) {
|
|
|
68
69
|
|
|
69
70
|
const mode = resolveMode(values.mode || store.getSetting('activeMode') || 'agent');
|
|
70
71
|
let strict = values.strict ? true : values['no-strict'] ? false : store.getSetting('strictScope') === true;
|
|
72
|
+
const effort = normalizeEffort(values.effort ?? store.getSetting('activeEffort') ?? config.effort);
|
|
71
73
|
const enabledFor = (m) =>
|
|
72
74
|
m.tools ? (baseEnabled ? baseEnabled.filter((tool) => m.tools.includes(tool)) : m.tools) : baseEnabled;
|
|
73
75
|
const promptFor = (m) =>
|
|
@@ -102,6 +104,7 @@ export async function tuiCommand(argv) {
|
|
|
102
104
|
maxDurationMs: config.maxDurationMs,
|
|
103
105
|
maxOutputTokens: config.maxOutputTokens,
|
|
104
106
|
promptCaching: config.promptCaching === true,
|
|
107
|
+
reasoningEffort: effort,
|
|
105
108
|
toolOptions: {
|
|
106
109
|
root: cwd,
|
|
107
110
|
enabled: enabledFor(mode),
|
|
@@ -133,5 +136,6 @@ Opciones:
|
|
|
133
136
|
--max-steps <n> Techo de rondas de herramientas (0 = sin límite, default)
|
|
134
137
|
--tools <lista> Tools habilitadas (coma) --no-tools
|
|
135
138
|
--strict Hacer solo lo pedido (estricto) --no-strict
|
|
139
|
+
--effort <nivel> Esfuerzo de razonamiento: off | low | medium | high | max
|
|
136
140
|
-y, --yes Auto-aprobar tools con permiso "ask"`);
|
|
137
141
|
}
|
|
@@ -78,6 +78,7 @@ export async function runTurn({
|
|
|
78
78
|
maxDurationMs,
|
|
79
79
|
maxOutputTokens,
|
|
80
80
|
promptCaching,
|
|
81
|
+
reasoningEffort,
|
|
81
82
|
stallWarnMs,
|
|
82
83
|
signal,
|
|
83
84
|
execute,
|
|
@@ -106,6 +107,7 @@ export async function runTurn({
|
|
|
106
107
|
maxDurationMs,
|
|
107
108
|
maxOutputTokens,
|
|
108
109
|
promptCaching,
|
|
110
|
+
reasoningEffort,
|
|
109
111
|
stallWarnMs,
|
|
110
112
|
signal,
|
|
111
113
|
onEvent,
|
|
@@ -15,8 +15,8 @@ export function parse(argv, options) {
|
|
|
15
15
|
return { values, positionals };
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
/** Re-
|
|
19
|
-
export {
|
|
18
|
+
/** Re-exportado desde el core: la base es global (casa de Tamnora). */
|
|
19
|
+
export { databasePath } from '../../../core/src/index.js';
|
|
20
20
|
|
|
21
21
|
export function num(value, fallback) {
|
|
22
22
|
const n = Number(value);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Alt-screen + raw input + incremental rendering; reuses the harness engine.
|
|
3
3
|
|
|
4
4
|
import { readFileSync } from 'node:fs';
|
|
5
|
-
import { createTools, diffRows, resolveInRoot } from '../lib/core.js';
|
|
5
|
+
import { createTools, diffRows, resolveInRoot, normalizeEffort } from '../lib/core.js';
|
|
6
6
|
import { runTurn } from '../lib/runtime.js';
|
|
7
7
|
import { parseKeys } from './keys.js';
|
|
8
8
|
import { render } from './layout.js';
|
|
@@ -25,6 +25,7 @@ export function runTui({
|
|
|
25
25
|
maxDurationMs,
|
|
26
26
|
maxOutputTokens,
|
|
27
27
|
promptCaching,
|
|
28
|
+
reasoningEffort,
|
|
28
29
|
toolOptions,
|
|
29
30
|
store,
|
|
30
31
|
sessionId,
|
|
@@ -37,8 +38,9 @@ export function runTui({
|
|
|
37
38
|
let activeMode = readMode(mode);
|
|
38
39
|
let modePrompt = activeMode.systemPrompt ?? systemPrompt;
|
|
39
40
|
let modeEnabled = activeMode.enabled;
|
|
41
|
+
let effort = reasoningEffort || '';
|
|
40
42
|
const state = {
|
|
41
|
-
header: { provider: providerId, model, cwd: toolOptions.root },
|
|
43
|
+
header: { provider: providerId, model, cwd: toolOptions.root, effort },
|
|
42
44
|
messages: [],
|
|
43
45
|
input: '',
|
|
44
46
|
inputCursor: 0,
|
|
@@ -170,12 +172,29 @@ export function runTui({
|
|
|
170
172
|
draw();
|
|
171
173
|
return;
|
|
172
174
|
}
|
|
173
|
-
if (text === '/estricto'
|
|
174
|
-
const strictArg = text.match(/^\/
|
|
175
|
+
if (text === '/estricto') return toggleStrict();
|
|
176
|
+
const strictArg = text.match(/^\/estricto\s+(on|off|si|sí|no)$/i);
|
|
175
177
|
if (strictArg) {
|
|
176
178
|
toggleStrict(/^(on|si|sí)$/i.test(strictArg[1]));
|
|
177
179
|
return;
|
|
178
180
|
}
|
|
181
|
+
const effortArg = text.match(/^\/(?:esfuerzo|effort)(?:\s+(\S+))?$/i);
|
|
182
|
+
if (effortArg) {
|
|
183
|
+
if (effortArg[1]) {
|
|
184
|
+
const next = normalizeEffort(effortArg[1]);
|
|
185
|
+
if (!next && !/^(auto|default)$/i.test(effortArg[1])) {
|
|
186
|
+
addMessage('assistant', 'Nivel inválido: off | low | medium | high | max | auto.');
|
|
187
|
+
draw();
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
effort = next;
|
|
191
|
+
state.header.effort = effort;
|
|
192
|
+
store.setSetting('activeEffort', effort || null);
|
|
193
|
+
}
|
|
194
|
+
addMessage('assistant', `Esfuerzo: ${effort || 'auto'}.`);
|
|
195
|
+
draw();
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
179
198
|
if (text === '/limpiar' || text === '/clear') {
|
|
180
199
|
state.input = '';
|
|
181
200
|
state.messages = [];
|
|
@@ -209,6 +228,7 @@ export function runTui({
|
|
|
209
228
|
maxDurationMs,
|
|
210
229
|
maxOutputTokens,
|
|
211
230
|
promptCaching,
|
|
231
|
+
reasoningEffort: effort,
|
|
212
232
|
signal: controller.signal,
|
|
213
233
|
tools: toolset.definitions,
|
|
214
234
|
execute: toolset.execute,
|
|
@@ -126,7 +126,7 @@ function buildBody(state, width) {
|
|
|
126
126
|
|
|
127
127
|
function headerText(state, width) {
|
|
128
128
|
const left = ` ❯ tamnora`;
|
|
129
|
-
const middle = ` ${state.header.provider}/${state.header.model}`;
|
|
129
|
+
const middle = ` ${state.header.provider}/${state.header.model}${state.header.effort ? ` · ${state.header.effort}` : ''}`;
|
|
130
130
|
const tokens = state.tokens?.prompt ? ` ctx ${formatK(state.tokens.prompt)}` : '';
|
|
131
131
|
const right = state.streaming ? `generando…${tokens} ` : `${state.header.cwd ?? ''}${tokens} `;
|
|
132
132
|
const pad = Math.max(0, width - chars(left).length - chars(middle).length - chars(right).length);
|
|
@@ -2,10 +2,8 @@ import { test } from 'node:test';
|
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { EventEmitter } from 'node:events';
|
|
4
4
|
import {
|
|
5
|
-
parseTunnelmoleUrls,
|
|
6
5
|
parseTailscaleStatus,
|
|
7
6
|
startTunnel,
|
|
8
|
-
startTunnelmole,
|
|
9
7
|
stopTailscaleServe,
|
|
10
8
|
tailscalePreviewUrl,
|
|
11
9
|
} from '../src/lib/tunnel.js';
|
|
@@ -21,16 +19,6 @@ function fakeChild(exitCode = 0) {
|
|
|
21
19
|
return child;
|
|
22
20
|
}
|
|
23
21
|
|
|
24
|
-
test('parseTunnelmoleUrls extracts the https URL', () => {
|
|
25
|
-
const out = parseTunnelmoleUrls('Listening at https://abc-123.tunnelmole.net (forwarding to 4517)');
|
|
26
|
-
assert.equal(out.https, 'https://abc-123.tunnelmole.net');
|
|
27
|
-
assert.equal(out.http, '');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test('parseTunnelmoleUrls ignores unrelated output', () => {
|
|
31
|
-
assert.deepEqual(parseTunnelmoleUrls('starting tunnelmole...'), { https: '', http: '' });
|
|
32
|
-
});
|
|
33
|
-
|
|
34
22
|
test('parseTailscaleStatus lee el DNSName del nodo y su IPv4', () => {
|
|
35
23
|
const out = parseTailscaleStatus(
|
|
36
24
|
JSON.stringify({ Self: { DNSName: 'pc.tailnet.ts.net.', TailscaleIPs: ['100.64.0.5', 'fd7a:115c:a1e0::5'] } })
|
|
@@ -46,8 +34,10 @@ test('parseTailscaleStatus marca desconectado sin DNSName', () => {
|
|
|
46
34
|
assert.equal(out.dnsName, '');
|
|
47
35
|
});
|
|
48
36
|
|
|
49
|
-
test('startTunnel rechaza
|
|
50
|
-
|
|
37
|
+
test('startTunnel rechaza los proveedores publicos retirados', async () => {
|
|
38
|
+
for (const provider of ['relay', 'tunnelmole', 'tmole']) {
|
|
39
|
+
await assert.rejects(() => startTunnel(4517, provider), /no soportado/, provider);
|
|
40
|
+
}
|
|
51
41
|
});
|
|
52
42
|
|
|
53
43
|
test('startTunnel con provider none no abre nada', async () => {
|
|
@@ -69,23 +59,6 @@ test('stopTailscaleServe apaga solo el handler 443 y lanza tailscale sin shell',
|
|
|
69
59
|
assert.equal(calls[0].options.shell, undefined);
|
|
70
60
|
});
|
|
71
61
|
|
|
72
|
-
test(
|
|
73
|
-
'startTunnelmole con shell manda un unico comando (Node no concatena args)',
|
|
74
|
-
{ skip: process.platform !== 'win32' },
|
|
75
|
-
async () => {
|
|
76
|
-
const calls = [];
|
|
77
|
-
const spawnImpl = (cmd, args, options) => {
|
|
78
|
-
calls.push({ cmd, args, options });
|
|
79
|
-
return fakeChild(1);
|
|
80
|
-
};
|
|
81
|
-
await assert.rejects(() => startTunnelmole(4517, { spawnImpl }), /termino/);
|
|
82
|
-
assert.equal(calls.length, 1);
|
|
83
|
-
assert.equal(calls[0].cmd, 'npx --yes tunnelmole 4517');
|
|
84
|
-
assert.deepEqual(calls[0].args, []);
|
|
85
|
-
assert.equal(calls[0].options.shell, true);
|
|
86
|
-
}
|
|
87
|
-
);
|
|
88
|
-
|
|
89
62
|
test('tailscalePreviewUrl usa el 8443 y omite el 443', () => {
|
|
90
63
|
assert.equal(tailscalePreviewUrl('pc.tailnet.ts.net'), 'https://pc.tailnet.ts.net:8443');
|
|
91
64
|
assert.equal(tailscalePreviewUrl('pc.tailnet.ts.net', 9000), 'https://pc.tailnet.ts.net:9000');
|
|
@@ -93,7 +66,7 @@ test('tailscalePreviewUrl usa el 8443 y omite el 443', () => {
|
|
|
93
66
|
});
|
|
94
67
|
|
|
95
68
|
test('qrTerminal renders a block matrix for a URL', () => {
|
|
96
|
-
const art = qrTerminal('https://
|
|
69
|
+
const art = qrTerminal('https://pc.tailnet.ts.net', 1);
|
|
97
70
|
assert.ok(art.length > 0);
|
|
98
71
|
assert.match(art, /[\u2580\u2584\u2588]/);
|
|
99
72
|
});
|
|
@@ -60,6 +60,7 @@ export async function runAgent({
|
|
|
60
60
|
maxDurationMs = 0,
|
|
61
61
|
maxOutputTokens = 0,
|
|
62
62
|
promptCaching = false,
|
|
63
|
+
reasoningEffort = '',
|
|
63
64
|
repeatLimit = 3,
|
|
64
65
|
wrapUp = true,
|
|
65
66
|
stallWarnMs = 90000,
|
|
@@ -148,6 +149,7 @@ export async function runAgent({
|
|
|
148
149
|
signal,
|
|
149
150
|
maxOutputTokens,
|
|
150
151
|
promptCaching,
|
|
152
|
+
reasoningEffort,
|
|
151
153
|
onEvent: (event) => {
|
|
152
154
|
if (event && (event.type === 'reasoning' || event.type === 'tool_calls')) emitted = true;
|
|
153
155
|
if (event?.type === 'text' && event.delta) emitted = true;
|
|
@@ -274,7 +276,7 @@ export async function runAgent({
|
|
|
274
276
|
|
|
275
277
|
let wrappedUp = false;
|
|
276
278
|
if (wrapUp && (stopReason === 'max_rounds' || stopReason === 'no_progress' || stopReason === 'budget')) {
|
|
277
|
-
const wrap = await runFinalAnswer({ provider, config, model, working, signal, onEvent, maxRetries: retries, retryDelayMs, maxOutputTokens, promptCaching });
|
|
279
|
+
const wrap = await runFinalAnswer({ provider, config, model, working, signal, onEvent, maxRetries: retries, retryDelayMs, maxOutputTokens, promptCaching, reasoningEffort });
|
|
278
280
|
if (wrap.usage) accumulateUsage(wrap.usage);
|
|
279
281
|
if (wrap.content) {
|
|
280
282
|
working.push({ role: 'assistant', content: wrap.content, tokens_in: wrap.usage?.prompt_tokens, tokens_out: wrap.usage?.completion_tokens });
|
|
@@ -297,7 +299,7 @@ export async function runAgent({
|
|
|
297
299
|
}
|
|
298
300
|
|
|
299
301
|
/** Ronda final con tools deshabilitadas para forzar un cierre con texto. */
|
|
300
|
-
async function runFinalAnswer({ provider, config, model, working, signal, onEvent, maxRetries, retryDelayMs, maxOutputTokens, promptCaching }) {
|
|
302
|
+
async function runFinalAnswer({ provider, config, model, working, signal, onEvent, maxRetries, retryDelayMs, maxOutputTokens, promptCaching, reasoningEffort }) {
|
|
301
303
|
try {
|
|
302
304
|
const result = await withRetry(
|
|
303
305
|
() =>
|
|
@@ -308,6 +310,7 @@ async function runFinalAnswer({ provider, config, model, working, signal, onEven
|
|
|
308
310
|
signal,
|
|
309
311
|
maxOutputTokens,
|
|
310
312
|
promptCaching,
|
|
313
|
+
reasoningEffort,
|
|
311
314
|
onEvent,
|
|
312
315
|
}),
|
|
313
316
|
{ retries: maxRetries, baseDelayMs: retryDelayMs, signal }
|