@danieltmn/tamnora 0.25.0 → 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.
- package/CHANGELOG.md +18 -0
- package/README.md +13 -25
- package/SECURITY.md +3 -4
- package/harness/packages/cli/src/cli.js +0 -3
- package/harness/packages/cli/src/commands/bridge.js +0 -1
- package/harness/packages/cli/src/commands/repl.js +20 -4
- package/harness/packages/cli/src/commands/run.js +6 -2
- package/harness/packages/cli/src/commands/server.js +1 -1
- package/harness/packages/cli/src/commands/tui.js +6 -2
- package/harness/packages/cli/src/lib/runtime.js +2 -0
- package/harness/packages/cli/src/lib/util.js +2 -2
- package/harness/packages/cli/src/tui/app.js +24 -4
- package/harness/packages/cli/src/tui/layout.js +1 -1
- package/harness/packages/cli/test/tunnel.test.js +5 -32
- package/harness/packages/core/src/agent/runAgent.js +5 -2
- package/harness/packages/core/src/agents/index.js +113 -0
- package/harness/packages/core/src/config/app.js +0 -4
- package/harness/packages/core/src/config/home.js +1 -20
- package/harness/packages/core/src/config/index.js +6 -8
- package/harness/packages/core/src/extensions/load.js +22 -0
- package/harness/packages/core/src/frontmatter.js +32 -0
- package/harness/packages/core/src/hooks/index.js +2 -16
- package/harness/packages/core/src/index.js +12 -14
- package/harness/packages/core/src/plugins/index.js +2 -16
- package/harness/packages/core/src/procs/project.js +68 -0
- package/harness/packages/core/src/procs/static.js +17 -0
- package/harness/packages/core/src/providers/index.js +3 -1
- package/harness/packages/core/src/providers/ollama.js +4 -1
- package/harness/packages/core/src/providers/openaiCompatible.js +6 -1
- package/harness/packages/core/src/providers/openrouter.js +5 -1
- package/harness/packages/core/src/providers/presets.js +4 -3
- package/harness/packages/core/src/providers/reasoning.js +58 -0
- package/harness/packages/core/src/rules/index.js +8 -27
- package/harness/packages/core/src/skills/index.js +8 -19
- package/harness/packages/core/src/storage/sqlite.js +595 -690
- package/harness/packages/core/src/tunnel.js +9 -147
- package/harness/packages/core/test/agents.test.js +111 -0
- package/harness/packages/core/test/home.test.js +2 -35
- package/harness/packages/core/test/procs.test.js +3 -64
- package/harness/packages/core/test/reasoning.test.js +43 -0
- package/harness/packages/core/test/storage.test.js +1 -12
- package/harness/packages/server/public/app.js +39 -19
- package/harness/packages/server/public/appearance.js +2 -56
- package/harness/packages/server/public/boot.js +2 -2
- package/harness/packages/server/public/cards.js +2 -21
- package/harness/packages/server/public/chat.js +1 -16
- package/harness/packages/server/public/chatFormat.js +0 -11
- package/harness/packages/server/public/chrome.js +95 -5
- package/harness/packages/server/public/commands.js +1 -2
- package/harness/packages/server/public/core.js +1 -1
- package/harness/packages/server/public/explorer.js +7 -10
- package/harness/packages/server/public/icons (1).zip +0 -0
- package/harness/packages/server/public/index.html +1228 -1237
- package/harness/packages/server/public/landing/boot.js +2 -3
- package/harness/packages/server/public/landing/index.html +3 -367
- package/harness/packages/server/public/sessions.js +1 -0
- package/harness/packages/server/public/settings.js +1255 -1155
- package/harness/packages/server/public/settingsNav.js +1 -1
- package/harness/packages/server/public/styles.css +2341 -2867
- package/harness/packages/server/public/system.js +356 -495
- package/harness/packages/server/public/voice.js +6 -22
- package/harness/packages/server/src/auth.js +4 -15
- package/harness/packages/server/src/cli.js +1 -1
- package/harness/packages/server/src/lib/agents.js +60 -0
- package/harness/packages/server/src/lib/appearance.js +3 -22
- package/harness/packages/server/src/lib/channels/telegram/commands.js +1 -1
- package/harness/packages/server/src/lib/channels/telegram/run.js +1 -0
- package/harness/packages/server/src/lib/definitions.js +40 -0
- package/harness/packages/server/src/lib/prompt.js +11 -3
- package/harness/packages/server/src/lib/scheduler.js +4 -0
- package/harness/packages/server/src/lib/sessionView.js +1 -0
- package/harness/packages/server/src/lib/skills.js +27 -21
- package/harness/packages/server/src/lib/tasks.js +71 -23
- package/harness/packages/server/src/lib/ttsSettings.js +2 -2
- package/harness/packages/server/src/lib/workspace.js +9 -11
- package/harness/packages/server/src/routes/agents.js +88 -0
- package/harness/packages/server/src/routes/chat.js +9 -44
- package/harness/packages/server/src/routes/preview.js +5 -22
- package/harness/packages/server/src/routes/procs.js +2 -4
- package/harness/packages/server/src/routes/providers.js +7 -0
- package/harness/packages/server/src/routes/sessions.js +3 -1
- package/harness/packages/server/src/routes/tts.js +1 -1
- package/harness/packages/server/src/server.js +8 -5
- package/harness/packages/server/test/agents.test.js +194 -0
- package/harness/packages/server/test/appearance-route.test.js +3 -3
- package/harness/packages/server/test/appearance.test.js +12 -38
- package/harness/packages/server/test/chatFormat.test.js +0 -8
- package/harness/packages/server/test/consoleTabs.test.js +2 -2
- package/harness/packages/server/test/ops.test.js +8 -56
- package/harness/packages/server/test/preview.test.js +2 -42
- package/harness/packages/server/test/security.test.js +194 -370
- package/harness/packages/server/test/settingsNav.test.js +2 -2
- package/harness/packages/server/test/tts.test.js +4 -4
- package/harness/tamnora.example.json +2 -1
- package/package.json +2 -2
- package/harness/packages/cli/src/commands/eval.js +0 -186
- package/harness/packages/core/src/eval/dataset.js +0 -47
- package/harness/packages/core/src/eval/judge.js +0 -39
- package/harness/packages/core/src/eval/report.js +0 -48
- package/harness/packages/core/src/eval/runTask.js +0 -62
- package/harness/packages/core/src/eval/runner.js +0 -153
- package/harness/packages/core/src/eval/scorers.js +0 -217
- package/harness/packages/core/src/eval/task.js +0 -59
- package/harness/packages/core/src/procs/detect.js +0 -149
- package/harness/packages/core/test/eval.test.js +0 -144
- package/harness/packages/server/src/lib/advisor.js +0 -198
- package/harness/packages/server/src/routes/advisor.js +0 -79
- package/harness/packages/server/src/routes/eval.js +0 -100
- package/harness/packages/server/test/advisor.test.js +0 -249
- package/harness/scripts/example-dataset.jsonl +0 -4
|
@@ -3,7 +3,7 @@ import net from 'node:net';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { sendError } from './http.js';
|
|
6
|
-
import {
|
|
6
|
+
import { realpathDeep } from './core.js';
|
|
7
7
|
|
|
8
8
|
/** True for 127.x / ::1 / ::ffff:127.x addresses. */
|
|
9
9
|
export function isLoopback(address) {
|
|
@@ -13,19 +13,17 @@ export function isLoopback(address) {
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* "Local de verdad": el par TCP es loopback (remoteAddress, no localAddress:
|
|
16
|
-
* esa es la direccion donde bindea el server, siempre 127.0.0.1)
|
|
17
|
-
*
|
|
18
|
-
* llegan desde loopback (el cliente del tunel corre en la misma maquina), asi
|
|
19
|
-
* que la ausencia de tunel es parte del check.
|
|
16
|
+
* esa es la direccion donde bindea el server, siempre 127.0.0.1). El bridge de
|
|
17
|
+
* tailscale tambien entra por loopback, asi que sigue siendo local.
|
|
20
18
|
*/
|
|
21
19
|
export function localOk(req) {
|
|
22
|
-
return isLoopback(req?.socket?.remoteAddress)
|
|
20
|
+
return isLoopback(req?.socket?.remoteAddress);
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
/** `localOk` o 403. Devuelve true si la request puede seguir. */
|
|
26
24
|
export function requireLocal(req, res, mensaje = 'Solo disponible en conexiones locales.') {
|
|
27
25
|
if (localOk(req)) return true;
|
|
28
|
-
sendError(res, 403,
|
|
26
|
+
sendError(res, 403, mensaje);
|
|
29
27
|
return false;
|
|
30
28
|
}
|
|
31
29
|
|
|
@@ -35,7 +33,7 @@ export function requireLocal(req, res, mensaje = 'Solo disponible en conexiones
|
|
|
35
33
|
// victima contra 127.0.0.1 no puede ejecutar nada.
|
|
36
34
|
// ---------------------------------------------------------------------------
|
|
37
35
|
|
|
38
|
-
const TRUSTED_HOST_SUFFIXES = ['.ts.net', '.
|
|
36
|
+
const TRUSTED_HOST_SUFFIXES = ['.ts.net', '.localhost'];
|
|
39
37
|
|
|
40
38
|
/** Hostname (sin puerto, sin brackets de IPv6) del header `Host`. */
|
|
41
39
|
export function hostNameOf(hostHeader) {
|
|
@@ -60,9 +58,9 @@ function normalizeHost(host) {
|
|
|
60
58
|
|
|
61
59
|
/**
|
|
62
60
|
* Guarda DNS-rebinding: el `Host` debe ser un nombre que controlamos — una IP
|
|
63
|
-
* literal, localhost, el hostname de la maquina, un dominio de tailscale o
|
|
64
|
-
*
|
|
65
|
-
*
|
|
61
|
+
* literal, localhost, el hostname de la maquina, un dominio de tailscale, o una
|
|
62
|
+
* lista explicita (`config.server.allowedHosts`). Una pagina que rebindee su
|
|
63
|
+
* dominio hacia 127.0.0.1 llega con `Host: <su dominio>` y cae aca.
|
|
66
64
|
*/
|
|
67
65
|
export function trustedHost(req, allowed = []) {
|
|
68
66
|
const host = hostNameOf(req?.headers?.host);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { readJson, sendJson, sendError } from '../lib/http.js';
|
|
2
|
+
import { normalizeEffort, resolveMode } from '../lib/core.js';
|
|
3
|
+
import {
|
|
4
|
+
AGENT_ID_PATTERN,
|
|
5
|
+
agentsView,
|
|
6
|
+
findAgent,
|
|
7
|
+
writeAgent,
|
|
8
|
+
removeAgent,
|
|
9
|
+
setAgentEnabled,
|
|
10
|
+
} from '../lib/agents.js';
|
|
11
|
+
|
|
12
|
+
function cleanList(value) {
|
|
13
|
+
if (value === undefined || value === null) return null;
|
|
14
|
+
if (!Array.isArray(value)) return null;
|
|
15
|
+
return value.map((item) => String(item).trim()).filter(Boolean);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function registerAgents(route, ctx) {
|
|
19
|
+
route('GET', '/api/agents', ({ res }) => {
|
|
20
|
+
sendJson(res, 200, { agents: agentsView(ctx) });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
route('GET', '/api/agents/:id', ({ res, params }) => {
|
|
24
|
+
const agent = findAgent(ctx, params.id);
|
|
25
|
+
if (!agent) return sendError(res, 404, 'Subagente no encontrado.');
|
|
26
|
+
sendJson(res, 200, {
|
|
27
|
+
agent: {
|
|
28
|
+
id: agent.id,
|
|
29
|
+
name: agent.name || agent.id,
|
|
30
|
+
description: agent.description || '',
|
|
31
|
+
model: agent.model || '',
|
|
32
|
+
provider: agent.provider || '',
|
|
33
|
+
mode: agent.mode || '',
|
|
34
|
+
effort: agent.effort || '',
|
|
35
|
+
maxRounds: agent.maxRounds || 0,
|
|
36
|
+
tools: agent.tools,
|
|
37
|
+
skills: agent.skills,
|
|
38
|
+
body: agent.body,
|
|
39
|
+
removable: Boolean(agent.home),
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
route('PUT', '/api/agents/:id', async ({ req, res, params }) => {
|
|
45
|
+
const id = String(params.id);
|
|
46
|
+
if (!AGENT_ID_PATTERN.test(id)) return sendError(res, 400, 'Id de subagente inválido (a-z, 0-9, - y _).');
|
|
47
|
+
const body = await readJson(req);
|
|
48
|
+
if (!String(body.body ?? '').trim() && !String(body.description ?? '').trim()) {
|
|
49
|
+
return sendError(res, 400, 'El subagente necesita un cuerpo o una descripción.');
|
|
50
|
+
}
|
|
51
|
+
const mode = body.mode ? resolveMode(String(body.mode)).id : '';
|
|
52
|
+
const maxRounds = Number(body.maxRounds);
|
|
53
|
+
try {
|
|
54
|
+
await writeAgent(id, {
|
|
55
|
+
name: String(body.name ?? id).trim() || id,
|
|
56
|
+
description: String(body.description ?? '').trim(),
|
|
57
|
+
model: String(body.model ?? '').trim(),
|
|
58
|
+
provider: String(body.provider ?? '').trim(),
|
|
59
|
+
mode,
|
|
60
|
+
effort: normalizeEffort(body.effort),
|
|
61
|
+
maxRounds: Number.isFinite(maxRounds) && maxRounds > 0 ? Math.round(maxRounds) : 0,
|
|
62
|
+
tools: cleanList(body.tools),
|
|
63
|
+
skills: cleanList(body.skills),
|
|
64
|
+
body: String(body.body ?? ''),
|
|
65
|
+
});
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return sendError(res, 500, `No se pudo guardar el subagente: ${error.message}`);
|
|
68
|
+
}
|
|
69
|
+
sendJson(res, 200, { agents: agentsView(ctx) });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
route('DELETE', '/api/agents/:id', async ({ res, params }) => {
|
|
73
|
+
const agent = findAgent(ctx, params.id);
|
|
74
|
+
if (!agent) return sendError(res, 404, 'Subagente no encontrado.');
|
|
75
|
+
if (!agent.home) return sendError(res, 400, 'Ese subagente vive en el workspace y no se borra desde acá.');
|
|
76
|
+
await removeAgent(agent.id);
|
|
77
|
+
setAgentEnabled(ctx.store, agent.id, true);
|
|
78
|
+
sendJson(res, 200, { agents: agentsView(ctx) });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
route('POST', '/api/agents/:id/toggle', async ({ res, params }) => {
|
|
82
|
+
const agent = findAgent(ctx, params.id);
|
|
83
|
+
if (!agent) return sendError(res, 404, 'Subagente no encontrado.');
|
|
84
|
+
const enabled = !agentsView(ctx).find((item) => item.id === agent.id)?.enabled;
|
|
85
|
+
setAgentEnabled(ctx.store, agent.id, enabled);
|
|
86
|
+
sendJson(res, 200, { id: agent.id, enabled, agents: agentsView(ctx) });
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -3,11 +3,13 @@ import {
|
|
|
3
3
|
runAgent,
|
|
4
4
|
createTools,
|
|
5
5
|
resolveMode,
|
|
6
|
+
normalizeEffort,
|
|
6
7
|
MAX_TOOL_ROUNDS,
|
|
7
8
|
} from '../lib/core.js';
|
|
8
9
|
import { openSse, readJson, sendJson, sendError } from '../lib/http.js';
|
|
9
10
|
import { workspaceForRequest, resolveExistingDir, localOk } from '../lib/workspace.js';
|
|
10
11
|
import { createSubagentTool } from '../lib/tasks.js';
|
|
12
|
+
import { enabledAgents } from '../lib/agents.js';
|
|
11
13
|
import {
|
|
12
14
|
combineToolset,
|
|
13
15
|
combineLifecycle,
|
|
@@ -18,7 +20,6 @@ import {
|
|
|
18
20
|
toolResultPayload,
|
|
19
21
|
} from '../lib/agentRun.js';
|
|
20
22
|
import { fraseInicio, fraseFin, fraseError, createFiltroTools } from '../lib/announcer.js';
|
|
21
|
-
import { resolveAdvisor, advisorShouldRun, consultAdvisor } from '../lib/advisor.js';
|
|
22
23
|
import { maybeAutoCompact } from '../lib/compact.js';
|
|
23
24
|
import { createRulesInjector } from '../lib/rules.js';
|
|
24
25
|
import { createCheckpointer } from '../lib/checkpoints.js';
|
|
@@ -162,6 +163,8 @@ export function registerChat(route, ctx) {
|
|
|
162
163
|
const model = body.model || session?.model || ctx.effectiveModel();
|
|
163
164
|
if (!model) return sendError(res, 400, 'Falta "model" (en el cuerpo, en Ajustes o en tamnora.json).');
|
|
164
165
|
const mode = resolveMode(body.mode ?? session?.meta?.mode ?? ctx.store.getSetting('activeMode'));
|
|
166
|
+
// Esfuerzo de razonamiento: cuerpo > sesión > default global/config.
|
|
167
|
+
const effort = normalizeEffort(body.effort ?? session?.meta?.effort ?? ctx.effectiveEffort?.());
|
|
165
168
|
|
|
166
169
|
if (!sessionId) {
|
|
167
170
|
try {
|
|
@@ -174,7 +177,7 @@ export function registerChat(route, ctx) {
|
|
|
174
177
|
provider: providerId,
|
|
175
178
|
model,
|
|
176
179
|
cwd: sessionCwd,
|
|
177
|
-
meta: { mode: mode.id },
|
|
180
|
+
meta: { mode: mode.id, effort },
|
|
178
181
|
});
|
|
179
182
|
}
|
|
180
183
|
|
|
@@ -209,6 +212,7 @@ export function registerChat(route, ctx) {
|
|
|
209
212
|
provider: providerId,
|
|
210
213
|
model,
|
|
211
214
|
mode: mode.id,
|
|
215
|
+
effort,
|
|
212
216
|
cwd: sessionCwd,
|
|
213
217
|
say: fraseInicio(),
|
|
214
218
|
});
|
|
@@ -271,6 +275,8 @@ export function registerChat(route, ctx) {
|
|
|
271
275
|
providerId,
|
|
272
276
|
model,
|
|
273
277
|
parentMode: mode,
|
|
278
|
+
effort,
|
|
279
|
+
agents: enabledAgents(ctx),
|
|
274
280
|
permissions: ctx.config.permissions,
|
|
275
281
|
mcp,
|
|
276
282
|
mcpMode: ctx.effectiveMcpMode(),
|
|
@@ -347,6 +353,7 @@ export function registerChat(route, ctx) {
|
|
|
347
353
|
maxDurationMs: Number(body.maxDurationMs) || ctx.effectiveMaxDurationMs?.() || 0,
|
|
348
354
|
maxOutputTokens: Number(body.maxOutputTokens) || ctx.effectiveMaxOutputTokens?.() || 0,
|
|
349
355
|
promptCaching: ctx.effectivePromptCaching?.() ?? ctx.config.promptCaching === true,
|
|
356
|
+
reasoningEffort: effort,
|
|
350
357
|
stallWarnMs: body.stallWarnMs ?? ctx.config.stallWarnMs,
|
|
351
358
|
contextTokens,
|
|
352
359
|
onEvent(event) {
|
|
@@ -411,48 +418,6 @@ export function registerChat(route, ctx) {
|
|
|
411
418
|
durationMs,
|
|
412
419
|
say: fraseFin({ stopReason: result.stopReason, rounds: result.rounds, toolCalls, durationMs }),
|
|
413
420
|
});
|
|
414
|
-
|
|
415
|
-
// El asesor va despues del `done` pero antes del `finally`, asi el run
|
|
416
|
-
// sigue vivo y el evento llega/persiste. Puede quedarse callado.
|
|
417
|
-
const advisorOptions = resolveAdvisor(ctx.app);
|
|
418
|
-
if (
|
|
419
|
-
result.stopReason !== 'aborted' &&
|
|
420
|
-
advisorOptions.enabled &&
|
|
421
|
-
advisorShouldRun({ toolCalls, answer: result.content })
|
|
422
|
-
) {
|
|
423
|
-
// El asesor puede tener proveedor/modelo propios; si no, usa los activos.
|
|
424
|
-
let advisorProvider = provider;
|
|
425
|
-
let advisorConfig = config;
|
|
426
|
-
let advisorModel = model;
|
|
427
|
-
if (advisorOptions.provider || advisorOptions.model) {
|
|
428
|
-
const resolved = resolveProviderFor(ctx, advisorOptions.provider || providerId);
|
|
429
|
-
advisorProvider = resolved.provider;
|
|
430
|
-
advisorConfig = resolved.config;
|
|
431
|
-
advisorModel = advisorOptions.model || model;
|
|
432
|
-
}
|
|
433
|
-
const advice = await consultAdvisor({
|
|
434
|
-
provider: advisorProvider,
|
|
435
|
-
config: advisorConfig,
|
|
436
|
-
model: advisorModel,
|
|
437
|
-
signal: ctx.runs.signal(sessionId),
|
|
438
|
-
userInput: input,
|
|
439
|
-
answer: result.content,
|
|
440
|
-
messages: newMessages,
|
|
441
|
-
options: advisorOptions,
|
|
442
|
-
});
|
|
443
|
-
// Un solo mensaje (estilo Jarvis): el asesor arranca con el resumen y
|
|
444
|
-
// sigue con la alerta/consejo en el mismo texto. Una sola locucion.
|
|
445
|
-
if (advice?.speak) {
|
|
446
|
-
ctx.store.addMessage(sessionId, {
|
|
447
|
-
role: 'assistant',
|
|
448
|
-
name: 'advisor',
|
|
449
|
-
content: advice.text,
|
|
450
|
-
mode: advice.kind,
|
|
451
|
-
});
|
|
452
|
-
ctx.runs.emit(sessionId, 'advice', { text: advice.text, kind: advice.kind, say: advice.text });
|
|
453
|
-
ctx.hooks?.dispatch({ type: 'advice', sessionId, text: advice.text, kind: advice.kind });
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
421
|
} catch (error) {
|
|
457
422
|
partials.flush();
|
|
458
423
|
if (error?.name === 'AbortError') ctx.runs.emit(sessionId, 'aborted', { say: fraseFin({ stopReason: 'aborted' }) });
|
|
@@ -3,11 +3,10 @@ import { readJson, sendJson, sendError } from '../lib/http.js';
|
|
|
3
3
|
import { requireLocal, localOk } from '../lib/workspace.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Preview de un dev server para acceso remoto
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* liberar el tunel al apagar.
|
|
6
|
+
* Preview de un dev server para acceso remoto: publica el puerto en el tailnet
|
|
7
|
+
* (`tailscale serve --https=8443`, privado). El estado vive en memoria y se
|
|
8
|
+
* expone en `GET /api/preview` para que el cliente lo recupere tras una recarga.
|
|
9
|
+
* Devuelve `{ close }` para liberar el tunel al apagar.
|
|
11
10
|
*/
|
|
12
11
|
export function registerPreview(route, ctx) {
|
|
13
12
|
let tunnel = null;
|
|
@@ -49,23 +48,7 @@ export function registerPreview(route, ctx) {
|
|
|
49
48
|
try {
|
|
50
49
|
started = await startTunnel(port, provider, { preview: true, httpsPort });
|
|
51
50
|
} catch (error) {
|
|
52
|
-
|
|
53
|
-
// explicito (`allowPublic` o `preview.fallback: true`). Quien pidio un
|
|
54
|
-
// tunel privado no puede quedar expuesto sin saberlo.
|
|
55
|
-
const allowPublic = body.allowPublic === true || preview.fallback === true;
|
|
56
|
-
if (provider !== 'tunnelmole' && allowPublic) {
|
|
57
|
-
try {
|
|
58
|
-
started = await startTunnel(port, 'tunnelmole');
|
|
59
|
-
} catch (fallbackError) {
|
|
60
|
-
return sendError(res, 502, `No se pudo abrir el túnel: ${fallbackError.message}`);
|
|
61
|
-
}
|
|
62
|
-
} else {
|
|
63
|
-
return sendError(
|
|
64
|
-
res,
|
|
65
|
-
502,
|
|
66
|
-
`No se pudo abrir el túnel: ${error.message}. Si querés exponerlo con tunnelmole (público), reintentá con ese proveedor.`
|
|
67
|
-
);
|
|
68
|
-
}
|
|
51
|
+
return sendError(res, 502, `No se pudo abrir el túnel: ${error.message}`);
|
|
69
52
|
}
|
|
70
53
|
tunnel = { url: started.url, port, provider: started.provider, stop: started.stop };
|
|
71
54
|
sendJson(res, 200, { tunnel: view(tunnel) });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { loadProjectProcs, killPort, portInfo } from '../lib/core.js';
|
|
2
2
|
import { readJson, sendJson, sendError } from '../lib/http.js';
|
|
3
3
|
import { requireLocal, resolveDirOr400 } from '../lib/workspace.js';
|
|
4
4
|
|
|
@@ -11,7 +11,7 @@ export function registerProcs(route, ctx) {
|
|
|
11
11
|
if (!ctx.procsEnabled) return sendJson(res, 200, { cwd: ctx.cwd, chips: [] });
|
|
12
12
|
const dir = resolveDirOr400(res, query.get('cwd'), ctx.cwd);
|
|
13
13
|
if (!dir) return;
|
|
14
|
-
const chips =
|
|
14
|
+
const chips = loadProjectProcs(dir).map((chip) => ({ ...chip, allowed: ctx.procs.allows(chip.command) }));
|
|
15
15
|
sendJson(res, 200, { cwd: dir, chips });
|
|
16
16
|
});
|
|
17
17
|
|
|
@@ -53,8 +53,6 @@ export function registerProcs(route, ctx) {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
route('POST', '/api/procs/kill', killPortHandler);
|
|
57
|
-
|
|
58
56
|
route('POST', '/api/ports/kill', killPortHandler);
|
|
59
57
|
|
|
60
58
|
route('GET', '/api/ports/:port', async ({ req, res, params }) => {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
readApp,
|
|
8
8
|
writeApp,
|
|
9
9
|
maskSecret,
|
|
10
|
+
normalizeEffort,
|
|
10
11
|
} from '../lib/core.js';
|
|
11
12
|
import { resolveAppearance, sanitizeAppearance } from '../lib/appearance.js';
|
|
12
13
|
import { readJson, sendJson, sendError } from '../lib/http.js';
|
|
@@ -26,6 +27,7 @@ export function providerList(ctx) {
|
|
|
26
27
|
baseUrl: row?.baseUrl || '',
|
|
27
28
|
defaultBaseUrl: meta.defaultBaseUrl,
|
|
28
29
|
requiresKey: row?.requiresKey ?? meta.requiresKey,
|
|
30
|
+
reasoning: meta.reasoning ?? 'none',
|
|
29
31
|
custom: false,
|
|
30
32
|
builtin: true,
|
|
31
33
|
stored: Boolean(row),
|
|
@@ -44,6 +46,7 @@ export function providerList(ctx) {
|
|
|
44
46
|
baseUrl: row.baseUrl,
|
|
45
47
|
defaultBaseUrl: '',
|
|
46
48
|
requiresKey: row.requiresKey ?? true,
|
|
49
|
+
reasoning: 'none',
|
|
47
50
|
custom: true,
|
|
48
51
|
builtin: false,
|
|
49
52
|
stored: true,
|
|
@@ -83,6 +86,7 @@ export function registerProviders(route, ctx) {
|
|
|
83
86
|
maxDurationMs: ctx.effectiveMaxDurationMs(),
|
|
84
87
|
maxOutputTokens: ctx.effectiveMaxOutputTokens(),
|
|
85
88
|
promptCaching: ctx.effectivePromptCaching(),
|
|
89
|
+
effort: ctx.effectiveEffort(),
|
|
86
90
|
defaultCwd: ctx.store.getSetting('defaultCwd') || '',
|
|
87
91
|
strictScope: ctx.effectiveStrictScope(),
|
|
88
92
|
mcpMode: ctx.effectiveMcpMode(),
|
|
@@ -101,6 +105,9 @@ export function registerProviders(route, ctx) {
|
|
|
101
105
|
if (body.mode !== undefined) ctx.store.setSetting('activeMode', resolveMode(String(body.mode)).id);
|
|
102
106
|
if (body.defaultCwd !== undefined) ctx.store.setSetting('defaultCwd', String(body.defaultCwd ?? '').trim() || null);
|
|
103
107
|
if (body.strictScope !== undefined) ctx.store.setSetting('strictScope', Boolean(body.strictScope));
|
|
108
|
+
if (body.effort !== undefined) {
|
|
109
|
+
ctx.store.setSetting('activeEffort', normalizeEffort(body.effort) || null);
|
|
110
|
+
}
|
|
104
111
|
if (body.promptCaching !== undefined) ctx.store.setSetting('promptCaching', Boolean(body.promptCaching));
|
|
105
112
|
if (body.mcpMode !== undefined) {
|
|
106
113
|
const mode = String(body.mcpMode);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readJson, sendJson, sendError } from '../lib/http.js';
|
|
2
|
-
import { resolveMode } from '../lib/core.js';
|
|
2
|
+
import { resolveMode, normalizeEffort } from '../lib/core.js';
|
|
3
3
|
import { workspaceForRequest } from '../lib/workspace.js';
|
|
4
4
|
import { sessionView, sessionState, awaitingBySession } from '../lib/sessionView.js';
|
|
5
5
|
import { summarizeSession, compactSession, SUMMARY_INSTRUCTION } from '../lib/compact.js';
|
|
@@ -49,6 +49,7 @@ export function registerSessions(route, ctx) {
|
|
|
49
49
|
const meta = {};
|
|
50
50
|
if (body.goal) meta.goal = String(body.goal);
|
|
51
51
|
if (body.mode) meta.mode = resolveMode(String(body.mode)).id;
|
|
52
|
+
if ('effort' in body) meta.effort = normalizeEffort(body.effort);
|
|
52
53
|
const id = ctx.store.createSession({
|
|
53
54
|
title: String(body.title || 'Nueva sesión').slice(0, 80),
|
|
54
55
|
provider: body.provider || ctx.effectiveProviderId(),
|
|
@@ -105,6 +106,7 @@ export function registerSessions(route, ctx) {
|
|
|
105
106
|
const meta = {};
|
|
106
107
|
if ('goal' in body) meta.goal = body.goal ? String(body.goal) : undefined;
|
|
107
108
|
if ('mode' in body) meta.mode = resolveMode(String(body.mode)).id;
|
|
109
|
+
if ('effort' in body) meta.effort = normalizeEffort(body.effort);
|
|
108
110
|
if (Object.keys(meta).length) patch.meta = meta;
|
|
109
111
|
ctx.store.touchSession(params.id, patch);
|
|
110
112
|
sendJson(res, 200, { session: sessionView(ctx.store.getSession(params.id)) });
|
|
@@ -40,7 +40,7 @@ export function registerTts(route, ctx) {
|
|
|
40
40
|
if ('pitch' in body) next.pitch = sanearPitch(body.pitch, actual.pitch);
|
|
41
41
|
if (body.announce && typeof body.announce === 'object') {
|
|
42
42
|
const announce = { ...actual.announce };
|
|
43
|
-
for (const key of ['start', 'tools', 'end', '
|
|
43
|
+
for (const key of ['start', 'tools', 'end', 'answer']) {
|
|
44
44
|
if (key in body.announce) announce[key] = body.announce[key] === true;
|
|
45
45
|
}
|
|
46
46
|
next.announce = announce;
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
MCP_AUTO_THRESHOLD,
|
|
18
18
|
readPackageVersion,
|
|
19
19
|
scopeDirective,
|
|
20
|
+
normalizeEffort,
|
|
20
21
|
} from './lib/core.js';
|
|
21
22
|
import { sendJson, sendError } from './lib/http.js';
|
|
22
23
|
import { requireTrustedHost, requireSameOrigin } from './lib/workspace.js';
|
|
@@ -24,6 +25,7 @@ import { applySecurityHeaders, cspMode } from './lib/securityHeaders.js';
|
|
|
24
25
|
import { createPromptBuilder, DEFAULT_SYSTEM_PROMPT } from './lib/prompt.js';
|
|
25
26
|
import { profilePrompt, resolveProfile } from './lib/profile.js';
|
|
26
27
|
import { skillsBlock } from './lib/skills.js';
|
|
28
|
+
import { agentsIndexPrompt } from './lib/agents.js';
|
|
27
29
|
import { createApprovalBroker } from './lib/approvals.js';
|
|
28
30
|
import { createQuestionBroker } from './lib/questions.js';
|
|
29
31
|
import { createPresence } from './lib/presence.js';
|
|
@@ -42,6 +44,7 @@ import { registerModels } from './routes/models.js';
|
|
|
42
44
|
import { registerProviders } from './routes/providers.js';
|
|
43
45
|
import { registerMcp } from './routes/mcp.js';
|
|
44
46
|
import { registerSkills } from './routes/skills.js';
|
|
47
|
+
import { registerAgents } from './routes/agents.js';
|
|
45
48
|
import { registerMemory } from './routes/memory.js';
|
|
46
49
|
import { registerChannels } from './routes/channels.js';
|
|
47
50
|
import { registerProfile } from './routes/profile.js';
|
|
@@ -49,7 +52,6 @@ import { registerTts } from './routes/tts.js';
|
|
|
49
52
|
import { registerTranscribe } from './routes/transcribe.js';
|
|
50
53
|
import { registerUpdate } from './routes/update.js';
|
|
51
54
|
import { registerReload } from './routes/reload.js';
|
|
52
|
-
import { registerAdvisor } from './routes/advisor.js';
|
|
53
55
|
import { registerProcs } from './routes/procs.js';
|
|
54
56
|
import { registerShell } from './routes/shell.js';
|
|
55
57
|
import { registerPreview } from './routes/preview.js';
|
|
@@ -57,7 +59,6 @@ import { registerChat } from './routes/chat.js';
|
|
|
57
59
|
import { registerEvents } from './routes/events.js';
|
|
58
60
|
import { registerState } from './routes/state.js';
|
|
59
61
|
import { registerPresence } from './routes/presence.js';
|
|
60
|
-
import { registerEval } from './routes/eval.js';
|
|
61
62
|
import { registerSchedules } from './routes/schedules.js';
|
|
62
63
|
import { createScheduler } from './lib/scheduler.js';
|
|
63
64
|
import { createHookRunner } from './lib/hooks.js';
|
|
@@ -96,7 +97,7 @@ export async function createTamnoraServer({
|
|
|
96
97
|
const config = await loadConfig(cwd);
|
|
97
98
|
const app = readApp();
|
|
98
99
|
const authEnabled = Boolean(app.user && app.user.password);
|
|
99
|
-
const store = new Store(dbPath ?? databasePath(
|
|
100
|
+
const store = new Store(dbPath ?? databasePath());
|
|
100
101
|
const approvals = createApprovalBroker({ timeoutMs: config.approvalTimeoutMs });
|
|
101
102
|
const questions = createQuestionBroker({ timeoutMs: config.questionTimeoutMs });
|
|
102
103
|
const presence = createPresence();
|
|
@@ -183,6 +184,7 @@ export async function createTamnoraServer({
|
|
|
183
184
|
const effectiveMaxDurationMs = () => settingNumber('maxDurationMs', config.maxDurationMs);
|
|
184
185
|
const effectiveMaxOutputTokens = () => settingNumber('maxOutputTokens', config.maxOutputTokens);
|
|
185
186
|
const effectiveStrictScope = () => store.getSetting('strictScope') === true;
|
|
187
|
+
const effectiveEffort = () => normalizeEffort(store.getSetting('activeEffort') ?? config.effort);
|
|
186
188
|
const effectivePromptCaching = () => {
|
|
187
189
|
const stored = store.getSetting('promptCaching');
|
|
188
190
|
if (stored === null || stored === undefined) return config.promptCaching === true;
|
|
@@ -228,6 +230,7 @@ export async function createTamnoraServer({
|
|
|
228
230
|
effectiveMaxDurationMs,
|
|
229
231
|
effectiveMaxOutputTokens,
|
|
230
232
|
effectiveStrictScope,
|
|
233
|
+
effectiveEffort,
|
|
231
234
|
effectivePromptCaching,
|
|
232
235
|
effectiveMcpMode,
|
|
233
236
|
effectiveMcpPinned,
|
|
@@ -269,6 +272,7 @@ export async function createTamnoraServer({
|
|
|
269
272
|
files: config.instructions,
|
|
270
273
|
profileText: () => profilePrompt(resolveProfile(ctx.app)),
|
|
271
274
|
skillsText: () => skillsBlock(ctx),
|
|
275
|
+
agentsText: () => agentsIndexPrompt(ctx),
|
|
272
276
|
mcpText: () => ctx.mcpText(),
|
|
273
277
|
scopeText: () => scopeDirective(ctx.effectiveStrictScope()),
|
|
274
278
|
ruleFiles: config.rules ?? [],
|
|
@@ -326,12 +330,12 @@ export async function createTamnoraServer({
|
|
|
326
330
|
registerProviders(route, ctx);
|
|
327
331
|
registerMcp(route, ctx);
|
|
328
332
|
registerSkills(route, ctx);
|
|
333
|
+
registerAgents(route, ctx);
|
|
329
334
|
registerMemory(route, ctx);
|
|
330
335
|
registerChannels(route, ctx);
|
|
331
336
|
registerProfile(route, ctx);
|
|
332
337
|
registerTts(route, ctx);
|
|
333
338
|
registerTranscribe(route, ctx);
|
|
334
|
-
registerAdvisor(route, ctx);
|
|
335
339
|
registerUpdate(route, ctx);
|
|
336
340
|
registerReload(route, ctx);
|
|
337
341
|
registerProcs(route, ctx);
|
|
@@ -341,7 +345,6 @@ export async function createTamnoraServer({
|
|
|
341
345
|
registerEvents(route, ctx);
|
|
342
346
|
registerState(route, ctx);
|
|
343
347
|
registerPresence(route, ctx);
|
|
344
|
-
registerEval(route, ctx);
|
|
345
348
|
registerSchedules(route, ctx);
|
|
346
349
|
|
|
347
350
|
route('GET', '/api/hooks', ({ res }) => {
|