@danieltmn/tamnora 0.25.1 → 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.
Files changed (87) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +13 -25
  3. package/SECURITY.md +3 -4
  4. package/harness/packages/cli/src/cli.js +0 -3
  5. package/harness/packages/cli/src/commands/bridge.js +0 -1
  6. package/harness/packages/cli/src/commands/repl.js +3 -3
  7. package/harness/packages/cli/src/commands/run.js +1 -1
  8. package/harness/packages/cli/src/commands/server.js +1 -1
  9. package/harness/packages/cli/src/commands/tui.js +1 -1
  10. package/harness/packages/cli/src/lib/util.js +2 -2
  11. package/harness/packages/cli/src/tui/app.js +2 -2
  12. package/harness/packages/cli/test/tunnel.test.js +5 -32
  13. package/harness/packages/core/src/agents/index.js +13 -41
  14. package/harness/packages/core/src/config/app.js +0 -4
  15. package/harness/packages/core/src/config/home.js +1 -20
  16. package/harness/packages/core/src/config/index.js +5 -8
  17. package/harness/packages/core/src/extensions/load.js +22 -0
  18. package/harness/packages/core/src/frontmatter.js +32 -0
  19. package/harness/packages/core/src/hooks/index.js +2 -16
  20. package/harness/packages/core/src/index.js +2 -14
  21. package/harness/packages/core/src/plugins/index.js +2 -16
  22. package/harness/packages/core/src/procs/project.js +68 -0
  23. package/harness/packages/core/src/procs/static.js +17 -0
  24. package/harness/packages/core/src/rules/index.js +8 -27
  25. package/harness/packages/core/src/skills/index.js +8 -19
  26. package/harness/packages/core/src/storage/sqlite.js +595 -690
  27. package/harness/packages/core/src/tunnel.js +9 -147
  28. package/harness/packages/core/test/home.test.js +2 -35
  29. package/harness/packages/core/test/procs.test.js +3 -64
  30. package/harness/packages/core/test/storage.test.js +1 -12
  31. package/harness/packages/server/public/app.js +2 -19
  32. package/harness/packages/server/public/appearance.js +2 -56
  33. package/harness/packages/server/public/boot.js +2 -2
  34. package/harness/packages/server/public/cards.js +2 -21
  35. package/harness/packages/server/public/chat.js +0 -16
  36. package/harness/packages/server/public/chatFormat.js +0 -11
  37. package/harness/packages/server/public/chrome.js +2 -5
  38. package/harness/packages/server/public/commands.js +1 -2
  39. package/harness/packages/server/public/explorer.js +7 -10
  40. package/harness/packages/server/public/index.html +1228 -1326
  41. package/harness/packages/server/public/landing/boot.js +2 -3
  42. package/harness/packages/server/public/landing/index.html +3 -367
  43. package/harness/packages/server/public/settings.js +1255 -1322
  44. package/harness/packages/server/public/settingsNav.js +0 -1
  45. package/harness/packages/server/public/styles.css +2341 -2867
  46. package/harness/packages/server/public/system.js +356 -495
  47. package/harness/packages/server/public/voice.js +6 -22
  48. package/harness/packages/server/src/auth.js +4 -15
  49. package/harness/packages/server/src/cli.js +1 -1
  50. package/harness/packages/server/src/lib/agents.js +13 -27
  51. package/harness/packages/server/src/lib/appearance.js +3 -22
  52. package/harness/packages/server/src/lib/channels/telegram/commands.js +1 -1
  53. package/harness/packages/server/src/lib/definitions.js +40 -0
  54. package/harness/packages/server/src/lib/skills.js +14 -21
  55. package/harness/packages/server/src/lib/ttsSettings.js +2 -2
  56. package/harness/packages/server/src/lib/workspace.js +9 -11
  57. package/harness/packages/server/src/routes/chat.js +0 -43
  58. package/harness/packages/server/src/routes/preview.js +5 -22
  59. package/harness/packages/server/src/routes/procs.js +2 -4
  60. package/harness/packages/server/src/routes/tts.js +1 -1
  61. package/harness/packages/server/src/server.js +1 -5
  62. package/harness/packages/server/test/appearance-route.test.js +3 -3
  63. package/harness/packages/server/test/appearance.test.js +12 -38
  64. package/harness/packages/server/test/chatFormat.test.js +0 -8
  65. package/harness/packages/server/test/consoleTabs.test.js +4 -4
  66. package/harness/packages/server/test/ops.test.js +8 -56
  67. package/harness/packages/server/test/preview.test.js +2 -42
  68. package/harness/packages/server/test/security.test.js +194 -370
  69. package/harness/packages/server/test/settingsNav.test.js +2 -2
  70. package/harness/packages/server/test/tts.test.js +4 -4
  71. package/harness/tamnora.example.json +1 -1
  72. package/package.json +2 -2
  73. package/harness/packages/cli/src/commands/eval.js +0 -186
  74. package/harness/packages/core/src/eval/dataset.js +0 -47
  75. package/harness/packages/core/src/eval/judge.js +0 -39
  76. package/harness/packages/core/src/eval/report.js +0 -48
  77. package/harness/packages/core/src/eval/runTask.js +0 -62
  78. package/harness/packages/core/src/eval/runner.js +0 -153
  79. package/harness/packages/core/src/eval/scorers.js +0 -217
  80. package/harness/packages/core/src/eval/task.js +0 -59
  81. package/harness/packages/core/src/procs/detect.js +0 -149
  82. package/harness/packages/core/test/eval.test.js +0 -144
  83. package/harness/packages/server/src/lib/advisor.js +0 -198
  84. package/harness/packages/server/src/routes/advisor.js +0 -79
  85. package/harness/packages/server/src/routes/eval.js +0 -100
  86. package/harness/packages/server/test/advisor.test.js +0 -249
  87. package/harness/scripts/example-dataset.jsonl +0 -4
