@danieltmn/tamnora 0.7.1 → 0.9.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 (66) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/README.md +9 -0
  3. package/SECURITY.md +11 -6
  4. package/harness/packages/cli/src/cli.js +1 -1
  5. package/harness/packages/cli/src/commands/bridge.js +180 -119
  6. package/harness/packages/cli/src/commands/run.js +1 -1
  7. package/harness/packages/cli/src/commands/server.js +26 -50
  8. package/harness/packages/cli/src/commands/tui.js +1 -1
  9. package/harness/packages/cli/src/lib/daemon.js +92 -40
  10. package/harness/packages/cli/test/release.test.js +9 -0
  11. package/harness/packages/core/src/agent/modes.js +1 -1
  12. package/harness/packages/core/src/agent/runAgent.js +3 -0
  13. package/harness/packages/core/src/git/index.js +177 -0
  14. package/harness/packages/core/src/index.js +16 -1
  15. package/harness/packages/core/src/open.js +43 -0
  16. package/harness/packages/core/src/pid.js +35 -0
  17. package/harness/packages/core/src/procs/kill.js +95 -0
  18. package/harness/packages/core/src/tools/executors.js +16 -0
  19. package/harness/packages/core/src/tools/labels.js +2 -0
  20. package/harness/packages/core/src/tools/permissions.js +1 -0
  21. package/harness/packages/core/src/tools/schema.js +17 -0
  22. package/harness/packages/core/src/tts/edge.js +232 -0
  23. package/harness/packages/core/src/tunnel.js +180 -19
  24. package/harness/packages/core/test/open.test.js +19 -0
  25. package/harness/packages/core/test/procs.test.js +50 -0
  26. package/harness/packages/core/test/tools.test.js +12 -0
  27. package/harness/packages/core/test/tts.test.js +41 -0
  28. package/harness/packages/server/public/app.js +4715 -3669
  29. package/harness/packages/server/public/index.html +94 -5
  30. package/harness/packages/server/public/landing/index.html +5 -5
  31. package/harness/packages/server/public/reconcile.js +121 -0
  32. package/harness/packages/server/public/relayClient.js +170 -0
  33. package/harness/packages/server/public/runStream.js +207 -0
  34. package/harness/packages/server/public/styles.css +196 -23
  35. package/harness/packages/server/src/auth.js +29 -5
  36. package/harness/packages/server/src/lib/agentRun.js +77 -0
  37. package/harness/packages/server/src/lib/announcer.js +92 -0
  38. package/harness/packages/server/src/lib/approvals.js +25 -3
  39. package/harness/packages/server/src/lib/channels/telegram.js +85 -20
  40. package/harness/packages/server/src/lib/ffmpeg.js +48 -0
  41. package/harness/packages/server/src/lib/procs.js +3 -18
  42. package/harness/packages/server/src/lib/runManager.js +24 -2
  43. package/harness/packages/server/src/lib/sessionView.js +43 -0
  44. package/harness/packages/server/src/lib/tasks.js +4 -9
  45. package/harness/packages/server/src/lib/ttsSettings.js +42 -0
  46. package/harness/packages/server/src/lib/workspace.js +37 -1
  47. package/harness/packages/server/src/routes/chat.js +51 -47
  48. package/harness/packages/server/src/routes/events.js +35 -0
  49. package/harness/packages/server/src/routes/files.js +22 -26
  50. package/harness/packages/server/src/routes/folders.js +9 -17
  51. package/harness/packages/server/src/routes/git.js +35 -0
  52. package/harness/packages/server/src/routes/procs.js +25 -15
  53. package/harness/packages/server/src/routes/sessions.js +12 -19
  54. package/harness/packages/server/src/routes/shell.js +19 -0
  55. package/harness/packages/server/src/routes/state.js +32 -0
  56. package/harness/packages/server/src/routes/tts.js +82 -0
  57. package/harness/packages/server/src/server.js +25 -1
  58. package/harness/packages/server/test/announcer.test.js +51 -0
  59. package/harness/packages/server/test/reconcile.test.js +146 -0
  60. package/harness/packages/server/test/relay.test.js +219 -0
  61. package/harness/packages/server/test/runStream.test.js +245 -0
  62. package/harness/packages/server/test/security.test.js +252 -0
  63. package/harness/packages/server/test/server.test.js +617 -1
  64. package/harness/packages/server/test/telegram-voz.test.js +156 -0
  65. package/harness/packages/server/test/tts.test.js +133 -0
  66. package/package.json +1 -1
@@ -1,9 +1,59 @@
1
1
  import { test } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
+ import { spawn } from 'node:child_process';
4
+ import net from 'node:net';
3
5
  import { mkdtemp, mkdir, writeFile } from 'node:fs/promises';
4
6
  import { tmpdir } from 'node:os';
5
7
  import path from 'node:path';
6
8
  import { detectProcs, packageManager, DEFAULT_PROCESS_ALLOW } from '../src/procs/detect.js';
