@danieltmn/tamnora 0.25.1 → 0.26.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 (99) 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 +3 -3
  7. package/harness/packages/cli/src/commands/run.js +1 -1
  8. package/harness/packages/cli/src/commands/server.js +1 -1
  9. package/harness/packages/cli/src/commands/tui.js +1 -1
  10. package/harness/packages/cli/src/lib/util.js +2 -2
  11. package/harness/packages/cli/src/tui/app.js +2 -2
  12. package/harness/packages/cli/test/tunnel.test.js +5 -32
  13. package/harness/packages/core/src/agents/index.js +13 -41
  14. package/harness/packages/core/src/config/app.js +0 -4
  15. package/harness/packages/core/src/config/home.js +1 -20
  16. package/harness/packages/core/src/config/index.js +5 -8
  17. package/harness/packages/core/src/extensions/load.js +22 -0
  18. package/harness/packages/core/src/frontmatter.js +32 -0
  19. package/harness/packages/core/src/hooks/index.js +2 -16
  20. package/harness/packages/core/src/index.js +2 -14
  21. package/harness/packages/core/src/plugins/index.js +2 -16
  22. package/harness/packages/core/src/procs/project.js +68 -0
  23. package/harness/packages/core/src/procs/static.js +17 -0
  24. package/harness/packages/core/src/rules/index.js +8 -27
  25. package/harness/packages/core/src/skills/index.js +8 -19
  26. package/harness/packages/core/src/storage/sqlite.js +595 -690
  27. package/harness/packages/core/src/tunnel.js +9 -147
  28. package/harness/packages/core/test/home.test.js +2 -35
  29. package/harness/packages/core/test/procs.test.js +3 -64
  30. package/harness/packages/core/test/storage.test.js +1 -12
  31. package/harness/packages/server/public/app.js +2 -19
  32. package/harness/packages/server/public/appearance.js +2 -56
  33. package/harness/packages/server/public/boot.js +2 -2
  34. package/harness/packages/server/public/cards.js +2 -21
  35. package/harness/packages/server/public/chat.js +0 -16
  36. package/harness/packages/server/public/chatFormat.js +0 -11
  37. package/harness/packages/server/public/chrome.js +2 -5
  38. package/harness/packages/server/public/commands.js +1 -2
  39. package/harness/packages/server/public/explorer.js +7 -10
  40. package/harness/packages/server/public/icons/icon-128x128.png +0 -0
  41. package/harness/packages/server/public/icons/icon-144x144.png +0 -0
  42. package/harness/packages/server/public/icons/icon-152x152.png +0 -0
  43. package/harness/packages/server/public/icons/icon-192x192.png +0 -0
  44. package/harness/packages/server/public/icons/icon-384x384.png +0 -0
  45. package/harness/packages/server/public/icons/icon-512x512.png +0 -0
  46. package/harness/packages/server/public/icons/icon-72x72.png +0 -0
  47. package/harness/packages/server/public/icons/icon-96x96.png +0 -0
  48. package/harness/packages/server/public/icons/logo.png +0 -0
  49. package/harness/packages/server/public/icons/logo_light.png +0 -0
  50. package/harness/packages/server/public/index.html +1228 -1326
  51. package/harness/packages/server/public/landing/boot.js +2 -3
  52. package/harness/packages/server/public/landing/index.html +3 -367
  53. package/harness/packages/server/public/logo.png +0 -0
  54. package/harness/packages/server/public/settings.js +1255 -1322
  55. package/harness/packages/server/public/settingsNav.js +0 -1
  56. package/harness/packages/server/public/styles.css +2341 -2867
  57. package/harness/packages/server/public/system.js +356 -495
  58. package/harness/packages/server/public/voice.js +6 -22
  59. package/harness/packages/server/src/auth.js +4 -15
  60. package/harness/packages/server/src/cli.js +1 -1
  61. package/harness/packages/server/src/lib/agents.js +13 -27
  62. package/harness/packages/server/src/lib/appearance.js +3 -22
  63. package/harness/packages/server/src/lib/channels/telegram/commands.js +1 -1
  64. package/harness/packages/server/src/lib/definitions.js +40 -0
  65. package/harness/packages/server/src/lib/skills.js +14 -21
  66. package/harness/packages/server/src/lib/ttsSettings.js +2 -2
  67. package/harness/packages/server/src/lib/workspace.js +9 -11
  68. package/harness/packages/server/src/routes/chat.js +0 -43
  69. package/harness/packages/server/src/routes/preview.js +5 -22
  70. package/harness/packages/server/src/routes/procs.js +2 -4
  71. package/harness/packages/server/src/routes/tts.js +1 -1
  72. package/harness/packages/server/src/server.js +1 -5
  73. package/harness/packages/server/test/appearance-route.test.js +3 -3
  74. package/harness/packages/server/test/appearance.test.js +12 -38
  75. package/harness/packages/server/test/chatFormat.test.js +0 -8
  76. package/harness/packages/server/test/consoleTabs.test.js +4 -4
  77. package/harness/packages/server/test/ops.test.js +8 -56
  78. package/harness/packages/server/test/preview.test.js +2 -42
  79. package/harness/packages/server/test/security.test.js +194 -370
  80. package/harness/packages/server/test/settingsNav.test.js +2 -2
  81. package/harness/packages/server/test/tts.test.js +4 -4
  82. package/harness/tamnora.example.json +1 -1
  83. package/package.json +2 -2
  84. package/harness/packages/cli/src/commands/eval.js +0 -186
  85. package/harness/packages/core/src/eval/dataset.js +0 -47
  86. package/harness/packages/core/src/eval/judge.js +0 -39
  87. package/harness/packages/core/src/eval/report.js +0 -48
  88. package/harness/packages/core/src/eval/runTask.js +0 -62
  89. package/harness/packages/core/src/eval/runner.js +0 -153
  90. package/harness/packages/core/src/eval/scorers.js +0 -217
  91. package/harness/packages/core/src/eval/task.js +0 -59
  92. package/harness/packages/core/src/procs/detect.js +0 -149
  93. package/harness/packages/core/test/eval.test.js +0 -144
  94. package/harness/packages/server/public/icons (1).zip +0 -0
  95. package/harness/packages/server/src/lib/advisor.js +0 -198
  96. package/harness/packages/server/src/routes/advisor.js +0 -79
  97. package/harness/packages/server/src/routes/eval.js +0 -100
  98. package/harness/packages/server/test/advisor.test.js +0 -249
  99. package/harness/scripts/example-dataset.jsonl +0 -4