@@ -1,12 +1,11 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { readFileSync, statSync } from 'node:fs';
3
- import { bridgeStatePath } from './config/home.js';
4
- import { pidAlive, killPidTree } from './pid.js';
2
+ import { killPidTree } from './pid.js';
5
3
 
6
4
  /**
7
- * Acceso remoto. Proveedores:
8
- * - tailscale: VPN mesh privada + `tailscale serve` (recomendado; sin exposicion).
9
- * - tunnelmole: tunel publico gratis (URL aleatoria); solo para `preview`.
5
+ * Acceso remoto por tailscale: VPN mesh privada + `tailscale serve`, que
6
+ * publica HTTPS dentro del tailnet reenviando a loopback. No hay proveedor
7
+ * publico: lo unico que sale de la maquina es lo que ya esta en la red privada
8
+ * del usuario.
10
9
  */
11
10
 
12
11
  function killTree(child) {
@@ -14,147 +13,11 @@ function killTree(child) {
14
13
  killPidTree(child.pid);
15
14
  }
16
15
 
17
- /**
18
- * Estado del bridge (escrito por `tamnora bridge`), si el supervisor sigue
19
- * vivo. Lo consulta el server para saber que hay un tunel publico activo y
20
- * ajustar el modelo de confianza (guards locales, cookie Secure).
21
- * Cache de 1 s + invalidacion por mtime del archivo: se lee en cada request
22
- * sensible, pero un bridge.json nuevo (o borrado) se ve al instante.
23
- */
24
- let tunnelCache = { key: null, mtime: null, at: 0, state: null };
25
- export function tunnelState() {
26
- let statePath;
27
- try {
28
- statePath = bridgeStatePath();
29
- } catch {
30
- return null;
31
- }
32
- const now = Date.now();
33
- let mtime = null;
34
- try {
35
- mtime = statSync(statePath).mtimeMs;
36
- } catch {
37
- tunnelCache = { key: statePath, mtime: null, at: now, state: null };
38
- return null;
39
- }
40
- if (tunnelCache.key === statePath && tunnelCache.mtime === mtime && now - tunnelCache.at < 1000) {
41
- return tunnelCache.state;
42
- }
43
- let state = null;
44
- try {
45
- const parsed = JSON.parse(readFileSync(statePath, 'utf8'));
46
- // Tailscale es una red privada que entra por loopback: no es un tunel
47
- // publico, asi que no debe degradar los guards locales (`localOk`).
48
- if (parsed?.provider !== 'tailscale' && parsed?.pid && pidAlive(parsed.pid)) state = parsed;
49
- } catch {
50
- /* sin bridge */
51
- }
52
- tunnelCache = { key: statePath, mtime, at: now, state };
53
- return state;
54
- }
55
-
56
- /** True si hay un tunel publico activo hacia este server. */
57
- export function tunnelActive() {
58
- return Boolean(tunnelState());
59
- }
60
-
61
- export function parseTunnelmoleUrls(text) {
62
- const urls = [...new Set(String(text).match(/https?:\/\/[a-z0-9-]+\.tunnelmole\.net/gi) || [])];
63
- return {
64
- https: urls.find((u) => u.startsWith('https')) || '',
65
- http: urls.find((u) => u.startsWith('http:')) || '',
66
- };
67
- }
68
-
69
- function spawnTunnel(bin, args, opts = {}) {
70
- const log = opts.log || (() => {});
71
- const parse = opts.parse || (() => ({ https: '', http: '' }));
72
- const timeoutMs = opts.timeoutMs || 90000;
73
- const label = opts.label || bin;
74
- const useShell = process.platform === 'win32';
75
- return new Promise((resolve, reject) => {
76
- let child;
77
- try {
78
- // En Windows `npx` es un shim .cmd y exige shell; ahi se manda un unico
79
- // comando (ver shellCommandLine): con array de args Node concatena sin
80
- // escapar y emite el warning DEP0190.
81
- const [cmd, cmdArgs] = useShell ? [shellCommandLine(bin, args), []] : [bin, args];
82
- child = (opts.spawnImpl || spawn)(cmd, cmdArgs, {
83
- stdio: ['ignore', 'pipe', 'pipe'],
84
- windowsHide: true,
85
- shell: useShell,
86
- detached: !useShell,
87
- });
88
- } catch (err) {
89
- reject(new Error(`no se pudo iniciar ${label}: ${err.message}`));
90
- return;
91
- }
92
- let buf = '';
93
- let settled = false;
94
- const timer = setTimeout(() => {
95
- if (settled) return;
96
- settled = true;
97
- killTree(child);
98
- reject(new Error(`${label} no devolvio URL (${Math.round(timeoutMs / 1000)} s).`));
99
- }, timeoutMs);
100
- const feed = (data) => {
101
- buf += String(data);
102
- if (opts.verbose) process.stdout.write(String(data));
103
- const urls = parse(buf);
104
- const url = urls.https || urls.http;
105
- if (url) {
106
- if (settled) return;
107
- settled = true;
108
- clearTimeout(timer);
109
- log(`${label}: ${url} -> localhost:${opts.port}`);
110
- resolve({ url, provider: opts.provider, pid: child.pid, child, stop: () => killTree(child) });
111
- }
112
- };
113
- child.stdout.on('data', feed);
114
- child.stderr.on('data', feed);
115
- child.on('error', (err) => {
116
- if (settled) return;
117
- settled = true;
118
- clearTimeout(timer);
119
- reject(new Error(`${label}: ${err.message}`));
120
- });
121
- child.on('exit', (code) => {
122
- if (settled) return;
123
- settled = true;
124
- clearTimeout(timer);
125
- reject(new Error(`${label} termino (codigo ${code})${buf ? `: ${buf.slice(0, 200).trim()}` : ''}`));
126
- });
127
- });
128
- }
129
-
130
- // Tunel publico para el feature `preview` (no lo usa `bridge`).
131
- export function startTunnelmole(port, opts = {}) {
132
- return spawnTunnel('npx', ['--yes', 'tunnelmole', String(port)], {
133
- ...opts,
134
- port,
135
- provider: 'tunnelmole',
136
- label: 'tunnelmole',
137
- parse: parseTunnelmoleUrls,
138
- });
139
- }
140
-
141
16
  /**
142
17
  * Tailscale: VPN mesh (WireGuard). `tailscale serve` publica HTTPS dentro del
143
- * tailnet reenviando a loopback, asi que no hay exposicion publica y las
144
- * peticiones siguen siendo "locales" para el server. Proveedor recomendado.
18
+ * tailnet reenviando a loopback, asi que las peticiones siguen siendo "locales"
19
+ * para el server.
145
20
  */
146
- // Con `shell: true` Node concatena los args sin escapar (warning DEP0190): se
147
- // arma un unico comando con args validados contra metacaracteres del shell.
148
- const SAFE_SHELL_ARG = /^[^&|<>^%!"\r\n]+$/;
149
-
150
- function shellCommandLine(bin, args) {
151
- for (const arg of args) {
152
- if (!SAFE_SHELL_ARG.test(String(arg))) {
153
- throw new Error(`argumento no permitido para ${bin}: "${arg}"`);
154
- }
155
- }
156
- return [bin, ...args].join(' ');
157
- }
158
21
 
159
22
  function runTailscale(args, opts = {}) {
160
23
  const timeoutMs = opts.timeoutMs || 15000;
@@ -312,12 +175,11 @@ export function stopTailscaleServe(opts = {}) {
312
175
  * `opts.preview` usa el `serve` de un puerto HTTPS propio (8443) para no pisar el
313
176
  * bridge de la consola en 443.
314
177
  */
315
- export async function startTunnel(port, provider = 'tunnelmole', opts = {}) {
316
- const p = String(provider || 'tunnelmole').toLowerCase();
178
+ export async function startTunnel(port, provider = 'tailscale', opts = {}) {
179
+ const p = String(provider || 'tailscale').toLowerCase();
317
180
  if (p === 'none' || p === 'off' || p === '') return { url: '', provider: 'none', pid: 0, stop: () => {} };
318
181
  if (p === 'tailscale' || p === 'ts') {
319
182
  return opts.preview ? startTailscalePreview(port, opts) : startTailscaleServe(port, opts);
320
183
  }
321
- if (p === 'tunnelmole' || p === 'tmole') return startTunnelmole(port, opts);
322
184
  throw new Error(`proveedor de tunel no soportado: ${provider}`);
323
185
  }
@@ -1,18 +1,9 @@
1
1
  import { test, afterEach } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { mkdtemp, mkdir, writeFile, readFile } from 'node:fs/promises';
3
+ import { mkdtemp, readFile } from 'node:fs/promises';
4
4
  import { tmpdir } from 'node:os';
5
5
  import path from 'node:path';
6
- import {
7
- setHome,
8
- homeDir,
9
- homePath,
10
- databaseFile,
11
- ensureApp,
12
- readApp,
13
- writeApp,
14
- migrateLegacyDb,
15
- } from '../src/index.js';
6
+ import { setHome, homeDir, homePath, ensureApp, readApp, writeApp } from '../src/index.js';
16
7
 
17
8
  const prevHome = process.env.TAMNORA_HOME;
18
9
 
@@ -47,27 +38,3 @@ test('ensureApp creates app.json with a secret and roundtrips', async () => {
47
38
  writeApp(raw);
48
39
  assert.equal(readApp().port, 4599);
49
40
  });
50
-
51
- test('readApp migra los proveedores retirados (tunnelmole/relay) a tailscale', async () => {
52
- const dir = await mkdtemp(path.join(tmpdir(), 'tamnora-home-'));
53
- setHome(dir);
54
- for (const provider of ['tunnelmole', 'relay']) {
55
- const app = ensureApp();
56
- app.tunnel = { ...app.tunnel, provider };
57
- writeApp(app);
58
- assert.equal(readApp().tunnel.provider, 'tailscale', provider);
59
- }
60
- });
61
-
62
- test('migrateLegacyDb copies the legacy project database to the global home', async () => {
63
- const home = await mkdtemp(path.join(tmpdir(), 'tamnora-home-'));
64
- const cwd = await mkdtemp(path.join(tmpdir(), 'tamnora-cwd-'));
65
- setHome(home);
66
-
67
- await mkdir(path.join(cwd, '.tamnora'), { recursive: true });
68
- await writeFile(path.join(cwd, '.tamnora', 'tamnora.db'), 'legacy-db');
69
-
70
- const target = migrateLegacyDb(cwd);
71
- assert.equal(target, databaseFile());
72
- assert.equal(await readFile(target, 'utf8'), 'legacy-db');
73
- });
@@ -5,7 +5,7 @@ import net from 'node:net';
5
5
  import { mkdtemp, mkdir, writeFile } from 'node:fs/promises';
6
6
  import { tmpdir } from 'node:os';
7
7
  import path from 'node:path';
8
- import { detectProcs, loadProjectProcs, packageManager, DEFAULT_PROCESS_ALLOW, PROCS_FILE } from '../src/procs/detect.js';
8
+ import { loadProjectProcs, PROCS_FILE } from '../src/procs/project.js';
9
9
  import { killPort, portInfo } from '../src/procs/kill.js';
10
10
 
11
11
  async function waitForPort(port, attempts = 50) {
@@ -55,49 +55,9 @@ test('killPort terminates the process listening on a port', async (t) => {
55
55
  assert.ok(child.exitCode !== null || child.signalCode, 'el hijo debería haber terminado');
56
56
  });
57
57
 
58
- test('detectProcs finds node dev scripts with the right package manager', async () => {
58
+ test('loadProjectProcs devuelve [] si el proyecto no declara procesos', async () => {
59
59
  const dir = await mkdtemp(path.join(tmpdir(), 'tamnora-proc-'));
60
- await writeFile(path.join(dir, 'package.json'), JSON.stringify({ scripts: { dev: 'vite', test: 'x' } }));
61
- await writeFile(path.join(dir, 'pnpm-lock.yaml'), '');
62
- assert.equal(packageManager(dir), 'pnpm');
63
-
64
- const chips = detectProcs(dir);
65
- const dev = chips.find((c) => c.command === 'pnpm' && c.args.includes('dev'));
66
- assert.ok(dev);
67
- assert.equal(dev.cwd, dir);
68
- assert.ok(DEFAULT_PROCESS_ALLOW.includes('pnpm'));
69
- });
70
-
71
- test('detectProcs falls back to npm and detects php entrypoints', async () => {
72
- const nodeDir = await mkdtemp(path.join(tmpdir(), 'tamnora-proc-'));
73
- await writeFile(path.join(nodeDir, 'package.json'), JSON.stringify({ scripts: { start: 'node app.js' } }));
74
- assert.equal(packageManager(nodeDir), 'npm');
75
- assert.ok(detectProcs(nodeDir).some((c) => c.command === 'npm' && c.args.includes('start')));
76
-
77
- const phpDir = await mkdtemp(path.join(tmpdir(), 'tamnora-proc-'));
78
- await mkdir(path.join(phpDir, 'public'));
79
- await writeFile(path.join(phpDir, 'public', 'index.php'), '<?php');
80
- const chips = detectProcs(phpDir);
81
- const php = chips.find((c) => c.command === 'php');
82
- assert.ok(php);
83
- assert.equal(php.args[0], '-S');
84
- });
85
-
86
- test('detectProcs offers a static server for plain HTML sites', async () => {
87
- const dir = await mkdtemp(path.join(tmpdir(), 'tamnora-proc-'));
88
- await writeFile(path.join(dir, 'index.html'), '<h1>hola</h1>');
89
- const chips = detectProcs(dir);
90
- const staticChip = chips.find((c) => c.port && c.args.some((a) => a.endsWith('static-server.mjs')));
91
- assert.ok(staticChip);
92
- assert.equal(staticChip.cwd, dir);
93
- assert.ok(staticChip.args.includes(dir));
94
- });
95
-
96
- test('detectProcs does not add the static chip when there is a dev script', async () => {
97
- const dir = await mkdtemp(path.join(tmpdir(), 'tamnora-proc-'));
98
- await writeFile(path.join(dir, 'index.html'), '<h1>hola</h1>');
99
- await writeFile(path.join(dir, 'package.json'), JSON.stringify({ scripts: { dev: 'vite' } }));
100
- assert.ok(!detectProcs(dir).some((c) => c.args.some((a) => String(a).endsWith('static-server.mjs'))));
60
+ assert.deepEqual(loadProjectProcs(dir), []);
101
61
  });
102
62
 
103
63
  test('loadProjectProcs reads .tamnora/procs.json and confines dir to the root', async () => {
@@ -124,27 +84,6 @@ test('loadProjectProcs reads .tamnora/procs.json and confines dir to the root',
124
84
  assert.ok(chips.every((c) => c.source === 'project'));
125
85
  });
126
86
 
127
- test('detectProcs puts project chips first and dedupes against auto-detection', async () => {
128
- const dir = await mkdtemp(path.join(tmpdir(), 'tamnora-proc-'));
129
- await mkdir(path.join(dir, '.tamnora'), { recursive: true });
130
- await writeFile(path.join(dir, 'package.json'), JSON.stringify({ scripts: { dev: 'vite' } }));
131
- await writeFile(
132
- path.join(dir, PROCS_FILE),
133
- JSON.stringify({
134
- processes: [
135
- { label: 'backend', command: 'node', args: ['server.js'], port: 3000 },
136
- { label: 'dev repetido', command: 'npm', args: ['run', 'dev'] },
137
- ],
138
- })
139
- );
140
-
141
- const chips = detectProcs(dir);
142
- assert.equal(chips[0].source, 'project');
143
- assert.equal(chips[0].command, 'node');
144
- const devChips = chips.filter((c) => c.command === 'npm' && c.args.join(' ') === 'run dev');
145
- assert.equal(devChips.length, 1, 'el chip manual gana al autodetectado');
146
- });
147
-
148
87
  test('portInfo reports whether a port is listening', async (t) => {
149
88
  const free = await portInfo(47599);
150
89
  assert.equal(free.listening, false);
@@ -2,7 +2,7 @@ import { test } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { Store } from '../src/storage/sqlite.js';
4
4
 
5
- test('Store persists sessions, messages and runs', () => {
5
+ test('Store persists sessions and messages', () => {
6
6
  const store = new Store(':memory:');
7
7
 
8
8
  const sessionId = store.createSession({ title: 'test', provider: 'openrouter', model: 'm' });
@@ -27,17 +27,6 @@ test('Store persists sessions, messages and runs', () => {
27
27
 
28
28
  assert.equal(store.latestSession().id, sessionId);
29
29
 
30
- const runId = store.createRun({ dataset: 'd.jsonl', config: { models: ['m'] } });
31
- store.addRunResult({ runId, taskId: 't1', model: 'm', passed: true, output: 'ok' });
32
- store.finishRun(runId, { summary: { total: 1 } });
33
-
34
- const run = store.getRun(runId);
35
- assert.equal(run.status, 'done');
36
- assert.equal(run.summary.total, 1);
37
- const results = store.getRunResults(runId);
38
- assert.equal(results.length, 1);
39
- assert.equal(results[0].passed, 1);
40
-
41
30
  store.close();
42
31
  });
43
32
 
@@ -92,7 +92,7 @@ import {
92
92
  PREVIEW_DEVICE_KEY,
93
93
  PREVIEW_URL_KEY,
94
94
  applyTreeFilter,
95
- detectProcs,
95
+ loadProcChips,
96
96
  explorerFilter,
97
97
  explorerMode,
98
98
  filePreviewState,
@@ -165,7 +165,6 @@ import {
165
165
  skillForm,
166
166
  skillModal,
167
167
  submitAgent,
168
- submitAsesor,
169
168
  submitChannel,
170
169
  submitMcp,
171
170
  submitMemory,
@@ -174,7 +173,6 @@ import {
174
173
  submitSchedule,
175
174
  submitSkill,
176
175
  syncMcpFormFields,
177
- testAsesor,
178
176
  testChannel,
179
177
  testProvider,
180
178
  vozPitchEl,
@@ -184,15 +182,11 @@ import {
184
182
  import {
185
183
  brandVerEl,
186
184
  dismissUpdate,
187
- evalRunBtn,
188
- evalTasks,
189
185
  followEvents,
190
186
  forceUpdateCheck,
191
187
  reconcile,
192
- refreshRuns,
193
188
  refreshUpdate,
194
189
  renderAlertSignals,
195
- runEval,
196
190
  runUpdate,
197
191
  watchLifecycle,
198
192
  } from './system.js';
@@ -648,8 +642,6 @@ $('#voz-save')?.addEventListener('click', submitVoz);
648
642
  $('#voz-test')?.addEventListener('click', testVoz);
649
643
  vozRateEl?.addEventListener('input', renderVozSliders);
650
644
  vozPitchEl?.addEventListener('input', renderVozSliders);
651
- $('#asesor-save')?.addEventListener('click', submitAsesor);
652
- $('#asesor-test')?.addEventListener('click', testAsesor);
653
645
  $('#mcp-close').addEventListener('click', closeMcpForm);
654
646
  $('#mf-cancel').addEventListener('click', closeMcpForm);
655
647
  $('#mf-transport').addEventListener('change', syncMcpFormFields);
@@ -693,15 +685,6 @@ $('#asesor-test')?.addEventListener('click', testAsesor);
693
685
  });
694
686
  }
695
687
 
696
- evalTasks.value = [
697
- '{"id":"math","input":"¿Cuánto es 17 * 23? Respondé solo el número.","expect":{"equals":"391"}}',
698
- '{"id":"regex","input":"Escribí una oración que contenga un número.","expect":{"regex":"\\\\d"}}',
699
- ].join('\n');
700
-
701
- evalRunBtn.addEventListener('click', runEval);
702
-
703
- $('#runs-refresh').addEventListener('click', refreshRuns);
704
-
705
688
  $('#file-preview-toggle')?.addEventListener('click', async () => {
706
689
  const toggle = $('#file-preview-toggle');
707
690
  const code = $('#file-code');
@@ -796,7 +779,7 @@ $('#preview-device')?.addEventListener('change', (event) => storage.set(PREVIEW_
796
779
  syncPreviewTunnel({ load: true });
797
780
  })();
798
781
 
799
- $('#procs-detect')?.addEventListener('click', detectProcs);
782
+ $('#procs-detect')?.addEventListener('click', loadProcChips);
800
783
 
801
784
  gitFilterEl?.addEventListener('input', () => {
802
785
  if (explorerMode === 'git' && gitEntries.length) renderGitList();
@@ -4,22 +4,9 @@
4
4
 
5
5
  export const THEMES = [
6
6
  { id: 'paper', label: 'Papel', scheme: 'light', metaColor: '#f5efe2' },
7
- { id: 'dark', label: 'Oscuro', scheme: 'dark', metaColor: '#101107' },
8
- { id: 'midnight', label: 'Medianoche', scheme: 'dark', metaColor: '#0b1220' },
9
- { id: 'nord', label: 'Nord', scheme: 'dark', metaColor: '#2e3440' },
10
- { id: 'dracula', label: 'Dracula', scheme: 'dark', metaColor: '#282a36' },
11
- { id: 'solarized', label: 'Solarized', scheme: 'light', metaColor: '#fdf6e3' },
12
- { id: 'solarized-dark', label: 'Solarized oscuro', scheme: 'dark', metaColor: '#002b36' },
13
- { id: 'bosque', label: 'Bosque', scheme: 'light', metaColor: '#f2f4ec' },
14
- { id: 'vino', label: 'Vino', scheme: 'light', metaColor: '#f7eeee' },
15
- { id: 'arena', label: 'Arena', scheme: 'light', metaColor: '#f6efdd' },
16
- { id: 'hielo', label: 'Hielo', scheme: 'light', metaColor: '#edf3f7' },
17
- { id: 'vercel', label: 'Vercel', scheme: 'dark', metaColor: '#000000' },
18
- { id: 'dark-plus', label: 'Dark+', scheme: 'dark', metaColor: '#1e1e1e' },
19
7
  { id: 'github', label: 'GitHub', scheme: 'light', metaColor: '#ffffff' },
20
- { id: 'github-dark', label: 'GitHub oscuro', scheme: 'dark', metaColor: '#0d1117' },
8
+ { id: 'dark', label: 'Oscuro', scheme: 'dark', metaColor: '#101107' },
21
9
  { id: 'mocha', label: 'Mocha', scheme: 'dark', metaColor: '#211912' },
22
- { id: 'ebano', label: 'Ébano', scheme: 'dark', metaColor: '#14191f' },
23
10
  ];
24
11
 
25
12
  export const FONT_PRESETS = [
@@ -39,46 +26,6 @@ export const FONT_PRESETS = [
39
26
  mono: 'ui-monospace, "Cascadia Code", "SF Mono", Consolas, monospace',
40
27
  google: null,
41
28
  },
42
- {
43
- id: 'plex',
44
- label: 'IBM Plex · Sans / Serif / Mono',
45
- sans: '"IBM Plex Sans", system-ui, "Segoe UI", Arial, sans-serif',
46
- serif: '"IBM Plex Serif", Georgia, "Times New Roman", serif',
47
- mono: '"IBM Plex Mono", ui-monospace, Consolas, monospace',
48
- google: 'family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Serif:wght@400;600',
49
- },
50
- {
51
- id: 'inter',
52
- label: 'Inter · Inter / Newsreader / JetBrains Mono',
53
- sans: 'Inter, system-ui, "Segoe UI", Arial, sans-serif',
54
- serif: 'Newsreader, Georgia, "Times New Roman", serif',
55
- mono: '"JetBrains Mono", ui-monospace, Consolas, monospace',
56
- google: 'family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Newsreader:wght@400;600',
57
- },
58
- {
59
- id: 'jetbrains',
60
- label: 'JetBrains Mono · código',
61
- sans: '"JetBrains Mono", ui-monospace, "Cascadia Code", Consolas, monospace',
62
- serif: 'Georgia, "Times New Roman", serif',
63
- mono: '"JetBrains Mono", ui-monospace, "Cascadia Code", Consolas, monospace',
64
- google: 'family=JetBrains+Mono:wght@400;500;700',
65
- },
66
- {
67
- id: 'fira',
68
- label: 'Fira Code · código',
69
- sans: '"Fira Code", ui-monospace, "Cascadia Code", Consolas, monospace',
70
- serif: 'Georgia, "Times New Roman", serif',
71
- mono: '"Fira Code", ui-monospace, "Cascadia Code", Consolas, monospace',
72
- google: 'family=Fira+Code:wght@400;500;600;700',
73
- },
74
- {
75
- id: 'source',
76
- label: 'Source Code Pro · código',
77
- sans: '"Source Code Pro", ui-monospace, "Cascadia Code", Consolas, monospace',
78
- serif: 'Georgia, "Times New Roman", serif',
79
- mono: '"Source Code Pro", ui-monospace, "Cascadia Code", Consolas, monospace',
80
- google: 'family=Source+Code+Pro:wght@400;500;600;700',
81
- },
82
29
  ];
83
30
 
84
31
  export const DEFAULTS = { theme: 'paper', font: 'clasico', chatFont: 14.5, scale: 1 };
@@ -116,8 +63,7 @@ export function fontIds() {
116
63
  }
117
64
 
118
65
  export function resolveTheme(id) {
119
- const legacy = id === 'light' ? 'paper' : id;
120
- return THEMES.some((t) => t.id === legacy) ? legacy : DEFAULTS.theme;
66
+ return THEMES.some((t) => t.id === id) ? id : DEFAULTS.theme;
121
67
  }
122
68
 
123
69
  export function resolveFont(id) {
@@ -1,7 +1,7 @@
1
1
  (function () {
2
2
  var root = document.documentElement;
3
- var THEMES = ['paper', 'dark', 'midnight', 'nord', 'dracula', 'solarized', 'solarized-dark', 'bosque', 'vino', 'arena', 'hielo', 'vercel', 'dark-plus', 'github', 'github-dark', 'mocha', 'ebano'];
4
- var DARK = ['dark', 'midnight', 'nord', 'dracula', 'solarized-dark', 'vercel', 'dark-plus', 'github-dark', 'mocha', 'ebano'];
3
+ var THEMES = ['paper', 'github', 'dark', 'mocha'];
4
+ var DARK = ['dark', 'mocha'];
5
5
  var appearance = null;
6
6
  try {
7
7
  appearance = JSON.parse(localStorage.getItem('tamnora-console-appearance') || 'null');
@@ -1,10 +1,10 @@
1
1
  // Tarjetas de la consola: las que dibujan una herramienta, un permiso, una
2
- // pregunta, un plan o el comentario del asesor.
2
+ // pregunta o un plan.
3
3
 
4
4
  import { api, button, el, icon, state } from './core.js';
5
5
  import { toast } from './commands.js';
6
6
  import { addMessage, chatInput, paneOf, scrollMessages, sendMessage } from './chat.js';
7
- import { ADVISOR_KINDS, advisorMeta, isFailedToolText, isReadOnlyTool, screenshotName, toolSummary } from './chatFormat.js';
7
+ import { isFailedToolText, isReadOnlyTool, screenshotName, toolSummary } from './chatFormat.js';
8
8
  import { renderMarkdown } from './markdown.js';
9
9
  import { summarizeArgs } from './reconcile.js';
10
10
  import { yesInput } from './chrome.js';
@@ -409,25 +409,6 @@ export function addPlanCard(container, { title, steps } = {}) {
409
409
  return card;
410
410
  }
411
411
 
412
- export function addAdvisorCard(container, { text, kind } = {}) {
413
- const clean = String(text ?? '').trim();
414
- if (!clean || !container) return null;
415
- const exists = [...container.querySelectorAll('.advisor-text')].some((node) => node.textContent === clean);
416
- if (exists) return null;
417
- const meta = advisorMeta(kind);
418
- const klass = ADVISOR_KINDS[kind] ? ` advisor-${kind}` : '';
419
- const card = el('div', `advisor-card${klass}`);
420
- const head = el('div', 'advisor-head');
421
- head.append(
422
- el('span', 'advisor-glyph', meta.glyph),
423
- el('span', 'advisor-title', 'asesor'),
424
- el('span', `advisor-badge advisor-badge-${kind || 'advice'}`, meta.label)
425
- );
426
- card.append(head, el('div', 'advisor-text', clean));
427
- container.appendChild(card);
428
- return card;
429
- }
430
-
431
412
  /**
432
413
  * Marca del punto de compactación: una línea que corta el historial y, debajo,
433
414
  * el resumen que pasa a ser el contexto del modelo. Lo de arriba sigue visible
@@ -13,9 +13,7 @@ import {
13
13
  } from './chrome.js';
14
14
  import { hideSlashMenu, notify, onChatInputChanged, runCommand, toast } from './commands.js';
15
15
  export {
16
- ADVISOR_KINDS,
17
16
  READ_ONLY_TOOLS,
18
- advisorMeta,
19
17
  basename,
20
18
  formatDuration,
21
19
  isFailedToolText,
@@ -53,7 +51,6 @@ import { setStatus } from './settings.js';
53
51
  import { reconcile, renderAlertSignals } from './system.js';
54
52
  import { narrar, reproducirVoz } from './voice.js';
55
53
  import {
56
- addAdvisorCard,
57
54
  addChoiceCard,
58
55
  addCompactCard,
59
56
  addPermissionCard,
@@ -69,7 +66,6 @@ import {
69
66
  toolCardIndex,} from './cards.js';
70
67
  export {
71
68
  PLAN_MARKS,
72
- addAdvisorCard,
73
69
  addChoiceCard,
74
70
  addCompactCard,
75
71
  addPermissionCard,
@@ -427,13 +423,6 @@ export function renderStored(message, pane) {
427
423
  addCompactCard(pane, { text: typeof message.content === 'string' ? message.content : '' });
428
424
  return;
429
425
  }
430
- if (message.role === 'assistant' && message.name === 'advisor') {
431
- addAdvisorCard(pane, {
432
- text: typeof message.content === 'string' ? message.content : '',
433
- kind: message.mode,
434
- });
435
- return;
436
- }
437
426
  if (message.role === 'user') {
438
427
  const bubble = addMessage('user', userText(message.content), pane);
439
428
  if (message.id) {
@@ -775,11 +764,6 @@ export function addContinueButton(turn, data) {
775
764
  export function handleChatEvent(event, data, turn) {
776
765
  if (handleAgentEvent(event, data, turn)) return;
777
766
  narrar(event, data, turn);
778
- if (event === 'advice') {
779
- addAdvisorCard(turn.pane, { text: data?.text, kind: data?.kind });
780
- scrollMessages(turn.pane);
781
- return;
782
- }
783
767
  if (event === 'agent_start') {
784
768
  turn.agents.set(data.agentId, addAgentCard(turn.assistant(), data));
785
769
  } else if (event === 'agent_end') {
@@ -131,17 +131,6 @@ export function userImages(content) {
131
131
  .filter(Boolean);
132
132
  }
133
133
 
134
- export const ADVISOR_KINDS = {
135
- code: { label: 'Cuidado!', glyph: '⚠' },
136
- explain: { label: 'Importante!', glyph: 'ℹ' },
137
- advice: { label: 'Consejo', glyph: '✦' },
138
- summary: { label: 'Resumen', glyph: '✎' },
139
- };
140
-
141
- export function advisorMeta(kind) {
142
- return ADVISOR_KINDS[kind] || { label: 'Asesor', glyph: '✦' };
143
- }
144
-
145
134
  /** Ultimo segmento de un path (sin separadores finales). */
146
135
  export function basename(p) {
147
136
  const text = String(p ?? '').replace(/[\\/]+$/, '');
@@ -17,7 +17,6 @@ import { activateSession, registerSession } from './sessions.js';
17
17
  import {
18
18
  activateAjustesPanel,
19
19
  refreshAgents,
20
- refreshAsesor,
21
20
  refreshChannels,
22
21
  refreshLimits,
23
22
  refreshMcp,
@@ -31,7 +30,6 @@ import {
31
30
  setAjustesLevel,
32
31
  setStatus,
33
32
  } from './settings.js';
34
- import { refreshRuns } from './system.js';
35
33
  import { refreshTranscribe, refreshVoz, setHandsFree } from './voice.js';
36
34
 
37
35
  export function setCwd(value) {
@@ -49,7 +47,7 @@ export const moreMenu = $('#more-menu');
49
47
  export const topbarSection = $('#topbar-section');
50
48
  export const backChat = $('#back-chat');
51
49
 
52
- export const TAB_LABELS = { chat: 'chat', providers: 'proveedores', ajustes: 'ajustes', eval: 'eval', runs: 'runs' };
50
+ export const TAB_LABELS = { chat: 'chat', providers: 'proveedores', ajustes: 'ajustes' };
53
51
 
54
52
  export function hideMoreMenu() {
55
53
  if (moreMenu.hidden) return;
@@ -68,9 +66,8 @@ export function activateTab(name) {
68
66
  moreTab.classList.toggle('active', name !== 'chat');
69
67
  document.querySelectorAll('.view').forEach((v) => v.classList.toggle('active', v.id === `view-${name}`));
70
68
  hideMoreMenu();
71
- if (name === 'runs') refreshRuns();
72
69
  if (name === 'providers') refreshProviders();
73
- if (name === 'ajustes') { setAjustesLevel('list'); refreshMcp(); refreshMcpCatalog(); refreshSkills(); refreshSkillCatalog(); refreshAgents(); refreshMemory(); refreshSchedules(); refreshChannels(); refreshLimits(); refreshVoz(); refreshAsesor(); refreshTranscribe(); refreshProfile(); }
70
+ if (name === 'ajustes') { setAjustesLevel('list'); refreshMcp(); refreshMcpCatalog(); refreshSkills(); refreshSkillCatalog(); refreshAgents(); refreshMemory(); refreshSchedules(); refreshChannels(); refreshLimits(); refreshVoz(); refreshTranscribe(); refreshProfile(); }
74
71
  }
75
72
 
76
73
  /* --- settings modal (modo / proveedor / modelo / auto) -------------------- */
@@ -218,8 +218,7 @@ export async function runCommand(name, arg) {
218
218
  notify(`Carpeta de trabajo: \`${cwd}\``);
219
219
  return { handled: true };
220
220
  }
221
- case 'portkill':
222
- case 'kill': {
221
+ case 'portkill': {
223
222
  const port = Number(arg);
224
223
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
225
224
  notify('Uso: `/portkill <puerto>`, por ejemplo `/portkill 3000`.', 'notice err');