@danieltmn/tamnora 0.8.0 → 0.9.1

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 (57) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/harness/packages/cli/src/cli.js +1 -1
  3. package/harness/packages/cli/src/commands/bridge.js +180 -119
  4. package/harness/packages/cli/src/commands/server.js +26 -50
  5. package/harness/packages/cli/src/lib/daemon.js +92 -40
  6. package/harness/packages/cli/test/release.test.js +9 -0
  7. package/harness/packages/core/src/agent/modes.js +1 -1
  8. package/harness/packages/core/src/index.js +14 -1
  9. package/harness/packages/core/src/open.js +43 -0
  10. package/harness/packages/core/src/pid.js +35 -0
  11. package/harness/packages/core/src/tools/executors.js +16 -0
  12. package/harness/packages/core/src/tools/labels.js +2 -0
  13. package/harness/packages/core/src/tools/permissions.js +1 -0
  14. package/harness/packages/core/src/tools/schema.js +17 -0
  15. package/harness/packages/core/src/tts/edge.js +232 -0
  16. package/harness/packages/core/src/tunnel.js +180 -19
  17. package/harness/packages/core/test/open.test.js +19 -0
  18. package/harness/packages/core/test/tools.test.js +12 -0
  19. package/harness/packages/core/test/tts.test.js +41 -0
  20. package/harness/packages/server/public/app.js +4665 -3975
  21. package/harness/packages/server/public/index.html +62 -5
  22. package/harness/packages/server/public/reconcile.js +121 -0
  23. package/harness/packages/server/public/relayClient.js +170 -0
  24. package/harness/packages/server/public/runStream.js +207 -0
  25. package/harness/packages/server/public/styles.css +142 -5
  26. package/harness/packages/server/src/auth.js +29 -5
  27. package/harness/packages/server/src/lib/agentRun.js +77 -0
  28. package/harness/packages/server/src/lib/announcer.js +92 -0
  29. package/harness/packages/server/src/lib/approvals.js +25 -3
  30. package/harness/packages/server/src/lib/channels/telegram.js +85 -20
  31. package/harness/packages/server/src/lib/ffmpeg.js +48 -0
  32. package/harness/packages/server/src/lib/procs.js +3 -18
  33. package/harness/packages/server/src/lib/runManager.js +24 -2
  34. package/harness/packages/server/src/lib/sessionView.js +43 -0
  35. package/harness/packages/server/src/lib/tasks.js +4 -9
  36. package/harness/packages/server/src/lib/ttsSettings.js +42 -0
  37. package/harness/packages/server/src/lib/workspace.js +37 -1
  38. package/harness/packages/server/src/routes/chat.js +48 -47
  39. package/harness/packages/server/src/routes/events.js +35 -0
  40. package/harness/packages/server/src/routes/files.js +22 -26
  41. package/harness/packages/server/src/routes/folders.js +9 -17
  42. package/harness/packages/server/src/routes/git.js +6 -14
  43. package/harness/packages/server/src/routes/procs.js +14 -16
  44. package/harness/packages/server/src/routes/sessions.js +12 -19
  45. package/harness/packages/server/src/routes/shell.js +5 -9
  46. package/harness/packages/server/src/routes/state.js +32 -0
  47. package/harness/packages/server/src/routes/tts.js +82 -0
  48. package/harness/packages/server/src/server.js +21 -1
  49. package/harness/packages/server/test/announcer.test.js +51 -0
  50. package/harness/packages/server/test/reconcile.test.js +146 -0
  51. package/harness/packages/server/test/relay.test.js +219 -0
  52. package/harness/packages/server/test/runStream.test.js +245 -0
  53. package/harness/packages/server/test/security.test.js +252 -0
  54. package/harness/packages/server/test/server.test.js +511 -1
  55. package/harness/packages/server/test/telegram-voz.test.js +156 -0
  56. package/harness/packages/server/test/tts.test.js +133 -0
  57. package/package.json +1 -1
