@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
@@ -33,11 +33,10 @@ test('los registros de tema y fuente son consistentes', () => {
33
33
  assert.equal(sistema.google, null, 'sistema no usa webfont');
34
34
  });
35
35
 
36
- test('resolveTheme mapea legacy light -> paper y cae al default', () => {
37
- assert.equal(resolveTheme('light'), 'paper');
38
- assert.equal(resolveTheme('nord'), 'nord');
36
+ test('resolveTheme cae al default y resolveFont tambien', () => {
37
+ assert.equal(resolveTheme('github'), 'github');
39
38
  assert.equal(resolveTheme('no-existe'), DEFAULTS.theme);
40
- assert.equal(resolveFont('plex'), 'plex');
39
+ assert.equal(resolveFont('sistema'), 'sistema');
41
40
  assert.equal(resolveFont('no-existe'), DEFAULTS.font);
42
41
  });
43
42
 
@@ -53,9 +52,9 @@ test('nextTheme recorre todos los temas y vuelve al inicio', () => {
53
52
  });
54
53
 
55
54
  test('normalizeAppearance acota tamaños y resuelve ids', () => {
56
- const a = normalizeAppearance({ theme: 'dracula', font: 'inter', chatFont: 99, scale: 5 });
57
- assert.equal(a.theme, 'dracula');
58
- assert.equal(a.font, 'inter');
55
+ const a = normalizeAppearance({ theme: 'mocha', font: 'sistema', chatFont: 99, scale: 5 });
56
+ assert.equal(a.theme, 'mocha');
57
+ assert.equal(a.font, 'sistema');
59
58
  assert.equal(a.chatFont, 20);
60
59
  assert.equal(a.scale, 1.3);
61
60
  const b = normalizeAppearance({ theme: 'nope', chatFont: 1, scale: 0.1 });
@@ -67,44 +66,19 @@ test('normalizeAppearance acota tamaños y resuelve ids', () => {
67
66
  });
68
67
 
69
68
  test('la validacion del server coincide con el registro del cliente', () => {
70
- assert.deepEqual(
71
- THEMES.map((t) => t.id).sort(),
72
- [
73
- 'paper',
74
- 'dark',
75
- 'midnight',
76
- 'nord',
77
- 'dracula',
78
- 'solarized',
79
- 'solarized-dark',
80
- 'bosque',
81
- 'vino',
82
- 'arena',
83
- 'hielo',
84
- 'vercel',
85
- 'dark-plus',
86
- 'github',
87
- 'github-dark',
88
- 'mocha',
89
- 'ebano',
90
- ].sort()
91
- );
92
- assert.equal(resolveThemeId('solarized-dark'), 'solarized-dark');
93
- assert.equal(resolveThemeId('vercel'), 'vercel');
94
- assert.equal(resolveThemeId('github-dark'), 'github-dark');
95
- assert.equal(resolveThemeId('light'), 'paper');
69
+ assert.deepEqual(THEMES.map((t) => t.id).sort(), ['paper', 'github', 'dark', 'mocha'].sort());
70
+ assert.equal(resolveThemeId('mocha'), 'mocha');
96
71
  assert.equal(resolveThemeId('bogus'), 'paper');
97
72
  assert.equal(resolveFontId('sistema'), 'sistema');
98
- assert.equal(resolveFontId('jetbrains'), 'jetbrains');
99
73
  assert.equal(resolveFontId('bogus'), 'clasico');
100
74
  });
101
75
 
102
76
  test('resolveAppearance y sanitizeAppearance normalizan app.json', () => {
103
77
  assert.deepEqual(resolveAppearance({}), DEFAULT_APPEARANCE);
104
- const saved = resolveAppearance({ appearance: { theme: 'nord', font: 'plex', chatFont: 18, scale: 1.1 } });
105
- assert.deepEqual(saved, { theme: 'nord', font: 'plex', chatFont: 18, scale: 1.1 });
106
- const merged = sanitizeAppearance({ theme: 'vino' }, saved);
107
- assert.deepEqual(merged, { theme: 'vino', font: 'plex', chatFont: 18, scale: 1.1 });
78
+ const saved = resolveAppearance({ appearance: { theme: 'github', font: 'sistema', chatFont: 18, scale: 1.1 } });
79
+ assert.deepEqual(saved, { theme: 'github', font: 'sistema', chatFont: 18, scale: 1.1 });
80
+ const merged = sanitizeAppearance({ theme: 'mocha' }, saved);
81
+ assert.deepEqual(merged, { theme: 'mocha', font: 'sistema', chatFont: 18, scale: 1.1 });
108
82
  const clamped = sanitizeAppearance({ theme: 'x', font: 'y', chatFont: -5, scale: 9 });
109
83
  assert.deepEqual(clamped, { theme: 'paper', font: 'clasico', chatFont: 12, scale: 1.3 });
110
84
  });
@@ -1,8 +1,6 @@
1
1
  import { test } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import {
4
- ADVISOR_KINDS,
5
- advisorMeta,
6
4
  basename,
7
5
  formatDuration,
8
6
  isFailedToolText,
@@ -97,12 +95,6 @@ test('userText y userImages separan las partes del mensaje multimodal', () => {
97
95
  assert.deepEqual(userImages('plano'), []);
98
96
  });
99
97
 
100
- test('advisorMeta cae al default con un tipo desconocido', () => {
101
- assert.equal(advisorMeta('code'), ADVISOR_KINDS.code);
102
- assert.equal(advisorMeta('nope').label, 'Asesor');
103
- assert.ok(ADVISOR_KINDS.summary);
104
- });
105
-
106
98
  test('basename corta el ultimo segmento y los separadores finales', () => {
107
99
  assert.equal(basename('a/b/c'), 'c');
108
100
  assert.equal(basename('a\\b\\c\\'), 'c');
@@ -62,7 +62,7 @@ for (const [label, width, height] of [['escritorio', 1280, 800], ['tablet', 820,
62
62
  const result = shot.preScriptResult;
63
63
  assert.ok(result, 'el recorrido no devolvio resultado');
64
64
 
65
- assert.ok(result.views.length >= 5, `vistas recorridas: ${JSON.stringify(result.views)}`);
65
+ assert.ok(result.views.length >= 3, `vistas recorridas: ${JSON.stringify(result.views)}`);
66
66
  for (const view of result.views) {
67
67
  assert.ok(view.active && view.visible, `la vista "${view.name}" no se mostro (${label})`);
68
68
  }
@@ -159,13 +159,13 @@ for (const [label, width, height] of [['escritorio', 1280, 800], ['movil', 390,
159
159
  const r = shot.preScriptResult;
160
160
  assert.ok(r, 'el script no devolvio resultado');
161
161
 
162
- assert.equal(r.todas, 14, 'sin busqueda se ven las 14 secciones');
162
+ assert.equal(r.todas, 13, 'sin busqueda se ven las 13 secciones');
163
163
  assert.deepEqual(r.filtradoVoz, ['voz']);
164
164
  assert.deepEqual(r.gruposVoz, ['canales'], 'los grupos sin resultados se ocultan');
165
165
  assert.ok(r.filtradoTema.includes('apariencia'), 'busca por palabra clave');
166
166
  assert.deepEqual(r.sinResultados, { tabs: 0, mensaje: true, grupos: 0 });
167
167
  assert.deepEqual(r.enter, { activo: 'limites', selected: 'limites', nivel: 'panel' });
168
- assert.deepEqual(r.escape, { valor: '', tabs: 14 }, 'Escape limpia la busqueda y vuelve a mostrar todo');
168
+ assert.deepEqual(r.escape, { valor: '', tabs: 13 }, 'Escape limpia la busqueda y vuelve a mostrar todo');
169
169
  assert.ok(r.palette.some((label) => label === 'Ajustes · Apariencia'), `la palette no encontro Apariencia: ${r.palette}`);
170
170
  assert.deepEqual(r.paletteSalto, { vista: true, panel: 'apariencia', nivel: 'panel' });
171
171
 
@@ -198,7 +198,7 @@ const TAMANOS = `(async () => {
198
198
  }
199
199
  };
200
200
  scan('chat');
201
- for (const tab of ['providers', 'ajustes', 'eval', 'runs']) {
201
+ for (const tab of ['providers', 'ajustes']) {
202
202
  document.querySelector('#more-menu button[data-tab="' + tab + '"]').click();
203
203
  await wait(250);
204
204
  scan(tab);
@@ -1,6 +1,5 @@
1
1
  import { test } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import http from 'node:http';
4
3
  import { execFileSync } from 'node:child_process';
5
4
  import { mkdtemp, mkdir, writeFile, stat } from 'node:fs/promises';
6
5
  import { tmpdir } from 'node:os';
@@ -123,9 +122,13 @@ test('runs a scheduled task on demand (shell and agent)', async () => {
123
122
  }
124
123
  });
125
124
 
126
- test('detects, runs and stops dev processes', async () => {
125
+ test('loads, runs and stops declared dev processes', async () => {
127
126
  const cwd = await mkdtemp(path.join(tmpdir(), 'tamnora-proc-'));
128
- await writeFile(path.join(cwd, 'package.json'), JSON.stringify({ scripts: { dev: 'node server.js' } }));
127
+ await mkdir(path.join(cwd, '.tamnora'), { recursive: true });
128
+ await writeFile(
129
+ path.join(cwd, '.tamnora', 'procs.json'),
130
+ JSON.stringify({ processes: [{ label: 'dev', command: 'npm', args: ['run', 'dev'], port: 5173 }] })
131
+ );
129
132
  const { instance, base } = await startServer({ cwd });
130
133
  const json = { 'Content-Type': 'application/json' };
131
134
  try {
@@ -191,14 +194,14 @@ test('runs a console shell command and kills a port', async () => {
191
194
  });
192
195
  assert.equal(missing.status, 400);
193
196
 
194
- const badPort = await fetch(`${base}/api/procs/kill`, {
197
+ const badPort = await fetch(`${base}/api/ports/kill`, {
195
198
  method: 'POST',
196
199
  headers: json,
197
200
  body: JSON.stringify({ port: 0 }),
198
201
  });
199
202
  assert.equal(badPort.status, 400);
200
203
 
201
- const none = await fetch(`${base}/api/procs/kill`, {
204
+ const none = await fetch(`${base}/api/ports/kill`, {
202
205
  method: 'POST',
203
206
  headers: json,
204
207
  body: JSON.stringify({ port: 47555 }),
@@ -213,14 +216,6 @@ test('runs a console shell command and kills a port', async () => {
213
216
  assert.equal(info.listening, false);
214
217
  assert.deepEqual(info.pids, []);
215
218
 
216
- const aliasKill = await fetch(`${base}/api/ports/kill`, {
217
- method: 'POST',
218
- headers: json,
219
- body: JSON.stringify({ port: 47555 }),
220
- });
221
- assert.equal(aliasKill.status, 200);
222
- assert.deepEqual((await aliasKill.json()).pids, []);
223
-
224
219
  const badStatus = await fetch(`${base}/api/ports/0`);
225
220
  assert.equal(badStatus.status, 400);
226
221
  } finally {
@@ -228,49 +223,6 @@ test('runs a console shell command and kills a port', async () => {
228
223
  }
229
224
  });
230
225
 
231
- test('runs an evaluation over HTTP and persists the run', async () => {
232
- const model = http.createServer((req, res) => {
233
- let body = '';
234
- req.on('data', (c) => (body += c));
235
- req.on('end', () => {
236
- res.setHeader('Content-Type', 'text/event-stream');
237
- res.write(`data: ${JSON.stringify({ choices: [{ delta: { content: 'hola' } }] })}\n\n`);
238
- res.write('data: [DONE]\n\n');
239
- res.end();
240
- });
241
- });
242
- await new Promise((r) => model.listen(0, '127.0.0.1', r));
243
- const modelBase = `http://127.0.0.1:${model.address().port}/v1`;
244
-
245
- const { instance, base } = await startServer();
246
- try {
247
- const res = await fetch(`${base}/api/eval`, {
248
- method: 'POST',
249
- headers: { 'Content-Type': 'application/json' },
250
- body: JSON.stringify({
251
- provider: 'openai-compatible',
252
- baseUrl: modelBase,
253
- models: ['mock'],
254
- concurrency: 1,
255
- tasks: [{ id: 't1', input: 'saluda', expect: { contains: 'hola' } }],
256
- }),
257
- });
258
- const text = await res.text();
259
- assert.match(text, /event: result/);
260
- assert.match(text, /event: summary/);
261
- assert.match(text, /"passed":1/);
262
-
263
- const runs = await (await fetch(`${base}/api/runs`)).json();
264
- assert.equal(runs.runs.length, 1);
265
- const detail = await (await fetch(`${base}/api/runs/${runs.runs[0].id}`)).json();
266
- assert.equal(detail.results.length, 1);
267
- assert.equal(detail.results[0].passed, 1);
268
- } finally {
269
- await instance.close();
270
- model.close();
271
- }
272
- });
273
-
274
226
  test('creates a folder over HTTP and rejects bad input', async () => {
275
227
  const root = await mkdtemp(path.join(tmpdir(), 'tamnora-newdir-'));
276
228
  const { instance, base } = await startServer();
@@ -1,10 +1,10 @@
1
1
  import { test } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { mkdtemp, writeFile, unlink } from 'node:fs/promises';
3
+ import { mkdtemp, writeFile } from 'node:fs/promises';
4
4
  import { tmpdir } from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { createTamnoraServer } from '../src/index.js';
7
- import { setHome, bridgeStatePath } from '../../core/src/index.js';
7
+ import { setHome } from '../../core/src/index.js';
8
8
 
9
9
  async function startServer() {
10
10
  const cwd = await mkdtemp(path.join(tmpdir(), 'tamnora-preview-'));
@@ -21,29 +21,12 @@ async function startServer() {
21
21
  return { instance, base: info.url, cwd };
22
22
  }
23
23
 
24
- /** Deja un bridge.json apuntando a un tunel publico vivo (este proceso). */
25
- async function fakePublicTunnel() {
26
- await writeFile(
27
- bridgeStatePath(),
28
- `${JSON.stringify({ pid: process.pid, url: 'https://fake.tunnelmole.net', provider: 'tunnelmole', port: 4517 })}\n`
29
- );
30
- }
31
-
32
- async function clearTunnel() {
33
- try {
34
- await unlink(bridgeStatePath());
35
- } catch {
36
- /* nada */
37
- }
38
- }
39
-
40
24
  test('GET /api/preview arranca sin tunel', async () => {
41
25
  const { instance, base } = await startServer();
42
26
  try {
43
27
  const data = await (await fetch(`${base}/api/preview`)).json();
44
28
  assert.equal(data.tunnel, null);
45
29
  } finally {
46
- await clearTunnel();
47
30
  await instance.close();
48
31
  }
49
32
  });
@@ -69,7 +52,6 @@ test('POST/GET/DELETE /api/preview/tunnel mantiene el estado', async () => {
69
52
  const after = await (await fetch(`${base}/api/preview`)).json();
70
53
  assert.equal(after.tunnel, null);
71
54
  } finally {
72
- await clearTunnel();
73
55
  await instance.close();
74
56
  }
75
57
  });
@@ -84,28 +66,6 @@ test('POST /api/preview/tunnel rechaza un puerto invalido', async () => {
84
66
  });
85
67
  assert.equal(post.status, 400);
86
68
  } finally {
87
- await clearTunnel();
88
- await instance.close();
89
- }
90
- });
91
-
92
- test('con un tunel publico activo el preview queda cerrado para no locales', async () => {
93
- const { instance, base } = await startServer();
94
- try {
95
- await fakePublicTunnel();
96
- await new Promise((resolve) => setTimeout(resolve, 1100));
97
-
98
- const post = await fetch(`${base}/api/preview/tunnel`, {
99
- method: 'POST',
100
- headers: { 'Content-Type': 'application/json' },
101
- body: JSON.stringify({ port: 8080 }),
102
- });
103
- assert.equal(post.status, 403);
104
-
105
- const del = await fetch(`${base}/api/preview/tunnel`, { method: 'DELETE' });
106
- assert.equal(del.status, 403);
107
- } finally {
108
- await clearTunnel();
109
69
  await instance.close();
110
70
  }
111
71
  });