@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.
Files changed (110) hide show
  1. package/CHANGELOG.md +18 -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 +20 -4
  7. package/harness/packages/cli/src/commands/run.js +6 -2
  8. package/harness/packages/cli/src/commands/server.js +1 -1
  9. package/harness/packages/cli/src/commands/tui.js +6 -2
  10. package/harness/packages/cli/src/lib/runtime.js +2 -0
  11. package/harness/packages/cli/src/lib/util.js +2 -2
  12. package/harness/packages/cli/src/tui/app.js +24 -4
  13. package/harness/packages/cli/src/tui/layout.js +1 -1
  14. package/harness/packages/cli/test/tunnel.test.js +5 -32
  15. package/harness/packages/core/src/agent/runAgent.js +5 -2
  16. package/harness/packages/core/src/agents/index.js +113 -0
  17. package/harness/packages/core/src/config/app.js +0 -4
  18. package/harness/packages/core/src/config/home.js +1 -20
  19. package/harness/packages/core/src/config/index.js +6 -8
  20. package/harness/packages/core/src/extensions/load.js +22 -0
  21. package/harness/packages/core/src/frontmatter.js +32 -0
  22. package/harness/packages/core/src/hooks/index.js +2 -16
  23. package/harness/packages/core/src/index.js +12 -14
  24. package/harness/packages/core/src/plugins/index.js +2 -16
  25. package/harness/packages/core/src/procs/project.js +68 -0
  26. package/harness/packages/core/src/procs/static.js +17 -0
  27. package/harness/packages/core/src/providers/index.js +3 -1
  28. package/harness/packages/core/src/providers/ollama.js +4 -1
  29. package/harness/packages/core/src/providers/openaiCompatible.js +6 -1
  30. package/harness/packages/core/src/providers/openrouter.js +5 -1
  31. package/harness/packages/core/src/providers/presets.js +4 -3
  32. package/harness/packages/core/src/providers/reasoning.js +58 -0
  33. package/harness/packages/core/src/rules/index.js +8 -27
  34. package/harness/packages/core/src/skills/index.js +8 -19
  35. package/harness/packages/core/src/storage/sqlite.js +595 -690
  36. package/harness/packages/core/src/tunnel.js +9 -147
  37. package/harness/packages/core/test/agents.test.js +111 -0
  38. package/harness/packages/core/test/home.test.js +2 -35
  39. package/harness/packages/core/test/procs.test.js +3 -64
  40. package/harness/packages/core/test/reasoning.test.js +43 -0
  41. package/harness/packages/core/test/storage.test.js +1 -12
  42. package/harness/packages/server/public/app.js +39 -19
  43. package/harness/packages/server/public/appearance.js +2 -56
  44. package/harness/packages/server/public/boot.js +2 -2
  45. package/harness/packages/server/public/cards.js +2 -21
  46. package/harness/packages/server/public/chat.js +1 -16
  47. package/harness/packages/server/public/chatFormat.js +0 -11
  48. package/harness/packages/server/public/chrome.js +95 -5
  49. package/harness/packages/server/public/commands.js +1 -2
  50. package/harness/packages/server/public/core.js +1 -1
  51. package/harness/packages/server/public/explorer.js +7 -10
  52. package/harness/packages/server/public/icons (1).zip +0 -0
  53. package/harness/packages/server/public/index.html +1228 -1237
  54. package/harness/packages/server/public/landing/boot.js +2 -3
  55. package/harness/packages/server/public/landing/index.html +3 -367
  56. package/harness/packages/server/public/sessions.js +1 -0
  57. package/harness/packages/server/public/settings.js +1255 -1155
  58. package/harness/packages/server/public/settingsNav.js +1 -1
  59. package/harness/packages/server/public/styles.css +2341 -2867
  60. package/harness/packages/server/public/system.js +356 -495
  61. package/harness/packages/server/public/voice.js +6 -22
  62. package/harness/packages/server/src/auth.js +4 -15
  63. package/harness/packages/server/src/cli.js +1 -1
  64. package/harness/packages/server/src/lib/agents.js +60 -0
  65. package/harness/packages/server/src/lib/appearance.js +3 -22
  66. package/harness/packages/server/src/lib/channels/telegram/commands.js +1 -1
  67. package/harness/packages/server/src/lib/channels/telegram/run.js +1 -0
  68. package/harness/packages/server/src/lib/definitions.js +40 -0
  69. package/harness/packages/server/src/lib/prompt.js +11 -3
  70. package/harness/packages/server/src/lib/scheduler.js +4 -0
  71. package/harness/packages/server/src/lib/sessionView.js +1 -0
  72. package/harness/packages/server/src/lib/skills.js +27 -21
  73. package/harness/packages/server/src/lib/tasks.js +71 -23
  74. package/harness/packages/server/src/lib/ttsSettings.js +2 -2
  75. package/harness/packages/server/src/lib/workspace.js +9 -11
  76. package/harness/packages/server/src/routes/agents.js +88 -0
  77. package/harness/packages/server/src/routes/chat.js +9 -44
  78. package/harness/packages/server/src/routes/preview.js +5 -22
  79. package/harness/packages/server/src/routes/procs.js +2 -4
  80. package/harness/packages/server/src/routes/providers.js +7 -0
  81. package/harness/packages/server/src/routes/sessions.js +3 -1
  82. package/harness/packages/server/src/routes/tts.js +1 -1
  83. package/harness/packages/server/src/server.js +8 -5
  84. package/harness/packages/server/test/agents.test.js +194 -0
  85. package/harness/packages/server/test/appearance-route.test.js +3 -3
  86. package/harness/packages/server/test/appearance.test.js +12 -38
  87. package/harness/packages/server/test/chatFormat.test.js +0 -8
  88. package/harness/packages/server/test/consoleTabs.test.js +2 -2
  89. package/harness/packages/server/test/ops.test.js +8 -56
  90. package/harness/packages/server/test/preview.test.js +2 -42
  91. package/harness/packages/server/test/security.test.js +194 -370
  92. package/harness/packages/server/test/settingsNav.test.js +2 -2
  93. package/harness/packages/server/test/tts.test.js +4 -4
  94. package/harness/tamnora.example.json +2 -1
  95. package/package.json +2 -2
  96. package/harness/packages/cli/src/commands/eval.js +0 -186
  97. package/harness/packages/core/src/eval/dataset.js +0 -47
  98. package/harness/packages/core/src/eval/judge.js +0 -39
  99. package/harness/packages/core/src/eval/report.js +0 -48
  100. package/harness/packages/core/src/eval/runTask.js +0 -62
  101. package/harness/packages/core/src/eval/runner.js +0 -153
  102. package/harness/packages/core/src/eval/scorers.js +0 -217
  103. package/harness/packages/core/src/eval/task.js +0 -59
  104. package/harness/packages/core/src/procs/detect.js +0 -149
  105. package/harness/packages/core/test/eval.test.js +0 -144
  106. package/harness/packages/server/src/lib/advisor.js +0 -198
  107. package/harness/packages/server/src/routes/advisor.js +0 -79
  108. package/harness/packages/server/src/routes/eval.js +0 -100
  109. package/harness/packages/server/test/advisor.test.js +0 -249
  110. 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 { tunnelState, realpathDeep } from './core.js';
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) y no hay un
17
- * tunel publico activo. Con el tunel abierto las peticiones remotas TAMBIEN
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) && !tunnelState();
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, tunnelState() ? `${mensaje} Hay un túnel público activo.` : mensaje);
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', '.tunnelmole.net', '.localhost'];
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 del
64
- * tunel, o una lista explicita (`config.server.allowedHosts`). Una pagina que
65
- * rebindee su dominio hacia 127.0.0.1 llega con `Host: <su dominio>` y cae aca.
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. Con `tailscale` publica el puerto
7
- * en el tailnet (`tailscale serve --https=8443`, privado); con `tunnelmole` abre
8
- * un tunel publico. El estado vive en memoria y se expone en `GET /api/preview`
9
- * para que el cliente lo recupere tras una recarga. Devuelve `{ close }` para
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
- // El fallback a tunnelmole es un tunel PUBLICO: solo con consentimiento
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 { detectProcs, killPort, portInfo } from '../lib/core.js';
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 = detectProcs(dir).map((chip) => ({ ...chip, allowed: ctx.procs.allows(chip.command) }));
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', 'advice', 'answer', 'summary']) {
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(cwd));
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 }) => {