kiku-agent 1.3.1 → 1.3.3
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 +69 -12
- package/package.json +5 -12
- package/server/agent/agentCore.js +319 -46
- package/server/agent/appPaths.js +31 -0
- package/server/agent/audioPlayer.js +339 -12
- package/server/agent/autoContext.js +193 -0
- package/server/agent/catalogEntityResolver.js +170 -28
- package/server/agent/clarifyingQuestions.js +137 -35
- package/server/agent/cli.js +177 -48
- package/server/agent/cloudAdapters.js +116 -0
- package/server/agent/commandRegistry.js +409 -80
- package/server/agent/commands.js +1056 -125
- package/server/agent/config.js +1088 -690
- package/server/agent/contextCompactor.js +213 -21
- package/server/agent/cpuMetrics.js +199 -0
- package/server/agent/engineService.js +591 -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 +288 -0
- package/server/agent/eval/modules/evaluation/scoring.js +363 -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 +4799 -0
- package/server/agent/eval/modules/utils/smokes.js +119 -0
- package/server/agent/eventBus.js +1 -0
- package/server/agent/ink/main.jsx +196 -191
- package/server/agent/jobManager.js +11 -1
- package/server/agent/lanDiscovery.js +721 -23
- package/server/agent/llmClient.js +317 -71
- package/server/agent/lmStudioWakeup.js +517 -65
- package/server/agent/locationService.js +797 -0
- package/server/agent/mcpQuizData.js +509 -0
- package/server/agent/memory/contextAllocator.js +19 -7
- package/server/agent/memory.js +3 -1
- package/server/agent/modelCompatibility.js +352 -0
- package/server/agent/modelDefaults.js +1 -0
- package/server/agent/modelManager.js +46 -2
- package/server/agent/newsWatch.js +9 -3
- package/server/agent/processManager.js +229 -0
- package/server/agent/prompts.js +174 -8
- package/server/agent/routingInterceptor.js +9 -1
- package/server/agent/sanitizer.js +41 -6
- package/server/agent/searchQueryPlanner.js +202 -0
- package/server/agent/session.js +59 -4
- package/server/agent/tests/agentBubbleSystem.test.js +65 -0
- package/server/agent/tests/agentFollowUps.test.js +253 -0
- package/server/agent/tests/agentMusicControl.test.js +245 -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/autoContext.test.js +176 -0
- package/server/agent/tests/autoResearch.test.js +64 -0
- package/server/agent/tests/bilibiliEmbed.test.js +296 -0
- package/server/agent/tests/catalogSync.test.js +130 -0
- package/server/agent/tests/chatHardening.test.js +121 -0
- package/server/agent/tests/chineseWebSearchEscalation.test.js +62 -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 +627 -0
- package/server/agent/tests/cliAutomation.test.js +158 -0
- package/server/agent/tests/cloudProviders.test.js +322 -0
- package/server/agent/tests/commandInput.test.js +24 -0
- package/server/agent/tests/commands.test.js +506 -0
- package/server/agent/tests/contextIntelligence.test.js +209 -0
- package/server/agent/tests/conversationSafety.test.js +53 -0
- package/server/agent/tests/cpuMetrics.test.js +101 -0
- package/server/agent/tests/decisionAndSearchEnhancements.test.js +167 -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 +131 -0
- package/server/agent/tests/engineService.test.js +436 -0
- package/server/agent/tests/engineUnload.test.js +94 -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 +284 -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 +320 -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/fetchApi.test.js +99 -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 +194 -0
- package/server/agent/tests/judgeService.test.js +251 -0
- package/server/agent/tests/lanDiscovery.test.js +324 -0
- package/server/agent/tests/lanInterfaces.test.js +510 -0
- package/server/agent/tests/lmStudioWakeup.test.js +278 -0
- package/server/agent/tests/locationTool.test.js +278 -0
- package/server/agent/tests/mcpClient.test.js +201 -0
- package/server/agent/tests/memoryRoute.test.js +173 -0
- package/server/agent/tests/memoryUiNormalization.test.js +75 -0
- package/server/agent/tests/modelCompatibility.test.js +148 -0
- package/server/agent/tests/modelManager.test.js +141 -0
- package/server/agent/tests/multilingualScenarios.test.js +69 -0
- package/server/agent/tests/musicControlUiBridge.test.js +113 -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 +101 -0
- package/server/agent/tests/offlineInput.test.js +217 -0
- package/server/agent/tests/openDomainPrompt.test.js +197 -0
- package/server/agent/tests/openDomainSearch.test.js +121 -0
- package/server/agent/tests/packagingGuard.test.js +57 -0
- package/server/agent/tests/presentation.test.js +61 -0
- package/server/agent/tests/processManager.test.js +144 -0
- package/server/agent/tests/providerPersistence.test.js +44 -0
- package/server/agent/tests/queryBenchmarks.test.js +83 -0
- package/server/agent/tests/queryDetails.test.js +126 -0
- package/server/agent/tests/quickActionsAndModals.test.js +180 -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/searchQueryPlanner.test.js +108 -0
- package/server/agent/tests/securityAuditRemediation.test.js +221 -0
- package/server/agent/tests/securityAuditRemediations2.test.js +154 -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 +267 -0
- package/server/agent/tests/sidebarControls.test.js +135 -0
- package/server/agent/tests/statefulMemory.test.js +277 -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 +197 -0
- package/server/agent/tests/textToolParsing.test.js +187 -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 +160 -0
- package/server/agent/tests/tools.test.js +259 -0
- package/server/agent/tests/unifiedEndpoints.test.js +159 -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 +595 -0
- package/server/agent/tests/webChatDocs.test.js +199 -0
- package/server/agent/tests/webChatInferenceControl.test.js +298 -0
- package/server/agent/tests/webChatInspection.test.js +551 -0
- package/server/agent/tests/webChatMcp.test.js +94 -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 +284 -0
- package/server/agent/tests/webSources.test.js +281 -0
- package/server/agent/textToolProtocol.js +173 -46
- package/server/agent/toolRouter.js +318 -44
- package/server/agent/tools.js +877 -96
- package/server/agent/ui.js +39 -34
- package/server/agent/vramService.js +35 -54
- package/server/agent/webSearch.js +441 -162
- package/server/agent/websearch/orchestrator.js +15 -5
- package/server/agent/websearch/resilience/cache.js +9 -2
- package/server/agent/websearch/security/ssrf.js +41 -10
- package/server/agent/youtubeAuth.js +5 -20
- package/server/apiRouter.js +5558 -4422
- package/server/eval/judgeRubrics.js +110 -0
- package/server/eval/judgeService.js +290 -0
- package/server/eval/runBenchmark.js +182 -0
- package/server/mcp/data/discography.js +2 -2
- package/server/mcp/examples/antigravity.example.json +17 -0
- package/server/mcp/examples/librechat.example.yaml +16 -0
- package/server/mcp/handler.js +8 -4
- package/server/mcp/security.js +220 -21
- package/server/mcp/server.js +2 -1
- package/server/mcp/tests/connection.test.js +162 -0
- package/server/mcp/tests/mcpKeyAuth.test.js +156 -0
- package/server/mcp/tools.js +7 -1
- package/server/supabase_migration_prefix.sql +102 -0
- package/server/supabase_schema.sql +63 -0
- package/server/web-chat/core/connection.js +179 -218
- package/server/web-chat/core/conversationContext.js +9 -2
- package/server/web-chat/core/directCatalogEngine.js +52 -28
- package/server/web-chat/core/dramaKnowledge.js +33 -22
- package/server/web-chat/core/followUpEngine.js +735 -81
- package/server/web-chat/core/futureEventGuard.js +5 -1
- package/server/web-chat/core/musicControlAction.js +70 -0
- package/server/web-chat/core/security.js +32 -21
- package/server/web-chat/core/toolInspector.js +6 -6
- package/server/web-chat/core/urlGuard.js +45 -0
- package/server/web-chat/router.js +204 -157
- package/server/web-chat/routes/chatRoute.js +885 -488
- package/server/web-chat/routes/engineRoute.js +270 -0
- package/server/web-chat/routes/exportRoute.js +1 -1
- package/server/web-chat/routes/lanDiscoveryRoute.js +17 -2
- package/server/web-chat/routes/lmStudioLoadRoute.js +172 -0
- package/server/web-chat/routes/mcpRoute.js +386 -0
- package/server/web-chat/routes/memoryRoute.js +253 -0
- package/server/web-chat/routes/providerRoute.js +49 -16
- package/server/web-chat/routes/staticRoute.js +306 -17
- package/server/web-chat/routes/statusRoute.js +194 -73
- package/server/web-chat/routes/suggestRoute.js +11 -3
- package/server/web-chat/routes/verifyRoute.js +26 -4
- package/server/web-chat/server.js +7 -6
- package/server/web-chat/ui/docsBuilder.js +3558 -0
- package/server/web-chat/ui/htmlBuilder.js +12 -0
- package/server/web-chat/ui/scripts/client.js +3401 -1302
- package/server/web-chat/ui/scripts/webchatI18n.js +155 -153
- package/server/web-chat/ui/settingsBuilder.js +7870 -0
- package/server/web-chat/ui/styles/lan-card.css +165 -0
- package/server/web-chat/ui/styles/main.css +4289 -447
- package/server/web-chat/ui/sw.js +64 -0
- package/server/web-chat/ui/templates/body.html +407 -281
- package/src/data/awardsData.js +146 -146
- package/src/data/musicVideoData.js +6 -4
- package/src/lib/vram/estimator.js +195 -19
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { allCases } from '../evaluation/cases.js';
|
|
6
|
+
import { summarize } from '../evaluation/scoring.js';
|
|
7
|
+
import { REPO_ROOT, TRACKED_AGENT_FILES } from './config.js';
|
|
8
|
+
|
|
9
|
+
const activeRunLocks = new Set();
|
|
10
|
+
const STALE_LOCK_MS = 60 * 60 * 1000;
|
|
11
|
+
|
|
12
|
+
function lockConflictError(message) {
|
|
13
|
+
const error = new Error(message);
|
|
14
|
+
error.status = 409;
|
|
15
|
+
return error;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function corpusHash(cases = allCases) {
|
|
19
|
+
return createHash('sha256').update(JSON.stringify(cases)).digest('hex');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function hashFile(relPath) {
|
|
23
|
+
const data = await fs.readFile(path.join(REPO_ROOT, relPath));
|
|
24
|
+
return createHash('sha256').update(data).digest('hex');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function computeSourceHashes() {
|
|
28
|
+
const files = [...TRACKED_AGENT_FILES, ...new Set(allCases.flatMap(c => c.sources))];
|
|
29
|
+
const entries = await Promise.all(files.map(async f => [f, await hashFile(f)]));
|
|
30
|
+
return Object.fromEntries(entries);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function gitRevision(cwd = REPO_ROOT) {
|
|
34
|
+
try {
|
|
35
|
+
return execFileSync('git', ['rev-parse', 'HEAD'], { cwd, encoding: 'utf8' }).trim();
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function buildMetadata({ model, baseUrl, timeoutMs, caseIds, extra = {} }) {
|
|
42
|
+
return {
|
|
43
|
+
startedAt: new Date().toISOString(),
|
|
44
|
+
node: process.version,
|
|
45
|
+
platform: process.platform,
|
|
46
|
+
model,
|
|
47
|
+
baseUrl,
|
|
48
|
+
revision: gitRevision(),
|
|
49
|
+
timeoutMs,
|
|
50
|
+
caseIds,
|
|
51
|
+
scope: 'Frozen repository catalog QA; no external fact verification, web, research, persistent memory, or interactive clarification. Fresh real KikuAgent and real LLMClient per question. Production temperature/output limits/iteration settings.',
|
|
52
|
+
scoring: 'Lexical fact coverage is a diagnostic only. Optional LLM judge is fallible and not human-validated. No measured population hallucination rate.',
|
|
53
|
+
...extra,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function finalizeMetadata(metadata) {
|
|
58
|
+
metadata.corpusHash = corpusHash();
|
|
59
|
+
metadata.sourceHashes = await computeSourceHashes();
|
|
60
|
+
return metadata;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function verifyCorpusFreshness(report) {
|
|
64
|
+
const reportedIds = new Set(report.rows?.map(r => r.id) || report.metadata?.caseIds || []);
|
|
65
|
+
const relevantCases = allCases.filter(c => reportedIds.has(c.id));
|
|
66
|
+
const relevantSources = new Set((relevantCases.length ? relevantCases : allCases).flatMap(c => c.sources || []));
|
|
67
|
+
for (const source of relevantSources) {
|
|
68
|
+
const current = await hashFile(source);
|
|
69
|
+
if (report.metadata?.sourceHashes?.[source] && current !== report.metadata.sourceHashes[source]) {
|
|
70
|
+
throw new Error(`Reference source changed since inference: ${source}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function assertResumeCompatible(prior, next) {
|
|
76
|
+
for (const field of ['model', 'baseUrl']) {
|
|
77
|
+
if (JSON.stringify(prior.metadata[field]) !== JSON.stringify(next.metadata[field])) {
|
|
78
|
+
throw new Error(`Resume mismatch: ${field}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (prior.metadata.timeoutMs && next.metadata.timeoutMs && prior.metadata.timeoutMs !== next.metadata.timeoutMs) {
|
|
82
|
+
next.metadata.timeoutMs = prior.metadata.timeoutMs;
|
|
83
|
+
}
|
|
84
|
+
if (JSON.stringify(prior.metadata.corpusHash) !== JSON.stringify(next.metadata.corpusHash)) {
|
|
85
|
+
const priorIds = prior.metadata?.caseIds || prior.rows?.map(r => r.id) || [];
|
|
86
|
+
const nextIds = new Set(next.metadata?.caseIds || []);
|
|
87
|
+
const missing = priorIds.filter(id => !nextIds.has(id));
|
|
88
|
+
if (missing.length > 0) {
|
|
89
|
+
throw new Error(`Resume mismatch: corpusHash (missing cases: ${missing.slice(0, 5).join(', ')})`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function loadReport(outPath) {
|
|
95
|
+
return JSON.parse(await fs.readFile(outPath, 'utf8'));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function loadReportWithBackups(outPath) {
|
|
99
|
+
for (const candidate of [outPath, `${outPath}.bak.1`, `${outPath}.bak`]) {
|
|
100
|
+
try {
|
|
101
|
+
const report = await loadReport(candidate);
|
|
102
|
+
if (Array.isArray(report?.rows) && report.rows.length > 0) return report;
|
|
103
|
+
} catch {}
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function acquireRunLock(outPath, { staleMs = STALE_LOCK_MS } = {}) {
|
|
109
|
+
const key = `${path.resolve(outPath).toLowerCase()}`;
|
|
110
|
+
if (activeRunLocks.has(key)) {
|
|
111
|
+
throw lockConflictError(`Another evaluation is already writing ${outPath} in this process`);
|
|
112
|
+
}
|
|
113
|
+
const lockPath = `${path.resolve(outPath)}.lock`;
|
|
114
|
+
for (;;) {
|
|
115
|
+
try {
|
|
116
|
+
await fs.writeFile(lockPath, String(process.pid), { flag: 'wx' });
|
|
117
|
+
break;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (error.code !== 'EEXIST') throw error;
|
|
120
|
+
let stale = true;
|
|
121
|
+
try {
|
|
122
|
+
const stat = await fs.stat(lockPath);
|
|
123
|
+
stale = (Date.now() - stat.mtimeMs) > staleMs;
|
|
124
|
+
} catch {}
|
|
125
|
+
if (!stale) {
|
|
126
|
+
throw lockConflictError(`Another evaluation run holds the lock for ${outPath}. If no run is active, delete ${lockPath}.`);
|
|
127
|
+
}
|
|
128
|
+
await fs.rm(lockPath, { force: true }).catch(() => {});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
activeRunLocks.add(key);
|
|
132
|
+
let released = false;
|
|
133
|
+
return {
|
|
134
|
+
async touch() {
|
|
135
|
+
if (!released) await fs.utimes(lockPath, new Date(), new Date()).catch(() => {});
|
|
136
|
+
},
|
|
137
|
+
async release() {
|
|
138
|
+
if (released) return;
|
|
139
|
+
released = true;
|
|
140
|
+
activeRunLocks.delete(key);
|
|
141
|
+
await fs.rm(lockPath, { force: true }).catch(() => {});
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function rotateBackups(filePath, maxKeep = 5) {
|
|
147
|
+
try {
|
|
148
|
+
const stat = await fs.stat(filePath).catch(() => null);
|
|
149
|
+
if (!stat || stat.size === 0) return;
|
|
150
|
+
for (let i = maxKeep - 1; i >= 1; i--) {
|
|
151
|
+
const src = `${filePath}.bak.${i}`;
|
|
152
|
+
const dst = `${filePath}.bak.${i + 1}`;
|
|
153
|
+
await fs.rename(src, dst).catch(() => {});
|
|
154
|
+
}
|
|
155
|
+
await fs.copyFile(filePath, `${filePath}.bak.1`).catch(() => {});
|
|
156
|
+
} catch {}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function archiveSessionReport(report, outPath) {
|
|
160
|
+
if (!report?.rows?.length) return null;
|
|
161
|
+
if (report.metadata?.archivedAt) return null;
|
|
162
|
+
try {
|
|
163
|
+
const historyDir = path.join(path.dirname(outPath), 'history');
|
|
164
|
+
await fs.mkdir(historyDir, { recursive: true });
|
|
165
|
+
const now = new Date();
|
|
166
|
+
const pad = n => String(n).padStart(2, '0');
|
|
167
|
+
const ms = String(now.getMilliseconds()).padStart(3, '0');
|
|
168
|
+
const timestamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}-${ms}`;
|
|
169
|
+
const planned = report.metadata?.caseIds?.length || report.rows.length;
|
|
170
|
+
const archive = {
|
|
171
|
+
...report,
|
|
172
|
+
metadata: {
|
|
173
|
+
...report.metadata,
|
|
174
|
+
archivedAt: now.toISOString(),
|
|
175
|
+
archivedPartial: report.rows.length < planned,
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
const archivePath = path.join(historyDir, `run_${timestamp}.json`);
|
|
179
|
+
await fs.writeFile(archivePath, JSON.stringify(archive, null, 2), 'utf8');
|
|
180
|
+
report.metadata.archivedAt = now.toISOString();
|
|
181
|
+
|
|
182
|
+
const files = await fs.readdir(historyDir);
|
|
183
|
+
const runFiles = files.filter(f => f.startsWith('run_') && f.endsWith('.json')).sort();
|
|
184
|
+
if (runFiles.length > 20) {
|
|
185
|
+
const toDelete = runFiles.slice(0, runFiles.length - 20);
|
|
186
|
+
for (const f of toDelete) {
|
|
187
|
+
await fs.unlink(path.join(historyDir, f)).catch(() => {});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return archivePath;
|
|
191
|
+
} catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function createSaver(outPath, report) {
|
|
197
|
+
let unflushedResults = 0;
|
|
198
|
+
let lastFlushTime = Date.now();
|
|
199
|
+
let hasArchived = false;
|
|
200
|
+
|
|
201
|
+
return async function save(latestRow = null, options = {}) {
|
|
202
|
+
if (latestRow) unflushedResults++;
|
|
203
|
+
const shouldFlush = !latestRow || options.force || unflushedResults >= 5 || (Date.now() - lastFlushTime) >= 30000;
|
|
204
|
+
if (shouldFlush) {
|
|
205
|
+
report.summary = summarize(report.rows, report.metadata?.caseIds?.length || report.rows.length);
|
|
206
|
+
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
207
|
+
await fs.writeFile(`${outPath}.tmp`, JSON.stringify(report, null, 2));
|
|
208
|
+
await fs.rename(`${outPath}.tmp`, outPath);
|
|
209
|
+
unflushedResults = 0;
|
|
210
|
+
lastFlushTime = Date.now();
|
|
211
|
+
|
|
212
|
+
if (report.metadata?.finishedAt && !hasArchived && report.rows?.length > 0) {
|
|
213
|
+
hasArchived = true;
|
|
214
|
+
await archiveSessionReport(report, outPath).catch(() => {});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function blockedReportPathFor(outPath) {
|
|
221
|
+
const resolved = path.resolve(outPath);
|
|
222
|
+
return resolved.endsWith('.json') ? `${resolved.slice(0, -5)}.blocked.json` : `${resolved}.blocked.json`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export async function writeBlockedReport(outPath, { reason, planned }) {
|
|
226
|
+
const blockedPath = blockedReportPathFor(outPath);
|
|
227
|
+
await fs.mkdir(path.dirname(blockedPath), { recursive: true });
|
|
228
|
+
await fs.writeFile(blockedPath, JSON.stringify({
|
|
229
|
+
status: 'blocked',
|
|
230
|
+
reason,
|
|
231
|
+
planned,
|
|
232
|
+
completed: 0,
|
|
233
|
+
writtenAt: new Date().toISOString(),
|
|
234
|
+
}, null, 2));
|
|
235
|
+
return blockedPath;
|
|
236
|
+
}
|