@danieltmn/tamnora 0.21.2 → 0.23.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 (77) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +3 -1
  3. package/SECURITY.md +1 -1
  4. package/harness/packages/cli/bin/tamnora.mjs +7 -1
  5. package/harness/packages/cli/src/commands/doctor.js +42 -4
  6. package/harness/packages/cli/src/commands/repl.js +1 -0
  7. package/harness/packages/cli/src/commands/run.js +1 -0
  8. package/harness/packages/cli/src/commands/tui.js +1 -0
  9. package/harness/packages/cli/src/lib/runtime.js +2 -0
  10. package/harness/packages/cli/src/tui/app.js +2 -0
  11. package/harness/packages/core/src/agent/runAgent.js +9 -5
  12. package/harness/packages/core/src/browser/index.js +27 -13
  13. package/harness/packages/core/src/index.js +1 -0
  14. package/harness/packages/core/src/mcp/stdio.js +15 -0
  15. package/harness/packages/core/src/net/hosts.js +70 -0
  16. package/harness/packages/core/src/redact.js +2 -0
  17. package/harness/packages/core/src/tools/executors.js +19 -24
  18. package/harness/packages/core/src/tools/index.js +1 -1
  19. package/harness/packages/core/src/tools/paths.js +35 -5
  20. package/harness/packages/core/src/tools/permissions.js +41 -2
  21. package/harness/packages/core/src/tools/registry.js +8 -3
  22. package/harness/packages/core/src/tts/edge.js +34 -4
  23. package/harness/packages/core/src/tunnel.js +15 -5
  24. package/harness/packages/core/test/agent.test.js +18 -0
  25. package/harness/packages/core/test/servicios-vivos.test.js +71 -0
  26. package/harness/packages/core/test/tools.test.js +44 -1
  27. package/harness/packages/core/test/tts.test.js +81 -0
  28. package/harness/packages/server/bin/tamnora-server.mjs +7 -1
  29. package/harness/packages/server/public/app.js +347 -6757
  30. package/harness/packages/server/public/cards.js +470 -0
  31. package/harness/packages/server/public/chat.js +1343 -0
  32. package/harness/packages/server/public/chatFormat.js +156 -0
  33. package/harness/packages/server/public/chrome.js +813 -0
  34. package/harness/packages/server/public/commands.js +403 -0
  35. package/harness/packages/server/public/core.js +209 -0
  36. package/harness/packages/server/public/explorer.js +625 -0
  37. package/harness/packages/server/public/icons/logo_dark.png +0 -0
  38. package/harness/packages/server/public/icons/logo_light.png +0 -0
  39. package/harness/packages/server/public/index.html +40 -41
  40. package/harness/packages/server/public/sessions.js +274 -0
  41. package/harness/packages/server/public/settings.js +1080 -0
  42. package/harness/packages/server/public/styles.css +45 -52
  43. package/harness/packages/server/public/system.js +495 -0
  44. package/harness/packages/server/public/voice.js +1058 -0
  45. package/harness/packages/server/src/auth.js +12 -10
  46. package/harness/packages/server/src/lib/agentRun.js +6 -1
  47. package/harness/packages/server/src/lib/channels/telegram/api.js +102 -0
  48. package/harness/packages/server/src/lib/channels/telegram/commands.js +309 -0
  49. package/harness/packages/server/src/lib/channels/telegram/media.js +64 -0
  50. package/harness/packages/server/src/lib/channels/telegram/permissions.js +0 -0
  51. package/harness/packages/server/src/lib/channels/telegram/projects.js +102 -0
  52. package/harness/packages/server/src/lib/channels/telegram/run.js +206 -0
  53. package/harness/packages/server/src/lib/channels/telegram/text.js +57 -0
  54. package/harness/packages/server/src/lib/channels/telegram.js +163 -932
  55. package/harness/packages/server/src/lib/presence.js +12 -1
  56. package/harness/packages/server/src/lib/scheduler.js +12 -2
  57. package/harness/packages/server/src/lib/tasks.js +21 -3
  58. package/harness/packages/server/src/lib/workspace.js +90 -2
  59. package/harness/packages/server/src/routes/auth.js +8 -4
  60. package/harness/packages/server/src/routes/chat.js +16 -5
  61. package/harness/packages/server/src/routes/preview.js +10 -2
  62. package/harness/packages/server/src/routes/providers.js +14 -9
  63. package/harness/packages/server/src/server.js +25 -0
  64. package/harness/packages/server/test/chat.test.js +586 -0
  65. package/harness/packages/server/test/chatFormat.test.js +119 -0
  66. package/harness/packages/server/test/consoleModules.test.js +301 -0
  67. package/harness/packages/server/test/consoleSmoke.test.js +34 -0
  68. package/harness/packages/server/test/helpers.js +121 -0
  69. package/harness/packages/server/test/ops.test.js +515 -0
  70. package/harness/packages/server/test/runs.test.js +816 -0
  71. package/harness/packages/server/test/security.test.js +112 -9
  72. package/harness/packages/server/test/sessions.test.js +437 -0
  73. package/harness/packages/server/test/settings.test.js +482 -0
  74. package/harness/scripts/canary.mjs +24 -0
  75. package/harness/tamnora.example.json +1 -1
  76. package/package.json +3 -2
  77. package/harness/packages/server/test/server.test.js +0 -2885