@@ -0,0 +1,133 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import path from 'node:path';
6
+ import { createTamnoraServer } from '../src/index.js';
7
+ import { setHome } from '../../core/src/index.js';
8
+
9
+ const FAKE_MP3 = 'FFF364MP3DEPRUEBA';
10
+
11
+ /** Frame binario exacto (sin el pool de Buffer, que arrastra bytes ajenos). */
12
+ const FAKE_FRAME = new TextEncoder().encode(`Path:audio\r\n${FAKE_MP3}`).buffer;
13
+
14
+ /** WebSocket falso: entrega un frame de audio y cierra con turn.end. */
15
+ class FakeWebSocket {
16
+ constructor() {
17
+ this.listeners = new Map();
18
+ this.binaryType = 'arraybuffer';
19
+ setTimeout(() => {
20
+ this.fire('open', {});
21
+ this.fire('message', FAKE_FRAME);
22
+ this.fire('message', 'X-RequestId:1\r\nPath:turn.end\r\n');
23
+ }, 0);
24
+ }
25
+ addEventListener(type, fn) {
26
+ if (!this.listeners.has(type)) this.listeners.set(type, new Set());
27
+ this.listeners.get(type).add(fn);
28
+ }
29
+ removeEventListener() {}
30
+ send() {}
31
+ close() {
32
+ this.fire('close', {});
33
+ }
34
+ fire(type, arg) {
35
+ for (const fn of this.listeners.get(type) ?? []) fn({ data: arg, message: 'fake' });
36
+ }
37
+ }
38
+
39
+ /** WebSocket falso que falla: para probar el 502 de la ruta. */
40
+ class BrokenWebSocket {
41
+ constructor() {
42
+ this.listeners = new Map();
43
+ setTimeout(() => this.fire('error', {}), 0);
44
+ }
45
+ addEventListener(type, fn) {
46
+ if (!this.listeners.has(type)) this.listeners.set(type, new Set());
47
+ this.listeners.get(type).add(fn);
48
+ }
49
+ removeEventListener() {}
50
+ send() {}
51
+ close() {}
52
+ fire(type, arg) {
53
+ for (const fn of this.listeners.get(type) ?? []) fn({ data: arg, message: 'boom' });
54
+ }
55
+ }
56
+
57
+ async function startServer() {
58
+ const cwd = await mkdtemp(path.join(tmpdir(), 'tamnora-tts-'));
59
+ setHome(cwd);
60
+ const instance = await createTamnoraServer({
61
+ cwd,
62
+ dbPath: path.join(cwd, 'tamnora.db'),
63
+ loadMcp: false,
64
+ resolveProvider: () => ({ provider: { id: 'fake' }, config: {} }),
65
+ });
66
+ const info = await instance.listen({ port: 0 });
67
+ return { instance, base: info.url };
68
+ }
69
+
70
+ test('los ajustes de voz exponen defaults y sanitizan lo guardado', async () => {
71
+ const { instance, base } = await startServer();
72
+ const original = global.WebSocket;
73
+ global.WebSocket = FakeWebSocket;
74
+ try {
75
+ const initial = await (await fetch(`${base}/api/tts/settings`)).json();
76
+ assert.equal(initial.enabled, false);
77
+ assert.equal(initial.voice, 'es-AR-ElenaNeural');
78
+ assert.equal(initial.announce.start, true);
79
+ assert.equal(initial.announce.tools, true);
80
+ assert.equal(initial.announce.end, true);
81
+
82
+ const put = await fetch(`${base}/api/tts/settings`, {
83
+ method: 'PUT',
84
+ headers: { 'Content-Type': 'application/json' },
85
+ body: JSON.stringify({ enabled: true, voice: '<script>', rate: '150%' }),
86
+ });
87
+ const saved = await put.json();
88
+ assert.equal(saved.enabled, true);
89
+ assert.equal(saved.voice, 'es-AR-ElenaNeural');
90
+ assert.equal(saved.rate, '+100%');
91
+
92
+ const reread = await (await fetch(`${base}/api/tts/settings`)).json();
93
+ assert.equal(reread.enabled, true);
94
+ assert.equal(reread.rate, '+100%');
95
+ } finally {
96
+ global.WebSocket = original;
97
+ await instance.close();
98
+ }
99
+ });
100
+
101
+ test('la sintesis exige texto y entrega MP3', async () => {
102
+ const { instance, base } = await startServer();
103
+ const original = global.WebSocket;
104
+ global.WebSocket = FakeWebSocket;
105
+ try {
106
+ const falta = await fetch(`${base}/api/tts`);
107
+ assert.equal(falta.status, 400);
108
+
109
+ const res = await fetch(`${base}/api/tts?texto=${encodeURIComponent('hola')}`);
110
+ assert.equal(res.status, 200);
111
+ assert.equal(res.headers.get('content-type'), 'audio/mpeg');
112
+ const body = Buffer.from(await res.arrayBuffer());
113
+ assert.equal(body.toString('utf8'), FAKE_MP3);
114
+ } finally {
115
+ global.WebSocket = original;
116
+ await instance.close();
117
+ }
118
+ });
119
+
120
+ test('un fallo del servicio de voz se ve como 502, no como crash', async () => {
121
+ const { instance, base } = await startServer();
122
+ const original = global.WebSocket;
123
+ global.WebSocket = BrokenWebSocket;
124
+ try {
125
+ const res = await fetch(`${base}/api/tts?texto=${encodeURIComponent('hola')}`);
126
+ assert.equal(res.status, 502);
127
+ const data = await res.json();
128
+ assert.match(data.error, /No pude sintetizar/);
129
+ } finally {
130
+ global.WebSocket = original;
131
+ await instance.close();
132
+ }
133
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danieltmn/tamnora",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "Harness local para IAs: agente CLI-first con TUI, consola web y evaluacion. Node puro, sin dependencias.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.25.0",