@danieltmn/tamnora 0.9.3 → 0.9.5
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 +30 -0
- package/README.md +51 -16
- package/harness/packages/cli/src/commands/doctor.js +14 -0
- package/harness/packages/cli/src/commands/update.js +91 -21
- package/harness/packages/core/src/agent/modes.js +14 -1
- package/harness/packages/core/src/agent/runAgent.js +23 -2
- package/harness/packages/core/src/browser/index.js +289 -0
- package/harness/packages/core/src/config/home.js +5 -0
- package/harness/packages/core/src/index.js +9 -2
- package/harness/packages/core/src/tools/executors.js +96 -0
- package/harness/packages/core/src/tools/labels.js +4 -0
- package/harness/packages/core/src/tools/permissions.js +4 -0
- package/harness/packages/core/src/tools/schema.js +47 -0
- package/harness/packages/core/src/version.js +81 -7
- package/harness/packages/core/test/agent.test.js +30 -1
- package/harness/packages/core/test/browser.test.js +18 -0
- package/harness/packages/core/test/tools.test.js +16 -1
- package/harness/packages/core/test/version.test.js +34 -0
- package/harness/packages/server/public/app.js +244 -8
- package/harness/packages/server/public/index.html +57 -3
- package/harness/packages/server/public/landing/index.html +14 -1
- package/harness/packages/server/public/markdown.js +54 -0
- package/harness/packages/server/public/reconcile.js +3 -1
- package/harness/packages/server/public/styles.css +48 -0
- package/harness/packages/server/src/lib/advisor.js +177 -0
- package/harness/packages/server/src/lib/agentRun.js +4 -3
- package/harness/packages/server/src/lib/channels/telegram.js +130 -25
- package/harness/packages/server/src/lib/ttsSettings.js +2 -2
- package/harness/packages/server/src/lib/update.js +82 -0
- package/harness/packages/server/src/routes/advisor.js +76 -0
- package/harness/packages/server/src/routes/chat.js +30 -0
- package/harness/packages/server/src/routes/files.js +25 -1
- package/harness/packages/server/src/routes/tts.js +1 -1
- package/harness/packages/server/src/routes/update.js +43 -0
- package/harness/packages/server/src/server.js +26 -2
- package/harness/packages/server/test/advisor.test.js +205 -0
- package/harness/packages/server/test/markdown.test.js +43 -1
- package/harness/packages/server/test/server.test.js +50 -14
- package/harness/packages/server/test/telegram-features.test.js +25 -4
- package/harness/packages/server/test/update.test.js +94 -0
- package/harness/scripts/self-update.mjs +123 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,36 @@ 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.9.5] - 2026-10-08
|
|
9
|
+
|
|
10
|
+
### Agregado
|
|
11
|
+
|
|
12
|
+
- **Actualizacion desde la consola**: `GET /api/update` consulta el registro npm
|
|
13
|
+
y un banner ofrece **actualizar con un click**; `POST /api/update` (solo
|
|
14
|
+
loopback) apaga el server, instala la version nueva con un script detached y lo
|
|
15
|
+
vuelve a levantar. Un click en la version del topbar fuerza el chequeo.
|
|
16
|
+
`tamnora update` detiene el server antes de instalar (Windows bloquea los
|
|
17
|
+
archivos), detecta drift entre instalaciones y lo reinicia; `tamnora doctor`
|
|
18
|
+
avisa si hay una version nueva.
|
|
19
|
+
- **Asesor**: una capa de comentario sobre el run activo que interpreta lo que
|
|
20
|
+
hizo el agente y, si aporta algo, alerta un problema real de codigo, explica un
|
|
21
|
+
proceso o da un consejo. No lee la respuesta del agente. Tarjeta en el chat,
|
|
22
|
+
evento `advice` y voz opcional. Ajustes en la pestana **Asesor** (`app.json` ->
|
|
23
|
+
`advisor`).
|
|
24
|
+
- **Voz: leer la respuesta final**: `tts.announce.answer` lee la respuesta del
|
|
25
|
+
agente limpiando bloques de codigo, tablas y marcas de markdown.
|
|
26
|
+
|
|
27
|
+
### Corregido
|
|
28
|
+
|
|
29
|
+
- El asesor y la respuesta final ya no se descartan de la cola de audio cuando
|
|
30
|
+
quedan detras de una narracion larga (`maxAgeMs: 0`).
|
|
31
|
+
|
|
32
|
+
## [0.9.4] - 2026-10-07
|
|
33
|
+
|
|
34
|
+
### Cambios
|
|
35
|
+
|
|
36
|
+
- feat: navegador headless (screenshot/console_errors) y permisos de Telegram
|
|
37
|
+
|
|
8
38
|
## [0.9.3] - 2026-10-07
|
|
9
39
|
|
|
10
40
|
### Cambios
|
package/README.md
CHANGED
|
@@ -5,8 +5,19 @@ con TUI, consola web, tools locales, MCP y evaluacion de modelos.
|
|
|
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.
|
|
8
|
-
- **Multi-proveedor**: OpenRouter, OpenAI,
|
|
9
|
-
Groq, Mistral, xAI, Together, Fireworks,
|
|
8
|
+
- **Multi-proveedor**: OpenRouter, OpenAI, Anthropic (Claude), Google Gemini,
|
|
9
|
+
proveedores compatibles (DeepSeek, Groq, Mistral, xAI, Together, Fireworks,
|
|
10
|
+
Cerebras, Perplexity) y Ollama.
|
|
11
|
+
- **Navegador headless**: tools `screenshot` y `console_errors` (renderiza JS y
|
|
12
|
+
saca una captura PNG o lista los errores de consola) y `preview_url`.
|
|
13
|
+
- **Voz**: el agente narra su progreso, puede **leer la respuesta final** (sin
|
|
14
|
+
bloques de codigo ni tablas) y mandar audio bajo pedido (`speak`,
|
|
15
|
+
`/audioresumen`).
|
|
16
|
+
- **Asesor**: una capa de comentario sobre el run que interpreta lo que hizo el
|
|
17
|
+
agente y, si aporta algo, avisa de un problema en el codigo, explica un proceso
|
|
18
|
+
o da un consejo (con tarjeta en el chat y voz opcional). No lee la respuesta.
|
|
19
|
+
- **Actualizar desde la consola**: la consola avisa cuando hay una version nueva
|
|
20
|
+
y la instala con un click (el server se reinicia solo).
|
|
10
21
|
|
|
11
22
|
## Instalacion
|
|
12
23
|
|
|
@@ -39,7 +50,8 @@ tamnora tui --mode plan # modo plan: solo lectura, sin escritura ni bash
|
|
|
39
50
|
Atajos: `enter` enviar · `↑/↓` historial · `←/→` cursor · `home/end` inicio/fin ·
|
|
40
51
|
`pgup/pgdn` scroll · `ctrl+l` limpiar · `ctrl+c` cancelar/salir · `/salir`,
|
|
41
52
|
`/limpiar`. El pegado de bloques multilinea entra como un solo texto y el header
|
|
42
|
-
muestra el contexto usado (`ctx <tokens>`).
|
|
53
|
+
muestra el contexto usado (`ctx <tokens>`). Las tools `write`/`edit` muestran un
|
|
54
|
+
**diff lado a lado** (viejo en rojo, nuevo en verde, con numeros de linea).
|
|
43
55
|
|
|
44
56
|
### Server como servicio
|
|
45
57
|
|
|
@@ -50,8 +62,8 @@ tamnora server --reload # reinicia aplicando config/codigo
|
|
|
50
62
|
tamnora server --stop
|
|
51
63
|
tamnora server --status
|
|
52
64
|
tamnora server --logs --follow
|
|
53
|
-
tamnora doctor # verifica Node, casa, puerto y
|
|
54
|
-
tamnora update # busca/instala una version nueva
|
|
65
|
+
tamnora doctor # verifica Node, casa, puerto, base y version nueva
|
|
66
|
+
tamnora update # busca/instala una version nueva (reinicia el server)
|
|
55
67
|
tamnora autostart install # arranque automatico del server
|
|
56
68
|
```
|
|
57
69
|
|
|
@@ -63,10 +75,14 @@ Tamnora (override con `--home` o `TAMNORA_HOME`):
|
|
|
63
75
|
- macOS: `~/Library/Application Support/tamnora`
|
|
64
76
|
- Linux: `~/.config/tamnora`
|
|
65
77
|
|
|
66
|
-
> **Actualizar**: en `0.
|
|
67
|
-
>
|
|
68
|
-
>
|
|
69
|
-
>
|
|
78
|
+
> **Actualizar**: en `0.9.5+` la consola avisa cuando hay una version nueva y la
|
|
79
|
+
> instala con un click (el server se reinicia solo); un click en la version del
|
|
80
|
+
> topbar fuerza el chequeo. Por CLI, `tamnora update --yes` ahora **detiene el
|
|
81
|
+
> server antes de instalar** (en Windows libera los archivos) y lo reinicia. Si
|
|
82
|
+
> venis de **`0.8.0` o anterior** y "dice que actualizo pero sigue igual", hace una
|
|
83
|
+
> vez: `tamnora server --stop`, `npm i -g @danieltmn/tamnora@latest` y
|
|
84
|
+
> `tamnora server --start`. Verifica que `where tamnora` coincida con
|
|
85
|
+
> `npm root -g` (si no, hay dos instalaciones y el update toca la otra).
|
|
70
86
|
|
|
71
87
|
### Consola web
|
|
72
88
|
|
|
@@ -91,6 +107,16 @@ menu "mas" (los tres puntitos). En el chat:
|
|
|
91
107
|
- **Runs durables**: cerrar el celular o cortar la red **no cancela** el turno; al
|
|
92
108
|
volver, la consola **reconecta** y reconstruye el progreso. Para cortarlo se usa
|
|
93
109
|
el boton de detener (o `POST /api/sessions/:id/stop`).
|
|
110
|
+
- **Audio**: un unico reproductor encola la narracion, el tool `speak`, la
|
|
111
|
+
respuesta final y los consejos del asesor, y reproduce de a uno con pausa y
|
|
112
|
+
detener (Ajustes -> Voz).
|
|
113
|
+
- **Asesor**: en Ajustes -> Asesor se activa una capa que comenta el turno
|
|
114
|
+
(problemas de codigo / explicaciones / consejos) con proveedor y modelo activos.
|
|
115
|
+
- **Actualizar**: cuando npm tiene una version nueva aparece un banner para
|
|
116
|
+
actualizar con un click; el server se reinicia solo.
|
|
117
|
+
- **Comandos**: el input muestra un menu al escribir `/` (`/new`, `/init`, `/goal`,
|
|
118
|
+
`/resumen`, `/audioresumen`, `/compact`, `/clear`, `/cwd`, `/kill`, `/help`).
|
|
119
|
+
- **Capturas**: el tool `screenshot` muestra la imagen inline en su tarjeta.
|
|
94
120
|
|
|
95
121
|
### Preview
|
|
96
122
|
|
|
@@ -136,10 +162,15 @@ Con un token de bot (`telegram.token` en `tamnora.json`, `app.json` o
|
|
|
136
162
|
`TAMNORA_TELEGRAM_TOKEN`) podes manejar el harness desde el chat. Cada chat mapea
|
|
137
163
|
a una sesion y el bot soporta:
|
|
138
164
|
|
|
139
|
-
- texto, **imagenes** y
|
|
140
|
-
|
|
141
|
-
-
|
|
142
|
-
-
|
|
165
|
+
- texto, **imagenes** (entrantes y salientes: el tool `screenshot` llega como foto)
|
|
166
|
+
y **notas de voz** (transcripcion con `telegram.transcribeModel`, default
|
|
167
|
+
`whisper-1`, via `POST {baseUrl}/audio/transcriptions`);
|
|
168
|
+
- aprobaciones `ask` con **botones inline** y una explicacion de lo que va a hacer
|
|
169
|
+
el agente (`Permitir` / `Permitir todo` / `Denegar`); "Permitir todo" deja de
|
|
170
|
+
preguntar por el resto de la sesion;
|
|
171
|
+
- el bot **solo manda audio cuando lo pedis** (tool `speak` o `/audioresumen`);
|
|
172
|
+
- comandos `/new`, `/resumen`, `/audioresumen`, `/status`, `/model`, `/provider`,
|
|
173
|
+
`/mode`, `/stop`, `/help`;
|
|
143
174
|
- **notificaciones**: si un run de la consola pertenece a una sesion vinculada a un
|
|
144
175
|
chat, el canal avisa el fin, los errores, los subagentes y los permisos.
|
|
145
176
|
|
|
@@ -161,11 +192,15 @@ entorno completo, `"inheritEnv": true`.
|
|
|
161
192
|
## Tools
|
|
162
193
|
|
|
163
194
|
Tools locales confinadas al workspace (`read`, `list`, `glob`, `grep`, `write`,
|
|
164
|
-
`edit`, `bash`, `fetch_url`)
|
|
195
|
+
`edit`, `bash`, `fetch_url`), de navegador (`screenshot`, `console_errors`,
|
|
196
|
+
`preview_url`), de voz (`speak`) y de Git (`git_status`, `git_diff`, `git_log`,
|
|
165
197
|
`git_commit`), con permisos `allow`/`ask`/`deny` por tool o por patron.
|
|
198
|
+
|
|
166
199
|
`fetch_url` lee URLs (HTML a texto) y avisa cuando un sitio exige JavaScript o
|
|
167
|
-
esta detras de Cloudflare; para esos casos
|
|
168
|
-
|
|
200
|
+
esta detras de Cloudflare; para esos casos las tools `screenshot` / `console_errors`
|
|
201
|
+
usan el **Chrome/Edge/Chromium del sistema** como navegador headless (sin
|
|
202
|
+
dependencias): sacan una captura PNG y recolectan los errores de consola. Para
|
|
203
|
+
automatizacion mas completa, registra un MCP (por ejemplo Playwright).
|
|
169
204
|
|
|
170
205
|
## Subagentes
|
|
171
206
|
|
|
@@ -7,6 +7,9 @@ import {
|
|
|
7
7
|
databasePath,
|
|
8
8
|
serverLogPath,
|
|
9
9
|
Store,
|
|
10
|
+
readPackageVersion,
|
|
11
|
+
compareVersions,
|
|
12
|
+
fetchLatestVersion,
|
|
10
13
|
} from '../../../core/src/index.js';
|
|
11
14
|
import { parse } from '../lib/util.js';
|
|
12
15
|
import { pidAlive, readRuntime } from '../lib/daemon.js';
|
|
@@ -52,6 +55,17 @@ export async function doctorCommand(argv) {
|
|
|
52
55
|
checks.push(['base de datos', dbOk, dbOk ? '' : 'se creara al primer uso']);
|
|
53
56
|
checks.push(['proveedor activo', Boolean(provider), provider || 'elegi uno en la consola (npm: tamnora serve)']);
|
|
54
57
|
|
|
58
|
+
const current = readPackageVersion();
|
|
59
|
+
let versionHint = '';
|
|
60
|
+
try {
|
|
61
|
+
const latest = await fetchLatestVersion();
|
|
62
|
+
versionHint =
|
|
63
|
+
compareVersions(latest, current) > 0 ? `hay v${latest}: corre tamnora update --yes` : 'al dia';
|
|
64
|
+
} catch (error) {
|
|
65
|
+
versionHint = `no pude verificar (${error?.message ?? error})`;
|
|
66
|
+
}
|
|
67
|
+
checks.push([`version (v${current})`, true, versionHint]);
|
|
68
|
+
|
|
55
69
|
if (serverLive) checks.push([`server (pid ${rt.pid})`, true, rt.url || '']);
|
|
56
70
|
checks.push([`logs`, true, serverLogPath()]);
|
|
57
71
|
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fetchLatestVersion, compareVersions, packageRoot, readPackageVersion, readApp } from '../lib/core.js';
|
|
3
4
|
import { parse } from '../lib/util.js';
|
|
5
|
+
import { readRuntime, pidAlive, stopRunningServer, spawnDetached, portHint } from '../lib/daemon.js';
|
|
4
6
|
|
|
5
7
|
const PACKAGE = '@danieltmn/tamnora';
|
|
6
8
|
const IS_WIN = process.platform === 'win32';
|
|
7
9
|
const NPM = IS_WIN ? 'npm.cmd' : 'npm';
|
|
8
|
-
const OPTIONS = {
|
|
10
|
+
const OPTIONS = {
|
|
11
|
+
yes: { type: 'boolean', short: 'y' },
|
|
12
|
+
check: { type: 'boolean' },
|
|
13
|
+
restart: { type: 'boolean' },
|
|
14
|
+
'no-restart': { type: 'boolean' },
|
|
15
|
+
tag: { type: 'string' },
|
|
16
|
+
help: { type: 'boolean', short: 'h' },
|
|
17
|
+
};
|
|
9
18
|
|
|
10
19
|
function npm(args) {
|
|
11
|
-
return spawnSync(NPM, args, { encoding: 'utf8', windowsHide: true, shell: IS_WIN });
|
|
20
|
+
return spawnSync(NPM, args, { encoding: 'utf8', windowsHide: true, shell: IS_WIN, timeout: 180000 });
|
|
12
21
|
}
|
|
13
22
|
|
|
23
|
+
/** Version del paquete instalado globalmente (la que resuelve `npm ls -g`). */
|
|
14
24
|
function installedVersion() {
|
|
15
25
|
const out = npm(['ls', '-g', PACKAGE, '--json', '--depth=0']);
|
|
16
26
|
try {
|
|
@@ -21,10 +31,49 @@ function installedVersion() {
|
|
|
21
31
|
}
|
|
22
32
|
}
|
|
23
33
|
|
|
34
|
+
function globalRoot() {
|
|
35
|
+
const out = npm(['root', '-g']);
|
|
36
|
+
return out.status === 0 ? String(out.stdout || '').trim() : '';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const norm = (p) => path.resolve(String(p || '')).toLowerCase();
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Detiene el server gestionado (runtime.json) para que Windows libere los
|
|
43
|
+
* archivos bloqueados; sin esto `npm i -g` falla o queda a medias.
|
|
44
|
+
*/
|
|
45
|
+
async function stopServerIfRunning() {
|
|
46
|
+
const rt = readRuntime();
|
|
47
|
+
if (!rt || !pidAlive(rt.pid)) return null;
|
|
48
|
+
console.log(`Deteniendo el server (pid ${rt.pid}) para poder reemplazar los archivos...`);
|
|
49
|
+
stopRunningServer();
|
|
50
|
+
for (let i = 0; i < 25 && pidAlive(rt.pid); i++) await new Promise((r) => setTimeout(r, 200));
|
|
51
|
+
return rt;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function restartServer(rt) {
|
|
55
|
+
try {
|
|
56
|
+
const bin = process.argv[1];
|
|
57
|
+
const args = [bin, 'server', '--stream', '--no-open'];
|
|
58
|
+
if (rt?.port) args.push('--port', String(rt.port));
|
|
59
|
+
if (rt?.host) args.push('--host', rt.host);
|
|
60
|
+
if (rt?.cwd) args.push('--dir', rt.cwd);
|
|
61
|
+
const { child } = spawnDetached(args, { envFlag: 'TAMNORA_DETACHED' });
|
|
62
|
+
console.log(`Server reiniciado en segundo plano (pid ${child.pid}).`);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error(`No pude reiniciar el server solo: ${error?.message ?? error}`);
|
|
65
|
+
console.error('Reinicialo a mano: tamnora server --start');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
24
69
|
export async function updateCommand(argv) {
|
|
25
70
|
const { values } = parse(argv, OPTIONS);
|
|
26
71
|
if (values.help) {
|
|
27
|
-
console.log(
|
|
72
|
+
console.log(
|
|
73
|
+
'Uso: tamnora update [--check] [--yes] [--no-restart]\n\n' +
|
|
74
|
+
'Busca una version nueva en npm y actualiza la instalacion global.\n' +
|
|
75
|
+
'Detiene el server antes de instalar (en Windows libera los archivos) y lo reinicia al terminar.'
|
|
76
|
+
);
|
|
28
77
|
return 0;
|
|
29
78
|
}
|
|
30
79
|
|
|
@@ -33,24 +82,20 @@ export async function updateCommand(argv) {
|
|
|
33
82
|
const version = readPackageVersion();
|
|
34
83
|
console.log(`Version actual: ${version}`);
|
|
35
84
|
|
|
36
|
-
const view = npm(['view', spec, 'version', '--json']);
|
|
37
85
|
let latest = '';
|
|
38
86
|
try {
|
|
39
|
-
latest =
|
|
40
|
-
} catch {
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
if (view.status !== 0 || !/^\d+\.\d+\.\d+/.test(latest)) {
|
|
44
|
-
console.error(`No se pudo consultar npm (${tag}): ${(view.stderr || '').trim() || 'revisa tu conexion'}`);
|
|
87
|
+
latest = await fetchLatestVersion({ pkg: PACKAGE, tag });
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error(`No se pudo consultar npm (${tag}): ${error?.message ?? error}`);
|
|
45
90
|
return 1;
|
|
46
91
|
}
|
|
47
92
|
console.log(`Disponible (${tag}): ${latest}`);
|
|
48
|
-
if (latest
|
|
93
|
+
if (compareVersions(latest, version) <= 0) {
|
|
49
94
|
console.log('Ya estas en la ultima version.');
|
|
50
95
|
return 0;
|
|
51
96
|
}
|
|
52
97
|
if (values.check) {
|
|
53
|
-
console.log(
|
|
98
|
+
console.log('Hay una version nueva. Corre: tamnora update --yes');
|
|
54
99
|
return 0;
|
|
55
100
|
}
|
|
56
101
|
if (!values.yes) {
|
|
@@ -60,21 +105,46 @@ export async function updateCommand(argv) {
|
|
|
60
105
|
return 0;
|
|
61
106
|
}
|
|
62
107
|
|
|
108
|
+
const wasRunning = await stopServerIfRunning();
|
|
109
|
+
if (!wasRunning) {
|
|
110
|
+
// Un server no gestionado (ej. `pnpm serve` en primer plano) tambien
|
|
111
|
+
// bloquea los archivos. Avisamos en vez de matar un proceso ajeno.
|
|
112
|
+
const app = readApp();
|
|
113
|
+
const hint = app?.port ? portHint(app.port) : '';
|
|
114
|
+
if (hint) console.warn(`aviso: algo escucha en el puerto ${app.port} (${hint}); cerralo si la instalacion falla.`);
|
|
115
|
+
}
|
|
116
|
+
|
|
63
117
|
console.log(`Actualizando a ${spec}...`);
|
|
64
|
-
const result = spawnSync(NPM, ['i', '-g', spec], {
|
|
65
|
-
stdio: 'inherit',
|
|
66
|
-
windowsHide: true,
|
|
67
|
-
shell: IS_WIN,
|
|
68
|
-
});
|
|
118
|
+
const result = spawnSync(NPM, ['i', '-g', spec], { stdio: 'inherit', windowsHide: true, shell: IS_WIN });
|
|
69
119
|
if (result.status !== 0) {
|
|
70
120
|
console.error(`La actualizacion fallo. Proba a mano: npm i -g ${spec}`);
|
|
121
|
+
if (wasRunning) restartServer(wasRunning);
|
|
71
122
|
return 1;
|
|
72
123
|
}
|
|
124
|
+
|
|
73
125
|
const installed = installedVersion() || readPackageVersion();
|
|
74
126
|
console.log(`Listo. Instalado: v${installed} (pediste ${latest}).`);
|
|
75
|
-
|
|
76
|
-
|
|
127
|
+
|
|
128
|
+
// Drift: npm instala en un prefix distinto al de la copia que esta corriendo.
|
|
129
|
+
const runningRoot = packageRoot();
|
|
130
|
+
const expectedRoot = globalRoot();
|
|
131
|
+
const expectedPkg = expectedRoot ? path.join(expectedRoot, ...PACKAGE.split('/')) : '';
|
|
132
|
+
if (runningRoot && expectedPkg && norm(runningRoot) !== norm(expectedPkg)) {
|
|
133
|
+
console.warn('Ojo: la copia global quedo en un lugar distinto al de esta instalacion.');
|
|
134
|
+
console.warn(` instalada: ${expectedPkg}`);
|
|
135
|
+
console.warn(` en uso: ${runningRoot}`);
|
|
136
|
+
console.warn(' puede que el update no afecte la copia que corre; revisa cual resuelve "tamnora".');
|
|
137
|
+
}
|
|
138
|
+
if (installed && compareVersions(installed, latest) < 0) {
|
|
139
|
+
console.warn('Ojo: la version instalada no coincide con la publicada. Cerra procesos que usen la carpeta y reintenta.');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (values['no-restart']) {
|
|
143
|
+
console.log('Reinicia el server cuando quieras: tamnora server --reload');
|
|
144
|
+
} else if (wasRunning || values.restart) {
|
|
145
|
+
restartServer(wasRunning || { port: readApp().port });
|
|
146
|
+
} else {
|
|
147
|
+
console.log('Reinicia el server: tamnora server --reload');
|
|
77
148
|
}
|
|
78
|
-
console.log('Reinicia el server: tamnora server --reload');
|
|
79
149
|
return 0;
|
|
80
150
|
}
|
|
@@ -13,7 +13,20 @@ export const MODES = {
|
|
|
13
13
|
id: 'plan',
|
|
14
14
|
label: 'Plan',
|
|
15
15
|
description: 'Solo lectura: investiga y propone un plan, sin modificar nada.',
|
|
16
|
-
tools: [
|
|
16
|
+
tools: [
|
|
17
|
+
'read',
|
|
18
|
+
'list',
|
|
19
|
+
'glob',
|
|
20
|
+
'grep',
|
|
21
|
+
'fetch_url',
|
|
22
|
+
'speak',
|
|
23
|
+
'screenshot',
|
|
24
|
+
'console_errors',
|
|
25
|
+
'preview_url',
|
|
26
|
+
'git_status',
|
|
27
|
+
'git_diff',
|
|
28
|
+
'git_log',
|
|
29
|
+
],
|
|
17
30
|
systemPrompt:
|
|
18
31
|
'Estás en modo PLAN: no modifiques archivos ni ejecutes comandos. Investigá el proyecto con las herramientas de solo lectura y respondé con un plan concreto y accionable.',
|
|
19
32
|
},
|
|
@@ -215,12 +215,19 @@ export async function runAgent({
|
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
|
|
218
|
+
const normalized = normalizeToolOutput(output);
|
|
219
|
+
onEvent?.({
|
|
220
|
+
type: 'tool_result',
|
|
221
|
+
id: call.id,
|
|
222
|
+
name,
|
|
223
|
+
result: normalized.content,
|
|
224
|
+
meta: normalized.meta,
|
|
225
|
+
});
|
|
219
226
|
working.push({
|
|
220
227
|
role: 'tool',
|
|
221
228
|
tool_call_id: call.id,
|
|
222
229
|
name,
|
|
223
|
-
content:
|
|
230
|
+
content: normalized.content,
|
|
224
231
|
});
|
|
225
232
|
}
|
|
226
233
|
|
|
@@ -315,6 +322,20 @@ function createStallGuard(onEvent, stallWarnMs) {
|
|
|
315
322
|
};
|
|
316
323
|
}
|
|
317
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Normaliza el resultado de un tool a `{ content, meta }`. Un executor puede
|
|
327
|
+
* devolver un string (lo normal) o `{ content, meta }` cuando necesita mandar
|
|
328
|
+
* metadata al cliente sin ensuciar lo que ve el modelo (p. ej. una captura).
|
|
329
|
+
*/
|
|
330
|
+
export function normalizeToolOutput(output) {
|
|
331
|
+
if (typeof output === 'string') return { content: output };
|
|
332
|
+
if (output && typeof output === 'object' && typeof output.content === 'string') {
|
|
333
|
+
return { content: output.content, meta: output.meta };
|
|
334
|
+
}
|
|
335
|
+
if (output == null) return { content: '' };
|
|
336
|
+
return { content: JSON.stringify(output) };
|
|
337
|
+
}
|
|
338
|
+
|
|
318
339
|
async function defaultExecute(name) {
|
|
319
340
|
return `Error: la herramienta "${name}" no está disponible todavía (se agrega en la Fase 1).`;
|
|
320
341
|
}
|