@@ -0,0 +1,156 @@
1
+ // Formato puro de la consola: lo que se calcula de los datos del turno sin
2
+ // tocar el DOM. Vive aparte para poder testearlo (chat.js es todo DOM).
3
+ export const READ_ONLY_TOOLS = new Set([
4
+ 'read',
5
+ 'list',
6
+ 'glob',
7
+ 'grep',
8
+ 'fetch_url',
9
+ 'git_status',
10
+ 'git_diff',
11
+ 'git_log',
12
+ 'task',
13
+ 'spawn_agent',
14
+ ]);
15
+ export function isReadOnlyTool(name) {
16
+ const value = String(name ?? '');
17
+ if (READ_ONLY_TOOLS.has(value)) return true;
18
+ return /^mcp__.+__(read|list|search|get|fetch|query)/i.test(value);
19
+ }
20
+
21
+ export function shortText(value, max = 80) {
22
+ const raw = String(value ?? '').replace(/\s+/g, ' ').trim();
23
+ return raw.length > max ? `${raw.slice(0, max - 1)}…` : raw;
24
+ }
25
+
26
+ export function toolSummary(name, args) {
27
+ const a = args && typeof args === 'object' ? args : {};
28
+ switch (name) {
29
+ case 'read': {
30
+ const base = a.path ? shortText(a.path, 60) : '.';
31
+ const offset = Number(a.offset) || 0;
32
+ const limit = Number(a.limit) || 0;
33
+ if (offset && limit) return `${base}:${offset}-${offset + limit - 1}`;
34
+ if (offset) return `${base}:${offset}+`;
35
+ return base;
36
+ }
37
+ case 'write':
38
+ case 'edit':
39
+ return a.path ? shortText(a.path, 60) : 'archivo';
40
+ case 'list':
41
+ return a.path ? shortText(a.path, 60) : '.';
42
+ case 'glob':
43
+ return [a.pattern ? `"${shortText(a.pattern, 50)}"` : '', a.path ? `en ${shortText(a.path, 40)}` : '']
44
+ .filter(Boolean)
45
+ .join(' ');
46
+ case 'grep':
47
+ return [
48
+ a.pattern ? `"${shortText(a.pattern, 50)}"` : '',
49
+ a.include ? shortText(a.include, 30) : '',
50
+ a.path ? `en ${shortText(a.path, 40)}` : '',
51
+ ]
52
+ .filter(Boolean)
53
+ .join(' ');
54
+ case 'bash':
55
+ return shortText(String(a.command ?? '').split('\n')[0], 90);
56
+ case 'fetch_url':
57
+ return shortText(a.url, 90);
58
+ case 'speak':
59
+ return shortText(a.texto, 90);
60
+ case 'screenshot':
61
+ case 'console_errors':
62
+ case 'preview_url':
63
+ return shortText(a.url, 90);
64
+ case 'git_diff':
65
+ return [a.ref ? `vs ${shortText(a.ref, 30)}` : a.staged ? 'staged' : 'unstaged', a.path ? shortText(a.path, 40) : '']
66
+ .filter(Boolean)
67
+ .join(' ');
68
+ case 'git_log':
69
+ return a.limit ? `${a.limit} commits` : '';
70
+ case 'git_commit':
71
+ return shortText(a.message, 70);
72
+ case 'task':
73
+ case 'spawn_agent':
74
+ return shortText(a.description || a.prompt, 80);
75
+ default:
76
+ return '';
77
+ }
78
+ }
79
+
80
+ export function parseToolArgs(call) {
81
+ const raw = call?.function?.arguments ?? call?.arguments;
82
+ if (raw && typeof raw === 'object') return raw;
83
+ if (typeof raw !== 'string') return {};
84
+ try {
85
+ return JSON.parse(raw);
86
+ } catch {
87
+ return {};
88
+ }
89
+ }
90
+
91
+ export function isFailedToolText(text) {
92
+ return /^Error|^Permiso denegado/i.test(String(text ?? ''));
93
+ }
94
+
95
+ /** Nombre de archivo de una captura, del meta o del texto del resultado. */
96
+ export function screenshotName(data, text) {
97
+ const fromMeta = data?.meta?.screenshot?.name;
98
+ if (fromMeta) return String(fromMeta);
99
+ const match = /capturas\/([a-z0-9][a-z0-9-]*\.png)/i.exec(String(text ?? ''));
100
+ return match ? match[1] : null;
101
+ }
102
+
103
+ export function previewText(value, maxLines = 24, maxChars = 4000) {
104
+ const content = typeof value === 'string' ? value : JSON.stringify(value ?? '', null, 2) ?? '';
105
+ const lines = content.split('\n');
106
+ let preview = lines.slice(0, maxLines).join('\n');
107
+ let truncated = lines.length > maxLines;
108
+ if (preview.length > maxChars) {
109
+ preview = preview.slice(0, maxChars);
110
+ truncated = true;
111
+ }
112
+ return { preview, truncated };
113
+ }
114
+
115
+ export function userText(content) {
116
+ if (typeof content === 'string') return content;
117
+ if (Array.isArray(content)) {
118
+ return content
119
+ .filter((part) => part?.type === 'text')
120
+ .map((part) => part.text)
121
+ .join('\n');
122
+ }
123
+ return content == null ? '' : JSON.stringify(content);
124
+ }
125
+
126
+ export function userImages(content) {
127
+ if (!Array.isArray(content)) return [];
128
+ return content
129
+ .filter((part) => part?.type === 'image_url')
130
+ .map((part) => part.image_url?.url)
131
+ .filter(Boolean);
132
+ }
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
+ /** Ultimo segmento de un path (sin separadores finales). */
146
+ export function basename(p) {
147
+ const text = String(p ?? '').replace(/[\\/]+$/, '');
148
+ return text.split(/[\\/]/).pop() || text;
149
+ }
150
+
151
+ /** Duracion humana de un turno: 1.2s o 300ms, vacio si no hubo tiempo. */
152
+ export function formatDuration(ms) {
153
+ const value = Number(ms) || 0;
154
+ if (value <= 0) return '';
155
+ return value >= 1000 ? `${(value / 1000).toFixed(1)}s` : `${value}ms`;
156
+ }