@@ -1,186 +0,0 @@
1
- import path from 'node:path';
2
- import {
3
- loadConfig,
4
- loadInstructions,
5
- loadDataset,
6
- runDataset,
7
- summarize,
8
- createJudge,
9
- Store,
10
- } from '../lib/core.js';
11
- import { parse, databasePath, num } from '../lib/util.js';
12
- import { buildSystemPrompt, resolveCliProvider } from '../lib/runtime.js';
13
- import { color } from '../ui/color.js';
14
-
15
- const OPTIONS = {
16
- provider: { type: 'string' },
17
- model: { type: 'string', multiple: true },
18
- dir: { type: 'string' },
19
- trials: { type: 'string' },
20
- concurrency: { type: 'string' },
21
- tools: { type: 'string' },
22
- 'no-tools': { type: 'boolean' },
23
- 'judge-model': { type: 'string' },
24
- yes: { type: 'boolean', short: 'y' },
25
- json: { type: 'boolean' },
26
- help: { type: 'boolean', short: 'h' },
27
- };
28
-
29
- function pct(value) {
30
- return `${Math.round(value * 100)}%`;
31
- }
32
-
33
- export async function evalCommand(argv) {
34
- const { values, positionals } = parse(argv, OPTIONS);
35
- if (values.help) {
36
- printHelp();
37
- return 0;
38
- }
39
-
40
- const datasetFile = positionals[0];
41
- if (!datasetFile) {
42
- console.error(color.red('Falta el dataset. Uso: tamnora eval <dataset.jsonl> [--model <id>]'));
43
- return 1;
44
- }
45
-
46
- const cwd = values.dir ? path.resolve(values.dir) : process.cwd();
47
- const config = await loadConfig(cwd);
48
-
49
- const tasks = await loadDataset(path.resolve(cwd, datasetFile));
50
- if (!tasks.length) {
51
- console.error(color.red('El dataset está vacío.'));
52
- return 1;
53
- }
54
-
55
- const instructions = await loadInstructions(cwd, config.instructions);
56
- const systemPrompt = buildSystemPrompt(instructions);
57
- const trials = num(values.trials, 1);
58
- const concurrency = num(values.concurrency, 1);
59
-
60
- const store = new Store(databasePath(cwd));
61
- const resolved = resolveCliProvider(store, config, { provider: values.provider, model: values.model });
62
- const providerId = resolved.providerId;
63
- const baseUrl = resolved.baseUrl;
64
- const apiKey = resolved.apiKey;
65
- const customProviders = resolved.customProviders;
66
- const models = (values.model?.length ? values.model : [resolved.model]).filter(Boolean);
67
- if (!models.length) {
68
- console.error(color.red('Falta el modelo (usá --model, Ajustes o "model" en tamnora.json).'));
69
- store.close();
70
- return 1;
71
- }
72
-
73
- const judge = createJudge({
74
- providerId,
75
- model: values['judge-model'] || config.judgeModel,
76
- baseUrl,
77
- apiKey,
78
- customProviders,
79
- });
80
-
81
- const enabledTools = values['no-tools']
82
- ? []
83
- : values.tools
84
- ? String(values.tools).split(',').map((s) => s.trim()).filter(Boolean)
85
- : Array.isArray(config.tools) && config.tools.length
86
- ? config.tools
87
- : undefined;
88
-
89
- const runId = store.createRun({
90
- dataset: datasetFile,
91
- config: { providerId, models, trials, concurrency, tools: enabledTools ?? 'all' },
92
- });
93
- const startedAt = Date.now();
94
-
95
- if (!values.json) {
96
- process.stderr.write(
97
- color.dim(
98
- `run ${runId} · ${tasks.length} tareas × ${models.length} modelos × ${trials} trials · concurrencia ${concurrency}\n`
99
- )
100
- );
101
- }
102
-
103
- const results = await runDataset({
104
- tasks,
105
- models,
106
- providerId,
107
- baseUrl,
108
- apiKey,
109
- customProviders,
110
- systemPrompt,
111
- rootDir: cwd,
112
- trials,
113
- concurrency,
114
- enabledTools,
115
- permissions: config.permissions,
116
- yes: Boolean(values.yes),
117
- judge,
118
- store,
119
- runId,
120
- onProgress(entry) {
121
- if (values.json) return;
122
- const mark = entry.error ? color.red('✗') : entry.passed ? color.green('✓') : color.yellow('·');
123
- const scorers = (entry.trace?.scorers ?? []).map((s) => `${s.scorer}:${s.passed ? 'ok' : 'no'}`).join(' ');
124
- const suffix = entry.error ? ` — ${entry.error}` : scorers ? ` [${scorers}]` : '';
125
- process.stdout.write(`${mark} ${entry.taskId} · ${entry.model}${suffix}\n`);
126
- },
127
- });
128
-
129
- const summary = summarize(results);
130
- summary.durationMs = Date.now() - startedAt;
131
- const status = summary.errors && summary.passed === 0 ? 'failed' : summary.errors ? 'done_with_errors' : 'done';
132
- store.finishRun(runId, { status, summary });
133
- store.close();
134
-
135
- if (values.json) {
136
- process.stdout.write(`${JSON.stringify({ runId, summary }, null, 2)}\n`);
137
- } else {
138
- printReport(summary, runId);
139
- }
140
-
141
- return summary.passed === 0 && summary.errors > 0 ? 1 : summary.passed < summary.total ? 1 : 0;
142
- }
143
-
144
- function printReport(summary, runId) {
145
- const lines = [];
146
- lines.push('');
147
- lines.push(color.bold(`Resultados (run ${runId})`));
148
- lines.push(color.dim('modelo'.padEnd(34) + 'pasó total pass errores'));
149
- for (const m of summary.models) {
150
- lines.push(
151
- `${String(m.model).slice(0, 33).padEnd(34)}${String(m.passed).padStart(4)}${String(m.total).padStart(7)}${pct(m.passRate).padStart(6)}${String(m.errors).padStart(9)}`
152
- );
153
- }
154
- if (summary.scorers.length) {
155
- lines.push('');
156
- lines.push(color.dim('scorer'.padEnd(34) + 'pasó total pass'));
157
- for (const s of summary.scorers) {
158
- lines.push(
159
- `${String(s.scorer).slice(0, 33).padEnd(34)}${String(s.passed).padStart(4)}${String(s.total).padStart(7)}${pct(s.passRate).padStart(6)}`
160
- );
161
- }
162
- }
163
- lines.push('');
164
- lines.push(
165
- color.dim(
166
- `total ${summary.total} · ${summary.passed} ok · ${summary.errors} errores · ${(summary.durationMs / 1000).toFixed(1)}s`
167
- )
168
- );
169
- process.stderr.write(`${lines.join('\n')}\n`);
170
- }
171
-
172
- function printHelp() {
173
- console.log(`Uso: tamnora eval <dataset.jsonl> [opciones]
174
-
175
- Opciones:
176
- --provider <id> Proveedor
177
- --model <id> Modelo (repetible para comparar varios)
178
- --trials <n> Repeticiones por tarea/modelo (default: 1)
179
- --concurrency <n> Tareas en paralelo (default: 1)
180
- --judge-model <id> Modelo para el scorer llmJudge
181
- --dir <path> Directorio del proyecto
182
- --tools <lista> Tools habilitadas (coma)
183
- --no-tools Deshabilitar tools
184
- -y, --yes Aprobar automáticamente permisos "ask"
185
- --json Salida JSON`);
186
- }
@@ -1,47 +0,0 @@
1
- import { readFile } from 'node:fs/promises';
2
-
3
- /**
4
- * Load an evaluation dataset. Supports:
5
- * - a JSON array of tasks
6
- * - a single JSON object ({ tasks: [...] } or one task)
7
- * - JSONL (one task per line)
8
- *
9
- * @param {string} file
10
- * @returns {Promise<object[]>}
11
- */
12
- export async function loadDataset(file) {
13
- const raw = await readFile(file, 'utf8');
14
- const trimmed = raw.trim();
15
- if (!trimmed) return [];
16
-
17
- // JSONL: each non-empty line is a standalone JSON object
18
- if (!trimmed.startsWith('[') && !trimmed.startsWith('{')) {
19
- return parseJsonl(trimmed, file);
20
- }
21
-
22
- let parsed;
23
- try {
24
- parsed = JSON.parse(trimmed);
25
- } catch {
26
- // Fall back to JSONL (multi-line pretty JSON would have parsed above)
27
- return parseJsonl(trimmed, file);
28
- }
29
-
30
- if (Array.isArray(parsed)) return parsed;
31
- if (Array.isArray(parsed.tasks)) return parsed.tasks;
32
- return [parsed];
33
- }
34
-
35
- function parseJsonl(text, file) {
36
- const tasks = [];
37
- const lines = text.split(/\r?\n/);
38
- lines.forEach((line, index) => {
39
- if (!line.trim()) return;
40
- try {
41
- tasks.push(JSON.parse(line));
42
- } catch (err) {
43
- throw new Error(`Línea ${index + 1} inválida en ${file}: ${err.message}`);
44
- }
45
- });
46
- return tasks;
47
- }
@@ -1,39 +0,0 @@
1
- import { resolveProviderRuntime } from '../providers/index.js';
2
- import { extractJson } from './scorers.js';
3
-
4
- /**
5
- * Build an LLM-as-judge function. Returns null when no judge model is set.
6
- * The judge model receives the criteria and the candidate output and must
7
- * reply with JSON: { "passed": boolean, "reason": string }.
8
- *
9
- * @param {{providerId?:string, model?:string, baseUrl?:string, apiKey?:string, customProviders?:object[]}} opts
10
- */
11
- export function createJudge({ providerId, model, baseUrl, apiKey, customProviders = [] } = {}) {
12
- if (!model) return null;
13
- const { provider, config } = resolveProviderRuntime({ providerId, apiKey, baseUrl, customProviders });
14
-
15
- return async ({ criteria, output, input }) => {
16
- const prompt =
17
- 'Sos un evaluador estricto. Decidí si la RESPUESTA cumple el CRITERIO.\n\n' +
18
- `CRITERIO: ${criteria}\n` +
19
- `ENTRADA: ${input ?? ''}\n` +
20
- `RESPUESTA: ${output ?? ''}\n\n` +
21
- 'Respondé SOLO un JSON: {"passed": true|false, "reason": "breve"}';
22
-
23
- try {
24
- const result = await provider.streamChat(config, {
25
- model,
26
- messages: [{ role: 'user', content: prompt }],
27
- tools: [],
28
- onEvent: () => {},
29
- });
30
- const { value, error } = extractJson(result.content ?? '');
31
- if (error || typeof value?.passed !== 'boolean') {
32
- return { passed: false, detail: `juez: respuesta inválida (${error ?? 'sin "passed"'})` };
33
- }
34
- return { passed: value.passed, detail: value.reason ?? 'juez' };
35
- } catch (err) {
36
- return { passed: false, detail: `juez error: ${err.message}` };
37
- }
38
- };
39
- }
@@ -1,48 +0,0 @@
1
- /**
2
- * Aggregate evaluation results into per-model and per-scorer summaries.
3
- * @param {object[]} results
4
- */
5
- export function summarize(results) {
6
- const byModel = new Map();
7
- const byScorer = new Map();
8
- let passed = 0;
9
- let errors = 0;
10
- let durationMs = 0;
11
-
12
- for (const result of results) {
13
- passed += result.passed ? 1 : 0;
14
- errors += result.error ? 1 : 0;
15
- durationMs += result.durationMs ?? 0;
16
-
17
- const model = byModel.get(result.model) ?? {
18
- model: result.model,
19
- total: 0,
20
- passed: 0,
21
- errors: 0,
22
- durationMs: 0,
23
- };
24
- model.total += 1;
25
- model.passed += result.passed ? 1 : 0;
26
- model.errors += result.error ? 1 : 0;
27
- model.durationMs += result.durationMs ?? 0;
28
- byModel.set(result.model, model);
29
-
30
- for (const detail of result.trace?.scorers ?? []) {
31
- const scorer = byScorer.get(detail.scorer) ?? { scorer: detail.scorer, total: 0, passed: 0 };
32
- scorer.total += 1;
33
- scorer.passed += detail.passed ? 1 : 0;
34
- byScorer.set(detail.scorer, scorer);
35
- }
36
- }
37
-
38
- const models = [...byModel.values()].map((m) => ({
39
- ...m,
40
- passRate: m.total ? m.passed / m.total : 0,
41
- }));
42
- const scorers = [...byScorer.values()].map((s) => ({
43
- ...s,
44
- passRate: s.total ? s.passed / s.total : 0,
45
- }));
46
-
47
- return { total: results.length, passed, errors, durationMs, models, scorers };
48
- }
@@ -1,62 +0,0 @@
1
- import { resolveProviderRuntime } from '../providers/index.js';
2
- import { runAgent } from '../agent/runAgent.js';
3
- import { buildMcpRegistry } from '../mcp/index.js';
4
- import { resolveApiKey } from '../config/secrets.js';
5
-
6
- export { resolveApiKey } from '../config/secrets.js';
7
-
8
- /**
9
- * Run a single evaluation task headlessly.
10
- *
11
- * @param {object} task
12
- * @param {string} [task.provider]
13
- * @param {string} [task.model]
14
- * @param {string} [task.input]
15
- * @param {string} [task.systemPrompt]
16
- * @param {object[]} [task.tools] - explicit OpenAI-schema tool definitions
17
- * @param {object[]} [task.mcpServers]
18
- * @param {number} [task.maxRounds]
19
- * @param {object} [opts]
20
- * @param {Record<string,string>} [opts.env]
21
- * @param {Function} [opts.onEvent]
22
- * @param {AbortSignal} [opts.signal]
23
- * @param {Function} [opts.execute] - tool executor
24
- */
25
- export async function runTask(task, { env = {}, onEvent, signal, execute } = {}) {
26
- const { provider, config } = resolveProviderRuntime({
27
- providerId: task.provider,
28
- apiKey: resolveApiKey(task.apiKey, env),
29
- baseUrl: task.baseUrl,
30
- customProviders: task.customProviders ?? [],
31
- });
32
-
33
- let tools = Array.isArray(task.tools) && task.tools[0]?.type === 'function' ? [...task.tools] : [];
34
- let mcp = null;
35
- if (task.mcpServers?.length) {
36
- mcp = await buildMcpRegistry(task.mcpServers, { signal });
37
- tools = [...tools, ...mcp.definitions];
38
- }
39
-
40
- const messages = [];
41
- if (task.systemPrompt) messages.push({ role: 'system', content: task.systemPrompt });
42
- messages.push({ role: 'user', content: task.input });
43
-
44
- try {
45
- const result = await runAgent({
46
- provider,
47
- config,
48
- model: task.model,
49
- messages,
50
- tools,
51
- execute,
52
- toolContext: { ...(task.toolContext ?? {}), mcp },
53
- maxRounds: task.maxRounds,
54
- onEvent,
55
- signal,
56
- });
57
-
58
- return { ...result, provider: provider.id, model: task.model };
59
- } finally {
60
- mcp?.close?.();
61
- }
62
- }
@@ -1,153 +0,0 @@
1
- import { createTaskWorkspace } from './task.js';
2
- import { runTask } from './runTask.js';
3
- import { runScorers } from './scorers.js';
4
- import { createTools } from '../tools/index.js';
5
-
6
- function safeParse(value) {
7
- if (value && typeof value === 'object') return value;
8
- if (typeof value !== 'string') return {};
9
- try {
10
- return JSON.parse(value);
11
- } catch {
12
- return {};
13
- }
14
- }
15
-
16
- export function extractToolCalls(messages = []) {
17
- const calls = [];
18
- for (const message of messages) {
19
- if (message.role === 'assistant' && Array.isArray(message.tool_calls)) {
20
- for (const call of message.tool_calls) {
21
- calls.push({
22
- name: call.function?.name ?? call.name,
23
- args: safeParse(call.function?.arguments ?? call.arguments),
24
- });
25
- }
26
- }
27
- }
28
- return calls;
29
- }
30
-
31
- async function pool(items, limit, worker) {
32
- const results = new Array(items.length);
33
- let index = 0;
34
- const size = Math.max(1, Math.min(limit || 1, items.length || 1));
35
- const runners = Array.from({ length: size }, async () => {
36
- while (true) {
37
- const i = index++;
38
- if (i >= items.length) break;
39
- results[i] = await worker(items[i], i);
40
- }
41
- });
42
- await Promise.all(runners);
43
- return results;
44
- }
45
-
46
- /**
47
- * Run an evaluation dataset: tasks × models × trials, each in an isolated
48
- * workspace, scored and (optionally) persisted.
49
- *
50
- * @param {object} opts
51
- * @param {object[]} opts.tasks
52
- * @param {string[]} opts.models
53
- * @param {string} opts.providerId
54
- * @param {string} [opts.baseUrl]
55
- * @param {string} [opts.apiKey]
56
- * @param {object[]} [opts.customProviders]
57
- * @param {string} [opts.systemPrompt]
58
- * @param {string} [opts.rootDir] - base dir for task fixtures
59
- * @param {number} [opts.trials]
60
- * @param {number} [opts.concurrency]
61
- * @param {string[]} [opts.enabledTools]
62
- * @param {object} [opts.permissions]
63
- * @param {boolean} [opts.yes]
64
- * @param {Function} [opts.judge]
65
- * @param {object} [opts.store]
66
- * @param {string} [opts.runId]
67
- * @param {Function} [opts.onProgress]
68
- * @param {AbortSignal} [opts.signal]
69
- */
70
- export async function runDataset({
71
- tasks,
72
- models,
73
- providerId,
74
- baseUrl,
75
- apiKey,
76
- customProviders = [],
77
- systemPrompt,
78
- rootDir = process.cwd(),
79
- trials = 1,
80
- concurrency = 1,
81
- enabledTools,
82
- permissions,
83
- yes = false,
84
- judge,
85
- store,
86
- runId,
87
- onProgress,
88
- signal,
89
- }) {
90
- const jobs = [];
91
- for (const task of tasks) {
92
- for (const model of models) {
93
- for (let trial = 0; trial < trials; trial++) jobs.push({ task, model, trial });
94
- }
95
- }
96
-
97
- return pool(jobs, concurrency, async ({ task, model, trial }) => {
98
- const workspace = await createTaskWorkspace(task, { rootDir });
99
- const tools = createTools({ root: workspace.dir, enabled: enabledTools, permissions, yes });
100
- const startedAt = Date.now();
101
- let result = null;
102
- let error = null;
103
-
104
- try {
105
- result = await runTask(
106
- {
107
- ...task,
108
- provider: providerId,
109
- model,
110
- baseUrl: task.baseUrl ?? baseUrl,
111
- apiKey: task.apiKey ?? apiKey,
112
- customProviders: task.customProviders ?? customProviders,
113
- systemPrompt: task.systemPrompt || systemPrompt,
114
- tools: tools.definitions.length ? tools.definitions : undefined,
115
- },
116
- { env: process.env, execute: tools.execute, signal }
117
- );
118
- } catch (err) {
119
- error = err.message;
120
- }
121
-
122
- const toolCalls = extractToolCalls(result?.messages ?? []);
123
- let scoring = { passed: false, score: 0, details: [] };
124
- if (!error) {
125
- scoring = await runScorers(task, {
126
- output: result.content,
127
- toolCalls,
128
- workspaceDir: workspace.dir,
129
- input: task.input,
130
- judge,
131
- });
132
- }
133
- await workspace.cleanup();
134
-
135
- const entry = {
136
- runId,
137
- taskId: task.id ?? String(task.input ?? '').slice(0, 40),
138
- model,
139
- trial,
140
- output: result?.content ?? null,
141
- passed: Boolean(scoring.passed),
142
- score: scoring.score,
143
- durationMs: Date.now() - startedAt,
144
- error,
145
- toolCalls,
146
- trace: { rounds: result?.rounds, stopReason: result?.stopReason, scorers: scoring.details, error },
147
- };
148
-
149
- if (store) store.addRunResult(entry);
150
- onProgress?.(entry);
151
- return entry;
152
- });
153
- }