kiku-agent 1.3.0 → 1.3.2
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/README.md +7 -7
- package/package.json +4 -7
- package/server/agent/agentCore.js +2 -2
- package/server/agent/catalogEntityResolver.js +8 -0
- package/server/agent/cli.js +4 -0
- package/server/agent/eval/AGENTS.md +228 -0
- package/server/agent/eval/analyze.py +684 -0
- package/server/agent/eval/gates.json +15 -0
- package/server/agent/eval/modules/analysis/compare.js +198 -0
- package/server/agent/eval/modules/analysis/gates.js +72 -0
- package/server/agent/eval/modules/analysis/judge.js +70 -0
- package/server/agent/eval/modules/core/cli.js +453 -0
- package/server/agent/eval/modules/core/config.js +60 -0
- package/server/agent/eval/modules/core/store.js +236 -0
- package/server/agent/eval/modules/evaluation/cases.js +689 -0
- package/server/agent/eval/modules/evaluation/runner.js +298 -0
- package/server/agent/eval/modules/evaluation/scoring.js +351 -0
- package/server/agent/eval/modules/evaluation/worker.js +122 -0
- package/server/agent/eval/modules/reporting/benchmark.js +265 -0
- package/server/agent/eval/modules/reporting/benchmarkPng.js +487 -0
- package/server/agent/eval/modules/reporting/dashboard.js +1406 -0
- package/server/agent/eval/modules/server/evalApi.js +1099 -0
- package/server/agent/eval/modules/ui/evalUi.js +4796 -0
- package/server/agent/eval/modules/utils/smokes.js +119 -0
- package/server/agent/ink/App.jsx +348 -0
- package/server/agent/ink/Panel.jsx +54 -0
- package/server/agent/ink/PromptInput.jsx +321 -0
- package/server/agent/ink/TaskPlanPanel.jsx +88 -0
- package/server/agent/ink/ThinkingPanel.jsx +68 -0
- package/server/agent/ink/ToolResult.jsx +133 -0
- package/server/agent/ink/main.jsx +195 -0
- package/server/agent/ink/useEventBus.js +218 -0
- package/server/agent/memory/contextAllocator.js +214 -0
- package/server/agent/memory/hierarchicalStore.js +523 -0
- package/server/agent/memory/reflectionWorker.js +110 -0
- package/server/agent/prompts.js +1 -1
- package/server/agent/telemetry/hardwareTiers.js +332 -0
- package/server/agent/telemetry/runtimeWatchdog.js +66 -0
- package/server/agent/telemetry/techPowerUpClient.js +682 -0
- package/server/agent/telemetry/techpowerup-mcp.json +22 -0
- package/server/agent/tests/agentBubbleSystem.test.js +65 -0
- package/server/agent/tests/agentFollowUps.test.js +127 -0
- package/server/agent/tests/apiRouter.test.js +108 -0
- package/server/agent/tests/architectureFixes.test.js +81 -0
- package/server/agent/tests/architectureRefactoring.test.js +194 -0
- package/server/agent/tests/autoResearch.test.js +64 -0
- package/server/agent/tests/bilibiliEmbed.test.js +251 -0
- package/server/agent/tests/catalogSync.test.js +130 -0
- package/server/agent/tests/citations.test.js +180 -0
- package/server/agent/tests/clarificationAnalytics.test.js +219 -0
- package/server/agent/tests/clarifyingQuestions.test.js +594 -0
- package/server/agent/tests/cliAutomation.test.js +121 -0
- package/server/agent/tests/cloudProviders.test.js +234 -0
- package/server/agent/tests/commandInput.test.js +24 -0
- package/server/agent/tests/commands.test.js +417 -0
- package/server/agent/tests/contextIntelligence.test.js +162 -0
- package/server/agent/tests/conversationSafety.test.js +53 -0
- package/server/agent/tests/decisionAndSearchEnhancements.test.js +135 -0
- package/server/agent/tests/dramaAnswerQuality.test.js +73 -0
- package/server/agent/tests/dynamicCatalogIngestion.test.js +285 -0
- package/server/agent/tests/dynamicRoutingAndSearch.test.js +92 -0
- package/server/agent/tests/edgeCasesScenarios.test.js +49 -0
- package/server/agent/tests/entertainmentMetrics.test.js +126 -0
- package/server/agent/tests/envBootstrap.test.js +87 -0
- package/server/agent/tests/envCommand.test.js +215 -0
- package/server/agent/tests/errorHandling.test.js +80 -0
- package/server/agent/tests/errors.test.js +13 -0
- package/server/agent/tests/evalAnalyze.test.js +54 -0
- package/server/agent/tests/evalCommand.test.js +54 -0
- package/server/agent/tests/evalCorpus.test.js +126 -0
- package/server/agent/tests/evalHarness.test.js +62 -0
- package/server/agent/tests/evalJudgeContext.test.js +38 -0
- package/server/agent/tests/evalMetrics.test.js +312 -0
- package/server/agent/tests/evalStore.test.js +190 -0
- package/server/agent/tests/evalStudio.test.js +179 -0
- package/server/agent/tests/feedbackLearning.test.js +186 -0
- package/server/agent/tests/firstRun.test.js +44 -0
- package/server/agent/tests/gemma4eOptimization.test.js +95 -0
- package/server/agent/tests/hardwareTiers.test.js +206 -0
- package/server/agent/tests/hybridWebSearch.test.js +192 -0
- package/server/agent/tests/judgeService.test.js +251 -0
- package/server/agent/tests/lanDiscovery.test.js +319 -0
- package/server/agent/tests/mcpClient.test.js +201 -0
- package/server/agent/tests/modelManager.test.js +109 -0
- package/server/agent/tests/multilingualScenarios.test.js +69 -0
- package/server/agent/tests/newCommands.test.js +347 -0
- package/server/agent/tests/newCommandsTui.test.js +141 -0
- package/server/agent/tests/newTools.test.js +113 -0
- package/server/agent/tests/newsIntelligence.test.js +178 -0
- package/server/agent/tests/nextGenArchitecture.test.js +99 -0
- package/server/agent/tests/offlineInput.test.js +217 -0
- package/server/agent/tests/packagingGuard.test.js +57 -0
- package/server/agent/tests/presentation.test.js +61 -0
- package/server/agent/tests/providerPersistence.test.js +39 -0
- package/server/agent/tests/queryBenchmarks.test.js +83 -0
- package/server/agent/tests/queryDetails.test.js +126 -0
- package/server/agent/tests/realWorldValidation.test.js +391 -0
- package/server/agent/tests/resilienceAndConcurrency.test.js +117 -0
- package/server/agent/tests/runtimeWatchdog.test.js +32 -0
- package/server/agent/tests/sanitizer.test.js +37 -0
- package/server/agent/tests/scenariosData.js +378 -0
- package/server/agent/tests/schemaRouting.test.js +54 -0
- package/server/agent/tests/securityAuditRemediation.test.js +214 -0
- package/server/agent/tests/securityHardening.test.js +146 -0
- package/server/agent/tests/securitySanitizer.test.js +38 -0
- package/server/agent/tests/sessionAndCopy.test.js +232 -0
- package/server/agent/tests/sidebarControls.test.js +101 -0
- package/server/agent/tests/statefulMemory.test.js +270 -0
- package/server/agent/tests/subagent.test.js +216 -0
- package/server/agent/tests/subagentProgress.test.js +109 -0
- package/server/agent/tests/subagentTextFallback.test.js +115 -0
- package/server/agent/tests/suggestions.test.js +199 -0
- package/server/agent/tests/suggestionsTui.test.js +251 -0
- package/server/agent/tests/telemetryAndProfiles.test.js +179 -0
- package/server/agent/tests/terminalImage.test.js +80 -0
- package/server/agent/tests/terminalMarkdown.test.js +166 -0
- package/server/agent/tests/terminalText.test.js +133 -0
- package/server/agent/tests/textProtocolLoop.test.js +126 -0
- package/server/agent/tests/textToolParsing.test.js +139 -0
- package/server/agent/tests/theme.test.js +26 -0
- package/server/agent/tests/toolRegistryParity.test.js +53 -0
- package/server/agent/tests/toolRoutingRegressions.test.js +119 -0
- package/server/agent/tests/tools.test.js +259 -0
- package/server/agent/tests/unifiedEndpoints.test.js +152 -0
- package/server/agent/tests/v110Features.test.js +262 -0
- package/server/agent/tests/versionBump.test.js +69 -0
- package/server/agent/tests/vramEstimator.test.js +520 -0
- package/server/agent/tests/webChatInspection.test.js +300 -0
- package/server/agent/tests/webChatMemory.test.js +112 -0
- package/server/agent/tests/webChatSecurity.test.js +169 -0
- package/server/agent/tests/webExtract.test.js +261 -0
- package/server/agent/tests/webSources.test.js +247 -0
- package/server/agent/websearch/adapters/duckduckgo.js +97 -0
- package/server/agent/websearch/adapters/jina.js +77 -0
- package/server/agent/websearch/adapters/searxng.js +57 -0
- package/server/agent/websearch/core/canonical.js +89 -0
- package/server/agent/websearch/core/fusion.js +81 -0
- package/server/agent/websearch/core/types.js +29 -0
- package/server/agent/websearch/extract.js +605 -0
- package/server/agent/websearch/index.js +43 -0
- package/server/agent/websearch/orchestrator.js +266 -0
- package/server/agent/websearch/resilience/breaker.js +97 -0
- package/server/agent/websearch/resilience/cache.js +59 -0
- package/server/agent/websearch/resilience/limiter.js +93 -0
- package/server/agent/websearch/security/filter.js +36 -0
- package/server/agent/websearch/security/ssrf.js +289 -0
- package/server/agent/websearch/telemetry.js +71 -0
- package/server/apiRouter.js +7 -11
- package/server/eval/judgeRubrics.js +110 -0
- package/server/eval/judgeService.js +290 -0
- package/server/eval/runBenchmark.js +182 -0
- package/server/mcp/examples/librechat.example.yaml +16 -0
- package/server/mcp/tests/connection.test.js +156 -0
- package/server/routes/agent.js +55 -0
- package/server/supabase_schema.sql +63 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { parseArgs } from 'node:util';
|
|
5
|
+
import { cases, securityCases, allCases, validateCases } from '../evaluation/cases.js';
|
|
6
|
+
import { DEFAULTS, REPO_ROOT, normalizeBaseUrl, normalizeTimeoutMs } from './config.js';
|
|
7
|
+
import { loadReport, verifyCorpusFreshness, acquireRunLock } from './store.js';
|
|
8
|
+
import { runSuite } from '../evaluation/runner.js';
|
|
9
|
+
import { runWorkerMain } from '../evaluation/worker.js';
|
|
10
|
+
import { judgeReport } from '../analysis/judge.js';
|
|
11
|
+
import { applyGates } from '../analysis/gates.js';
|
|
12
|
+
import { createSaver } from './store.js';
|
|
13
|
+
import { diffReports } from '../analysis/compare.js';
|
|
14
|
+
import { writeDashboardReport } from '../reporting/dashboard.js';
|
|
15
|
+
import { writeBenchmarkPng } from '../reporting/benchmarkPng.js';
|
|
16
|
+
import { runWebSmoke, runResearchSmoke, runTokenCalibration } from '../utils/smokes.js';
|
|
17
|
+
import { spawnSync } from 'node:child_process';
|
|
18
|
+
|
|
19
|
+
const SUBCOMMANDS = ['validate', 'run', 'judge', 'compare', 'dashboard', 'dashboard-png', 'dashboard-enterprise', 'smoke', 'calibrate', 'gate', 'analyze', 'ui', '__worker'];
|
|
20
|
+
|
|
21
|
+
const HELP = `Kiku eval harness — single entry point (dev-only, never published to npm)
|
|
22
|
+
|
|
23
|
+
Usage:
|
|
24
|
+
node server/agent/eval/modules/cli.js <command> [options]
|
|
25
|
+
npm run eval -- <command> [options]
|
|
26
|
+
|
|
27
|
+
Commands:
|
|
28
|
+
validate Validate eval corpus (no LM Studio needed)
|
|
29
|
+
run [options] Run catalog/security suite with real agent + LM Studio
|
|
30
|
+
judge --out <f> --judge-model <id> Grade an existing report with an LLM judge
|
|
31
|
+
compare --baseline <f> --candidate <f> [--json] [--fail-on-regression]
|
|
32
|
+
dashboard [--in <f>] [--out <f>] [--compare <f>]
|
|
33
|
+
dashboard-png [--in <f>] [--out <f>] [--width <n>]
|
|
34
|
+
dashboard-enterprise Launch interactive Kiku Eval Studio web UI (primary)
|
|
35
|
+
smoke web|research External-tool / research-orchestrator availability checks
|
|
36
|
+
calibrate [--model <id>] Compare context estimator vs real prompt tokens
|
|
37
|
+
gate --in <f> [--gates-file <f>] Apply gates.json thresholds to a report
|
|
38
|
+
analyze [--mode all|codebase|report] [--report <f>] [--out <f>] [--markdown-out <f>] [--skip-corpus]
|
|
39
|
+
ui [--port <n>] [--open] Launch interactive Kiku Eval Studio web UI
|
|
40
|
+
|
|
41
|
+
run options:
|
|
42
|
+
--suite catalog|security|all (default: catalog) --ids p01,w01 --limit 3
|
|
43
|
+
--model <id> --base-url <url> --timeout-ms <n> --out <file> --resume
|
|
44
|
+
--judge-model <id> --gate --dashboard --save-as <snapshot-name>
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
npm run eval:validate
|
|
48
|
+
npm run eval:quick
|
|
49
|
+
npm run eval:run -- --suite all --limit 5
|
|
50
|
+
npm run eval:judge -- --out artifacts/eval/live.json --judge-model google/gemma-4-e4b
|
|
51
|
+
npm run eval:compare -- --baseline a.json --candidate b.json
|
|
52
|
+
npm run eval:dashboard
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
function opt(spec) {
|
|
56
|
+
return parseArgs({ args: spec, options: {
|
|
57
|
+
model: { type: 'string' },
|
|
58
|
+
'base-url': { type: 'string' },
|
|
59
|
+
'timeout-ms': { type: 'string' },
|
|
60
|
+
out: { type: 'string' },
|
|
61
|
+
in: { type: 'string' },
|
|
62
|
+
suite: { type: 'string' },
|
|
63
|
+
ids: { type: 'string' },
|
|
64
|
+
limit: { type: 'string' },
|
|
65
|
+
'judge-model': { type: 'string' },
|
|
66
|
+
resume: { type: 'boolean' },
|
|
67
|
+
'retry-failed': { type: 'boolean' },
|
|
68
|
+
'retry-errors': { type: 'boolean' },
|
|
69
|
+
from: { type: 'string' },
|
|
70
|
+
gate: { type: 'boolean' },
|
|
71
|
+
'gates-file': { type: 'string' },
|
|
72
|
+
dashboard: { type: 'boolean' },
|
|
73
|
+
'dashboard-out': { type: 'string' },
|
|
74
|
+
'save-as': { type: 'string' },
|
|
75
|
+
baseline: { type: 'string' },
|
|
76
|
+
candidate: { type: 'string' },
|
|
77
|
+
compare: { type: 'string' },
|
|
78
|
+
json: { type: 'boolean' },
|
|
79
|
+
'fail-on-regression': { type: 'boolean' },
|
|
80
|
+
id: { type: 'string' },
|
|
81
|
+
mode: { type: 'string' },
|
|
82
|
+
width: { type: 'string' },
|
|
83
|
+
report: { type: 'string' },
|
|
84
|
+
gates: { type: 'string' },
|
|
85
|
+
'markdown-out': { type: 'string' },
|
|
86
|
+
'skip-corpus': { type: 'boolean' },
|
|
87
|
+
root: { type: 'string' },
|
|
88
|
+
port: { type: 'string' },
|
|
89
|
+
open: { type: 'boolean' },
|
|
90
|
+
help: { type: 'boolean', short: 'h' },
|
|
91
|
+
}, allowPositionals: true });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function cmdValidate() {
|
|
95
|
+
validateCases();
|
|
96
|
+
console.log(JSON.stringify({
|
|
97
|
+
valid: true,
|
|
98
|
+
count: allCases.length,
|
|
99
|
+
catalogCount: cases.length,
|
|
100
|
+
securityCount: securityCases.length,
|
|
101
|
+
categories: [...new Set(allCases.map(c => c.category))]
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function cmdRun(raw) {
|
|
106
|
+
const { values } = opt(raw);
|
|
107
|
+
await runSuite({
|
|
108
|
+
suite: values.suite ?? DEFAULTS.suite,
|
|
109
|
+
ids: values.ids,
|
|
110
|
+
limit: values.limit,
|
|
111
|
+
model: values.model ?? DEFAULTS.model,
|
|
112
|
+
baseUrl: normalizeBaseUrl(values['base-url'] ?? DEFAULTS.baseUrl),
|
|
113
|
+
timeoutMs: normalizeTimeoutMs(values['timeout-ms'] ?? DEFAULTS.timeoutMs),
|
|
114
|
+
out: values.out ?? DEFAULTS.out,
|
|
115
|
+
from: values.from,
|
|
116
|
+
resume: values.resume ?? false,
|
|
117
|
+
retryFailed: values['retry-failed'] ?? false,
|
|
118
|
+
retryErrors: values['retry-errors'] ?? false,
|
|
119
|
+
judgeModel: values['judge-model'],
|
|
120
|
+
gate: values.gate ?? false,
|
|
121
|
+
gatesFile: values['gates-file'] ?? DEFAULTS.gatesFile,
|
|
122
|
+
saveAs: values['save-as'],
|
|
123
|
+
dashboard: values.dashboard ?? false,
|
|
124
|
+
dashboardOut: values['dashboard-out'] ?? DEFAULTS.dashboardOut,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function cmdJudge(raw) {
|
|
129
|
+
const { values } = opt(raw);
|
|
130
|
+
const outPath = path.resolve(REPO_ROOT, values.out ?? DEFAULTS.out);
|
|
131
|
+
if (!values['judge-model']) throw new Error('judge requires --judge-model <id>');
|
|
132
|
+
const report = await loadReport(outPath);
|
|
133
|
+
await verifyCorpusFreshness(report);
|
|
134
|
+
const save = createSaver(outPath, report);
|
|
135
|
+
const lock = await acquireRunLock(outPath);
|
|
136
|
+
try {
|
|
137
|
+
await judgeReport(report,
|
|
138
|
+
{ judgeModel: values['judge-model'], baseUrl: normalizeBaseUrl(values['base-url'] ?? DEFAULTS.baseUrl), timeoutMs: normalizeTimeoutMs(values['timeout-ms'] ?? DEFAULTS.timeoutMs) },
|
|
139
|
+
save);
|
|
140
|
+
await save();
|
|
141
|
+
} finally {
|
|
142
|
+
await lock.release();
|
|
143
|
+
}
|
|
144
|
+
console.log(JSON.stringify(report.summary, null, 2));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function cmdCompare(raw) {
|
|
148
|
+
const { values } = opt(raw);
|
|
149
|
+
if (!values.baseline || !values.candidate) throw new Error('compare requires --baseline <file> --candidate <file>');
|
|
150
|
+
const base = JSON.parse(await fs.readFile(path.resolve(REPO_ROOT, values.baseline), 'utf8'));
|
|
151
|
+
const cand = JSON.parse(await fs.readFile(path.resolve(REPO_ROOT, values.candidate), 'utf8'));
|
|
152
|
+
const diff = diffReports(base, cand);
|
|
153
|
+
if (values.json) {
|
|
154
|
+
console.log(JSON.stringify(diff, null, 2));
|
|
155
|
+
} else {
|
|
156
|
+
console.log('=== Kiku Evaluation Report Comparison ===');
|
|
157
|
+
console.log('Baseline:', values.baseline);
|
|
158
|
+
console.log('Candidate:', values.candidate);
|
|
159
|
+
console.log('\n--- Metric Deltas ---');
|
|
160
|
+
for (const [m, data] of Object.entries(diff.metricDeltas)) {
|
|
161
|
+
const deltaSign = data.delta !== null && data.delta > 0 ? `+${data.delta}` : `${data.delta ?? '-'}`;
|
|
162
|
+
console.log(`${m.padEnd(25)}: ${String(data.baseline ?? '-').padEnd(10)} -> ${String(data.candidate ?? '-').padEnd(10)} (delta: ${deltaSign})`);
|
|
163
|
+
}
|
|
164
|
+
console.log('\n--- Case Diff Summary ---');
|
|
165
|
+
console.log(`Regressions : ${diff.caseDiffs.summary.regressionsCount}`);
|
|
166
|
+
console.log(`Improvements: ${diff.caseDiffs.summary.improvementsCount}`);
|
|
167
|
+
console.log(`Unchanged : ${diff.caseDiffs.summary.unchangedCount}`);
|
|
168
|
+
if (diff.caseDiffs.regressions.length > 0) {
|
|
169
|
+
console.log('\n[REGRESSION] Regressions detected:');
|
|
170
|
+
for (const r of diff.caseDiffs.regressions) {
|
|
171
|
+
console.log(` - [${r.id}] (${r.category}): base=${r.baseline.status}/matched=${r.baseline.allMatched}/tool=${r.baseline.toolCorrect} -> cand=${r.candidate.status}/matched=${r.candidate.allMatched}/tool=${r.candidate.toolCorrect}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (values['fail-on-regression'] && diff.caseDiffs.regressions.length > 0) process.exit(2);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function cmdDashboard(raw) {
|
|
179
|
+
const { values } = opt(raw);
|
|
180
|
+
const inPath = path.resolve(REPO_ROOT, values.in ?? DEFAULTS.out);
|
|
181
|
+
const outPath = path.resolve(REPO_ROOT, values.out ?? DEFAULTS.dashboardOut);
|
|
182
|
+
const report = JSON.parse(await fs.readFile(inPath, 'utf8'));
|
|
183
|
+
let compareReport = null;
|
|
184
|
+
if (values.compare) compareReport = JSON.parse(await fs.readFile(path.resolve(REPO_ROOT, values.compare), 'utf8'));
|
|
185
|
+
await writeDashboardReport(report, outPath, compareReport);
|
|
186
|
+
|
|
187
|
+
let lastMtime = 0;
|
|
188
|
+
try {
|
|
189
|
+
const st = await fs.stat(inPath);
|
|
190
|
+
lastMtime = st.mtimeMs;
|
|
191
|
+
} catch {}
|
|
192
|
+
|
|
193
|
+
const watcher = setInterval(async () => {
|
|
194
|
+
try {
|
|
195
|
+
const st = await fs.stat(inPath);
|
|
196
|
+
if (st.mtimeMs !== lastMtime) {
|
|
197
|
+
lastMtime = st.mtimeMs;
|
|
198
|
+
const freshRaw = await fs.readFile(inPath, 'utf8');
|
|
199
|
+
const freshReport = JSON.parse(freshRaw);
|
|
200
|
+
await writeDashboardReport(freshReport, outPath, compareReport);
|
|
201
|
+
}
|
|
202
|
+
} catch {}
|
|
203
|
+
}, 1000);
|
|
204
|
+
if (typeof watcher.unref === 'function') {
|
|
205
|
+
watcher.unref();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const shouldOpen = values.open !== false;
|
|
209
|
+
if (shouldOpen) {
|
|
210
|
+
let targetUrl = `file:///${outPath.replace(/\\/g, '/')}`;
|
|
211
|
+
try {
|
|
212
|
+
const ping3001 = await fetch('http://localhost:3001/api/eval/status', { signal: AbortSignal.timeout(600) });
|
|
213
|
+
if (ping3001.ok) {
|
|
214
|
+
targetUrl = 'http://localhost:3001/eval/dashboard';
|
|
215
|
+
} else {
|
|
216
|
+
const ping3000 = await fetch('http://localhost:3000/api/eval/status', { signal: AbortSignal.timeout(600) });
|
|
217
|
+
if (ping3000.ok) targetUrl = 'http://localhost:3000/eval/dashboard';
|
|
218
|
+
}
|
|
219
|
+
} catch {}
|
|
220
|
+
const safeUrl = targetUrl.replace(/["`$&|;<>^%]/g, '');
|
|
221
|
+
const openCmd = process.platform === 'win32'
|
|
222
|
+
? `start "" "${safeUrl}"`
|
|
223
|
+
: (process.platform === 'darwin' ? `open "${safeUrl}"` : `xdg-open "${safeUrl}"`);
|
|
224
|
+
const cp = await import('node:child_process');
|
|
225
|
+
cp.exec(openCmd, () => {});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function cmdDashboardPng(raw) {
|
|
230
|
+
const { values } = opt(raw);
|
|
231
|
+
const inPath = path.resolve(REPO_ROOT, values.in ?? DEFAULTS.out);
|
|
232
|
+
const outPath = path.resolve(REPO_ROOT, values.out ?? DEFAULTS.dashboardPngOut);
|
|
233
|
+
const report = JSON.parse(await fs.readFile(inPath, 'utf8'));
|
|
234
|
+
const width = Number(values.width ?? 1440) || 1440;
|
|
235
|
+
const mode = values.mode ?? 'full';
|
|
236
|
+
await writeBenchmarkPng(report, outPath, { width, mode });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function cmdDashboardEnterprise(raw) {
|
|
240
|
+
return cmdUi(raw);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function cmdSmoke(raw) {
|
|
244
|
+
const [target, ...rest] = raw;
|
|
245
|
+
const { values } = opt(rest);
|
|
246
|
+
if (target === 'web') {
|
|
247
|
+
await runWebSmoke({ out: values.out ?? DEFAULTS.webSmokeOut });
|
|
248
|
+
} else if (target === 'research') {
|
|
249
|
+
await runResearchSmoke({
|
|
250
|
+
out: values.out ?? DEFAULTS.researchSmokeOut,
|
|
251
|
+
model: values.model ?? DEFAULTS.model,
|
|
252
|
+
baseUrl: normalizeBaseUrl(values['base-url'] ?? DEFAULTS.baseUrl),
|
|
253
|
+
timeoutMs: normalizeTimeoutMs(values['timeout-ms'] ?? DEFAULTS.timeoutMs),
|
|
254
|
+
});
|
|
255
|
+
} else {
|
|
256
|
+
throw new Error('smoke requires a target: web | research');
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function cmdCalibrate(raw) {
|
|
261
|
+
const { values } = opt(raw);
|
|
262
|
+
await runTokenCalibration({
|
|
263
|
+
out: values.out ?? DEFAULTS.tokenCalibrationOut,
|
|
264
|
+
model: values.model ?? DEFAULTS.model,
|
|
265
|
+
baseUrl: normalizeBaseUrl(values['base-url'] ?? DEFAULTS.baseUrl),
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function cmdGate(raw) {
|
|
270
|
+
const { values } = opt(raw);
|
|
271
|
+
const inPath = path.resolve(REPO_ROOT, values.in ?? DEFAULTS.out);
|
|
272
|
+
const report = JSON.parse(await fs.readFile(inPath, 'utf8'));
|
|
273
|
+
const save = createSaver(inPath, report);
|
|
274
|
+
const lock = await acquireRunLock(inPath);
|
|
275
|
+
try {
|
|
276
|
+
const result = await applyGates(report, values['gates-file'] ?? DEFAULTS.gatesFile, save);
|
|
277
|
+
console.log(JSON.stringify(result, null, 2));
|
|
278
|
+
if (!result.passed) process.exitCode = 2;
|
|
279
|
+
} finally {
|
|
280
|
+
await lock.release();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function cmdAnalyze(raw) {
|
|
285
|
+
const { values } = opt(raw);
|
|
286
|
+
const script = new URL('../../analyze.py', import.meta.url);
|
|
287
|
+
const scriptPath = fileURLToPath(script);
|
|
288
|
+
const args = [scriptPath, '--mode', values.mode ?? 'all'];
|
|
289
|
+
if (values.report) args.push('--report', values.report);
|
|
290
|
+
if (values.out) args.push('--out', values.out);
|
|
291
|
+
if (values['markdown-out']) args.push('--markdown-out', values['markdown-out']);
|
|
292
|
+
if (values.gates) args.push('--gates', values.gates);
|
|
293
|
+
if (values.root) args.push('--root', values.root);
|
|
294
|
+
if (values['skip-corpus']) args.push('--skip-corpus');
|
|
295
|
+
const candidates = process.platform === 'win32'
|
|
296
|
+
? [['python', []], ['py', ['-3']], ['python3', []]]
|
|
297
|
+
: [['python3', []], ['python', []]];
|
|
298
|
+
let lastError = null;
|
|
299
|
+
for (const [bin, prefix] of candidates) {
|
|
300
|
+
const result = spawnSync(bin, [...prefix, ...args], { cwd: REPO_ROOT, stdio: 'inherit' });
|
|
301
|
+
if (result.error?.code === 'ENOENT') { lastError = result.error; continue; }
|
|
302
|
+
if (typeof result.status === 'number' && result.status !== 0) process.exit(result.status);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
throw new Error(`Python interpreter not found for analyze (${lastError?.message ?? 'no candidates'})`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function cmdUi(raw) {
|
|
309
|
+
const { values } = opt(raw);
|
|
310
|
+
const port = Number(values.port || 3001);
|
|
311
|
+
const shouldOpen = values.open ?? false;
|
|
312
|
+
const { createServer } = await import('node:http');
|
|
313
|
+
const { handleEvalApiRequest } = await import('../server/evalApi.js');
|
|
314
|
+
|
|
315
|
+
const server = createServer(async (req, res) => {
|
|
316
|
+
try {
|
|
317
|
+
const handled = await handleEvalApiRequest(req, res);
|
|
318
|
+
if (!handled) {
|
|
319
|
+
res.writeHead(302, { Location: '/eval' });
|
|
320
|
+
res.end();
|
|
321
|
+
}
|
|
322
|
+
} catch (err) {
|
|
323
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
324
|
+
res.end('Server error: ' + err.message);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
server.on('error', (err) => {
|
|
329
|
+
if (err.code === 'EADDRINUSE') {
|
|
330
|
+
if (shouldOpen) {
|
|
331
|
+
const url = `http://localhost:${port}/eval`;
|
|
332
|
+
const openCmd = process.platform === 'win32' ? `start "" "${url}"` : (process.platform === 'darwin' ? `open "${url}"` : `xdg-open "${url}"`);
|
|
333
|
+
import('node:child_process').then(cp => {
|
|
334
|
+
cp.exec(openCmd, () => {});
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
throw err;
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
server.listen(port, () => {
|
|
343
|
+
const url = `http://localhost:${port}/eval`;
|
|
344
|
+
console.log(`[Eval Studio] Running at ${url}`);
|
|
345
|
+
if (shouldOpen) {
|
|
346
|
+
const openCmd = process.platform === 'win32' ? `start "" "${url}"` : (process.platform === 'darwin' ? `open "${url}"` : `xdg-open "${url}"`);
|
|
347
|
+
import('node:child_process').then(cp => {
|
|
348
|
+
cp.exec(openCmd, () => {});
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function cmdLegacy(argv) {
|
|
355
|
+
const { values } = parseArgs({ args: argv, options: {
|
|
356
|
+
worker: { type: 'string' },
|
|
357
|
+
out: { type: 'string', default: DEFAULTS.out },
|
|
358
|
+
suite: { type: 'string', default: DEFAULTS.suite },
|
|
359
|
+
dashboard: { type: 'boolean', default: false },
|
|
360
|
+
'dashboard-out': { type: 'string', default: DEFAULTS.dashboardOut },
|
|
361
|
+
ids: { type: 'string' },
|
|
362
|
+
limit: { type: 'string' },
|
|
363
|
+
model: { type: 'string', default: DEFAULTS.model },
|
|
364
|
+
'base-url': { type: 'string', default: DEFAULTS.baseUrl },
|
|
365
|
+
'timeout-ms': { type: 'string', default: String(DEFAULTS.timeoutMs) },
|
|
366
|
+
'judge-model': { type: 'string' },
|
|
367
|
+
'judge-only': { type: 'boolean', default: false },
|
|
368
|
+
resume: { type: 'boolean', default: false },
|
|
369
|
+
validate: { type: 'boolean', default: false },
|
|
370
|
+
gate: { type: 'boolean', default: false },
|
|
371
|
+
'gates-file': { type: 'string', default: DEFAULTS.gatesFile },
|
|
372
|
+
'save-as': { type: 'string' },
|
|
373
|
+
}, allowPositionals: true });
|
|
374
|
+
|
|
375
|
+
validateCases();
|
|
376
|
+
const timeoutMs = normalizeTimeoutMs(values['timeout-ms']);
|
|
377
|
+
const baseUrl = normalizeBaseUrl(values['base-url']);
|
|
378
|
+
|
|
379
|
+
if (values.validate) {
|
|
380
|
+
await cmdValidate();
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (values.worker) {
|
|
384
|
+
await runWorkerMain({ caseId: values.worker, model: values.model, baseUrl, timeoutMs });
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
await runSuite({
|
|
388
|
+
suite: values.suite,
|
|
389
|
+
ids: values.ids,
|
|
390
|
+
limit: values.limit,
|
|
391
|
+
model: values.model,
|
|
392
|
+
baseUrl,
|
|
393
|
+
timeoutMs,
|
|
394
|
+
out: values.out,
|
|
395
|
+
resume: values.resume,
|
|
396
|
+
judgeModel: values['judge-model'],
|
|
397
|
+
judgeOnly: values['judge-only'],
|
|
398
|
+
gate: values.gate,
|
|
399
|
+
gatesFile: values['gates-file'],
|
|
400
|
+
saveAs: values['save-as'],
|
|
401
|
+
dashboard: values.dashboard,
|
|
402
|
+
dashboardOut: values['dashboard-out'],
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export async function runCli(argv = process.argv.slice(2)) {
|
|
407
|
+
const [first, ...rest] = argv;
|
|
408
|
+
if (!first || first === '--help' || first === '-h') {
|
|
409
|
+
console.log(HELP);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
if (rest.includes('--help') || rest.includes('-h')) {
|
|
413
|
+
console.log(HELP);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (!SUBCOMMANDS.includes(first)) {
|
|
417
|
+
await cmdLegacy(argv);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
if (first === '__worker') {
|
|
421
|
+
const { values } = opt(rest);
|
|
422
|
+
if (!values.id) throw new Error('__worker requires --id <caseId>');
|
|
423
|
+
await runWorkerMain({
|
|
424
|
+
caseId: values.id,
|
|
425
|
+
model: values.model ?? DEFAULTS.model,
|
|
426
|
+
baseUrl: normalizeBaseUrl(values['base-url'] ?? DEFAULTS.baseUrl),
|
|
427
|
+
timeoutMs: normalizeTimeoutMs(values['timeout-ms'] ?? DEFAULTS.timeoutMs),
|
|
428
|
+
});
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
switch (first) {
|
|
432
|
+
case 'validate': await cmdValidate(); break;
|
|
433
|
+
case 'run': await cmdRun(rest); break;
|
|
434
|
+
case 'judge': await cmdJudge(rest); break;
|
|
435
|
+
case 'compare': await cmdCompare(rest); break;
|
|
436
|
+
case 'dashboard': await cmdDashboard(rest); break;
|
|
437
|
+
case 'dashboard-png': await cmdDashboardPng(rest); break;
|
|
438
|
+
case 'dashboard-enterprise': await cmdDashboardEnterprise(rest); break;
|
|
439
|
+
case 'smoke': await cmdSmoke(rest); break;
|
|
440
|
+
case 'calibrate': await cmdCalibrate(rest); break;
|
|
441
|
+
case 'gate': await cmdGate(rest); break;
|
|
442
|
+
case 'analyze': await cmdAnalyze(rest); break;
|
|
443
|
+
case 'ui': await cmdUi(rest); break;
|
|
444
|
+
default: throw new Error(`Unknown command: ${first}`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
|
|
449
|
+
runCli(process.argv.slice(2)).catch(err => {
|
|
450
|
+
console.error(err);
|
|
451
|
+
process.exit(1);
|
|
452
|
+
});
|
|
453
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
|
|
3
|
+
export const REPO_ROOT = fileURLToPath(new URL('../../../../../', import.meta.url));
|
|
4
|
+
|
|
5
|
+
export const DEFAULTS = {
|
|
6
|
+
model: 'google/gemma-4-e4b',
|
|
7
|
+
baseUrl: 'http://127.0.0.1:1234/v1',
|
|
8
|
+
timeoutMs: 240000,
|
|
9
|
+
workerGraceMs: 15000,
|
|
10
|
+
out: 'artifacts/eval/live.json',
|
|
11
|
+
suite: 'catalog',
|
|
12
|
+
dashboardOut: 'artifacts/eval/dashboard.html',
|
|
13
|
+
dashboardPngOut: 'artifacts/eval/benchmark.png',
|
|
14
|
+
gatesFile: 'server/agent/eval/gates.json',
|
|
15
|
+
snapshotsDir: 'artifacts/eval/snapshots',
|
|
16
|
+
webSmokeOut: 'artifacts/eval/web-smoke.json',
|
|
17
|
+
researchSmokeOut: 'artifacts/eval/research-smoke.json',
|
|
18
|
+
tokenCalibrationOut: 'artifacts/eval/token-calibration.json',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const ALLOWED_TOOLS = [
|
|
22
|
+
'get_profile_and_fandom',
|
|
23
|
+
'get_quotes',
|
|
24
|
+
'search_works',
|
|
25
|
+
'get_work_detail',
|
|
26
|
+
'get_character_roles',
|
|
27
|
+
'search_discography',
|
|
28
|
+
'get_music_videos',
|
|
29
|
+
'get_media_links',
|
|
30
|
+
'get_brand_endorsements',
|
|
31
|
+
'get_career_timeline',
|
|
32
|
+
'get_awards',
|
|
33
|
+
'get_gallery_photos',
|
|
34
|
+
'global_search',
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
export const TRACKED_AGENT_FILES = [
|
|
38
|
+
'server/agent/agentCore.js',
|
|
39
|
+
'server/agent/llmClient.js',
|
|
40
|
+
'server/agent/config.js',
|
|
41
|
+
'server/agent/tools.js',
|
|
42
|
+
'server/agent/textToolProtocol.js',
|
|
43
|
+
'server/agent/contextCompactor.js',
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
export function normalizeBaseUrl(raw = DEFAULTS.baseUrl) {
|
|
47
|
+
const base = new URL(raw);
|
|
48
|
+
if (!['127.0.0.1', 'localhost', '[::1]'].includes(base.hostname) || base.username || base.password || base.search) {
|
|
49
|
+
throw new Error('This harness only supports local, credential-free LM Studio URLs');
|
|
50
|
+
}
|
|
51
|
+
return raw;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function normalizeTimeoutMs(raw = DEFAULTS.timeoutMs) {
|
|
55
|
+
const timeoutMs = Number(raw);
|
|
56
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1000) {
|
|
57
|
+
throw new Error('timeout-ms must be an integer >= 1000');
|
|
58
|
+
}
|
|
59
|
+
return timeoutMs;
|
|
60
|
+
}
|