9
+ import { killPort } from '../src/procs/kill.js';
10
+
11
+ async function waitForPort(port, attempts = 50) {
12
+ for (let i = 0; i < attempts; i++) {
13
+ const ok = await new Promise((resolve) => {
14
+ const socket = net.connect({ port, host: '127.0.0.1' });
15
+ socket.once('connect', () => {
16
+ socket.destroy();
17
+ resolve(true);
18
+ });
19
+ socket.once('error', () => resolve(false));
20
+ });
21
+ if (ok) return;
22
+ await new Promise((resolve) => setTimeout(resolve, 100));
23
+ }
24
+ throw new Error('el puerto no abrió');
25
+ }
26
+
27
+ test('killPort rejects invalid ports', async () => {
28
+ await assert.rejects(() => killPort(0), /Puerto inválido/);
29
+ await assert.rejects(() => killPort(70000), /Puerto inválido/);
30
+ await assert.rejects(() => killPort('abc'), /Puerto inválido/);
31
+ });
32
+
33
+ test('killPort terminates the process listening on a port', async (t) => {
34
+ const port = 47000 + Math.floor(Math.random() * 1000);
35
+ const script = `const http=require('node:http');http.createServer((q,r)=>r.end('ok')).listen(${port},'127.0.0.1');setInterval(()=>{},1000);`;
36
+ const child = spawn(process.execPath, ['-e', script], { stdio: 'ignore', windowsHide: true });
37
+ t.after(() => {
38
+ try {
39
+ child.kill('SIGKILL');
40
+ } catch {
41
+ // already gone
42
+ }
43
+ });
44
+ await waitForPort(port);
45
+
46
+ const result = await killPort(port);
47
+ assert.ok(result.pids.length >= 1, 'debería encontrar el PID');
48
+ assert.ok(result.killed.length >= 1, 'debería matar el proceso');
49
+
50
+ await new Promise((resolve) => {
51
+ if (child.exitCode !== null || child.signalCode) return resolve();
52
+ child.once('exit', resolve);
53
+ setTimeout(resolve, 3000);
54
+ });
55
+ assert.ok(child.exitCode !== null || child.signalCode, 'el hijo debería haber terminado');
56
+ });
7
57
 
8
58
  test('detectProcs finds node dev scripts with the right package manager', async () => {
9
59
  const dir = await mkdtemp(path.join(tmpdir(), 'tamnora-proc-'));
@@ -157,3 +157,15 @@ test('bash runs inside the workspace root', async () => {
157
157
  assert.match(out, /hola/);
158
158
  assert.match(out, /\[exit 0\]/);
159
159
  });
160
+
161
+ test('la tool speak valida el texto y la recorta', async () => {
162
+ const root = await makeWorkspace();
163
+ const reg = createToolRegistry({ root });
164
+ assert.equal(evaluatePermission('speak', {}, {}), 'allow');
165
+ assert.match(await reg.execute('speak', {}), /falta "texto"/);
166
+ assert.match(await reg.execute('speak', { texto: 'Hola mundo' }), /Hola mundo/);
167
+ const largo = await reg.execute('speak', { texto: 'a'.repeat(2000) });
168
+ assert.ok(largo.length < 2000, 'el texto largo se recorta');
169
+ const { definitions } = createTools({ root: await makeWorkspace() });
170
+ assert.ok(definitions.some((d) => d.function.name === 'speak'));
171
+ });
@@ -0,0 +1,41 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { generarSecMsGec, generarSsml, sintetizar, MAX_TEXTO, VOZ_POR_DEFECTO } from '../src/index.js';
4
+
5
+ const GEC_1700000000000 = '42301B335578FEFDAE2637DED1ABD614505D432559EC08032B82048483726AFF';
6
+
7
+ test('generarSecMsGec es estable dentro de la ventana de 5 minutos', () => {
8
+ assert.equal(generarSecMsGec(1700000000000), GEC_1700000000000);
9
+ assert.equal(generarSecMsGec(1700000000000 + 59999), GEC_1700000000000);
10
+ assert.notEqual(generarSecMsGec(1700000000000 + 600000), GEC_1700000000000);
11
+ assert.match(generarSecMsGec(), /^[0-9A-F]{64}$/);
12
+ });
13
+
14
+ test('generarSsml escapa el texto y deduce el locale de la voz', () => {
15
+ const ssml = generarSsml({ voz: VOZ_POR_DEFECTO, rate: '+10%', pitch: '+0Hz', volume: '+0%', texto: 'hola <>&"\' mundo' });
16
+ assert.ok(ssml.includes('xml:lang="es-AR"'));
17
+ assert.ok(ssml.includes('<voice name="es-AR-ElenaNeural">'));
18
+ assert.ok(ssml.includes('rate="+10%"'));
19
+ assert.ok(!ssml.includes('hola <'));
20
+ assert.ok(ssml.includes('&lt;&gt;&amp;&quot;&apos;'));
21
+ });
22
+
23
+ test('generarSsml rechaza voces sin locale deducible', () => {
24
+ assert.throws(() => generarSsml({ voz: 'Elena', rate: '+0%', pitch: '+0Hz', volume: '+0%', texto: 'x' }), /locale/);
25
+ });
26
+
27
+ test('sintetizar rechaza texto vacio y señales ya abortadas sin abrir socket', () => {
28
+ assert.throws(() => sintetizar(' '), /vac/);
29
+ const controller = new AbortController();
30
+ controller.abort();
31
+ assert.throws(() => sintetizar('hola', { signal: controller.signal }), /cancel/);
32
+ });
33
+
34
+ test('sintetizar recorta el texto al tope del servicio', () => {
35
+ // El recorte ocurre antes de abrir el socket: con señal ya abortada el error
36
+ // de cancelacion llega antes que cualquier intento de conexion.
37
+ const controller = new AbortController();
38
+ controller.abort();
39
+ const largo = 'x'.repeat(MAX_TEXTO + 100);
40
+ assert.throws(() => sintetizar(largo, { signal: controller.signal }), /cancel/);
41
+ });