renor-engine 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/NOTICE +73 -0
- package/README.md +105 -2
- package/dist/BUILD.json +6 -0
- package/dist/api/apiAuth.js +43 -0
- package/dist/api/server.js +172 -0
- package/dist/buildIdentity.js +132 -0
- package/dist/cli-extractors.js +46 -0
- package/dist/cli.js +491 -0
- package/dist/config.js +65 -0
- package/dist/context/composer.js +320 -0
- package/dist/context/readHonesty.js +478 -0
- package/dist/context/rowStore.js +238 -0
- package/dist/conversation/formatter.js +81 -0
- package/dist/conversation/parser.js +189 -0
- package/dist/conversation/types.js +2 -0
- package/dist/conversation/writer.js +42 -0
- package/dist/db/client.js +147 -0
- package/dist/db/indexSalvage.js +81 -0
- package/dist/db/migrations.js +642 -0
- package/dist/db/readCache.js +109 -0
- package/dist/db/schema.js +5 -0
- package/dist/db/statements.js +89 -0
- package/dist/digests/digestCache.js +142 -0
- package/dist/digests/digests.js +597 -0
- package/dist/digests/worker.js +348 -0
- package/dist/embeddings/caching.js +337 -0
- package/dist/embeddings/coalescing.js +177 -0
- package/dist/embeddings/interactive.js +103 -0
- package/dist/embeddings/local.js +68 -0
- package/dist/embeddings/openai.js +105 -0
- package/dist/embeddings/rebuild.js +166 -0
- package/dist/embeddings/simdScan.js +258 -0
- package/dist/embeddings/space.js +21 -0
- package/dist/embeddings/types.js +14 -0
- package/dist/embeddings/vectorIndex.js +675 -0
- package/dist/embeddings/vertex.js +224 -0
- package/dist/eval/artifacts.js +42 -0
- package/dist/eval/cacheReport.js +139 -0
- package/dist/eval/comparison.js +182 -0
- package/dist/eval/context700-candidate.js +145 -0
- package/dist/eval/evalCache.js +525 -0
- package/dist/eval/hashEmbedding.js +35 -0
- package/dist/eval/longmemeval.js +56 -0
- package/dist/eval/metrics.js +181 -0
- package/dist/eval/renorbenchBaseline.js +486 -0
- package/dist/eval/renorbenchDocuments.js +134 -0
- package/dist/eval/renorbenchProduct.js +182 -0
- package/dist/eval/runLedger.js +279 -0
- package/dist/eval/runManifest.js +479 -0
- package/dist/eval/scoring.js +197 -0
- package/dist/evidence/attach.js +267 -0
- package/dist/evidence/background.js +147 -0
- package/dist/evidence/evidenceCache.js +182 -0
- package/dist/evidence/gate.js +37 -0
- package/dist/evidence/keyPool.js +85 -0
- package/dist/evidence/ollamaChat.js +84 -0
- package/dist/evidence/optin.js +165 -0
- package/dist/evidence/options.js +130 -0
- package/dist/evidence/secondPass.js +456 -0
- package/dist/extraction/classifyCapture.js +34 -0
- package/dist/extraction/decision-model.js +174 -0
- package/dist/extraction/eventExtractor.js +113 -0
- package/dist/extraction/extractionClient.js +122 -0
- package/dist/extraction/factExtractor.js +46 -0
- package/dist/extraction/factsWritePath.js +179 -0
- package/dist/extraction/preferenceExtractor.js +153 -0
- package/dist/extraction/secretGuard.js +176 -0
- package/dist/extraction/secretScanner.js +665 -0
- package/dist/extraction/typedFacts.js +224 -0
- package/dist/extraction/writePathAdjudication.js +110 -0
- package/dist/importing/apply.js +644 -0
- package/dist/importing/bundle-write.js +80 -0
- package/dist/importing/cli.js +407 -0
- package/dist/importing/existing.js +49 -0
- package/dist/importing/facts.js +123 -0
- package/dist/importing/forgetImported.js +79 -0
- package/dist/importing/hosted.js +467 -0
- package/dist/importing/index.js +29 -0
- package/dist/importing/journalLog.js +96 -0
- package/dist/importing/markdown.js +98 -0
- package/dist/importing/plan.js +206 -0
- package/dist/importing/secretScreen.js +113 -0
- package/dist/importing/serialize.js +188 -0
- package/dist/importing/source.js +463 -0
- package/dist/importing/types.js +6 -0
- package/dist/indexer/chunker.js +319 -0
- package/dist/indexer/dailyEntries.js +63 -0
- package/dist/indexer/hasher.js +10 -0
- package/dist/indexer/laneRuns.js +48 -0
- package/dist/indexer/o200k.js +481 -0
- package/dist/indexer/o200k.json +8 -0
- package/dist/indexer/outsideRoot.js +77 -0
- package/dist/indexer/parser.js +167 -0
- package/dist/indexer/pipeline.js +1168 -0
- package/dist/indexer/staleness.js +201 -0
- package/dist/indexer/tokenizer.js +77 -0
- package/dist/indexer/walker.js +184 -0
- package/dist/mcp/hive.js +88 -0
- package/dist/mcp/requestErrors.js +47 -0
- package/dist/mcp/server.js +396 -0
- package/dist/memory/agentLease.js +43 -0
- package/dist/memory/confinedWrite.js +315 -0
- package/dist/memory/controls.js +73 -0
- package/dist/memory/dailyNote.js +265 -0
- package/dist/memory/durableAppend.js +57 -0
- package/dist/memory/factConflicts.js +203 -0
- package/dist/memory/factTerms.js +321 -0
- package/dist/memory/hiveNameHeader.cjs +10 -0
- package/dist/memory/inventory.js +55 -0
- package/dist/memory/invisibleText.js +20 -0
- package/dist/memory/lifecycle.js +1756 -0
- package/dist/memory/limits.js +13 -0
- package/dist/memory/requests.js +183 -0
- package/dist/memory/sourceName.js +200 -0
- package/dist/memory/whatsNew.js +158 -0
- package/dist/memory/writer.js +175 -0
- package/dist/memory/writerConflicts.js +85 -0
- package/dist/memorymap/memoryMap.js +1568 -0
- package/dist/memorymap/replacedJournal.js +58 -0
- package/dist/memorymap/replacements.js +405 -0
- package/dist/mvp/activity.js +86 -0
- package/dist/mvp/agentMemory.js +1744 -0
- package/dist/mvp/auth.js +531 -0
- package/dist/mvp/cli.js +73 -0
- package/dist/mvp/client-store.js +462 -0
- package/dist/mvp/compileCache.js +249 -0
- package/dist/mvp/compileSeed.js +27 -0
- package/dist/mvp/config.js +288 -0
- package/dist/mvp/connectionStatus.js +80 -0
- package/dist/mvp/contextRelevance.js +413 -0
- package/dist/mvp/engine.js +1065 -0
- package/dist/mvp/exportAll.js +359 -0
- package/dist/mvp/factLanePack.js +77 -0
- package/dist/mvp/factMarkers.js +49 -0
- package/dist/mvp/factsLane.js +203 -0
- package/dist/mvp/hiddenSpans.js +585 -0
- package/dist/mvp/history.js +413 -0
- package/dist/mvp/hitDates.js +31 -0
- package/dist/mvp/hitFloor.js +36 -0
- package/dist/mvp/identity.js +18 -0
- package/dist/mvp/idle.js +113 -0
- package/dist/mvp/memoryDir.js +98 -0
- package/dist/mvp/posthogMcp.js +210 -0
- package/dist/mvp/readScope.js +104 -0
- package/dist/mvp/readUsefulness.js +222 -0
- package/dist/mvp/sanitize.js +48 -0
- package/dist/mvp/schemaRejections.js +91 -0
- package/dist/mvp/scope.js +127 -0
- package/dist/mvp/server.js +2089 -0
- package/dist/mvp/serverCard.js +29 -0
- package/dist/mvp/storedSize.js +139 -0
- package/dist/mvp/telemetry.js +441 -0
- package/dist/retrieval/adviceLines.js +195 -0
- package/dist/retrieval/adviceQuestion.js +44 -0
- package/dist/retrieval/conversationShape.js +44 -0
- package/dist/retrieval/dailyEntryTime.js +55 -0
- package/dist/retrieval/entityLookup.js +113 -0
- package/dist/retrieval/factsFirst.js +188 -0
- package/dist/retrieval/keyword.js +84 -0
- package/dist/retrieval/ledgerEventTime.js +51 -0
- package/dist/retrieval/ledgerLiveness.js +83 -0
- package/dist/retrieval/ledgerPresentation.js +161 -0
- package/dist/retrieval/noteIdentity.js +57 -0
- package/dist/retrieval/orchestrator.js +1086 -0
- package/dist/retrieval/queryHelpers.js +142 -0
- package/dist/retrieval/questionSpan.js +475 -0
- package/dist/retrieval/readerEvents.js +690 -0
- package/dist/retrieval/readerLayout.js +503 -0
- package/dist/retrieval/relativeTimeHints.js +113 -0
- package/dist/retrieval/semantic.js +67 -0
- package/dist/router/routerCache.js +106 -0
- package/dist/router/sessionRouter.js +726 -0
- package/dist/toolRefusal.js +120 -0
- package/dist/watcher/watcher.js +75 -0
- package/package.json +120 -4
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EVIDENCE_HIT_PATH = void 0;
|
|
4
|
+
exports.searchEvidenceEnabled = searchEvidenceEnabled;
|
|
5
|
+
exports.productEvidenceReport = productEvidenceReport;
|
|
6
|
+
exports.collectEvidence = collectEvidence;
|
|
7
|
+
exports.evidenceItemsToFactRows = evidenceItemsToFactRows;
|
|
8
|
+
exports.renderEvidenceTable = renderEvidenceTable;
|
|
9
|
+
exports.evidenceItemsToHit = evidenceItemsToHit;
|
|
10
|
+
exports.prependEvidenceHit = prependEvidenceHit;
|
|
11
|
+
exports.attachSearchEvidence = attachSearchEvidence;
|
|
12
|
+
const gate_1 = require("./gate");
|
|
13
|
+
const secondPass_1 = require("./secondPass");
|
|
14
|
+
const background_1 = require("./background");
|
|
15
|
+
const optin_1 = require("./optin");
|
|
16
|
+
/**
|
|
17
|
+
* Attach H10-v4 second-pass evidence onto the default product search path.
|
|
18
|
+
*
|
|
19
|
+
* Kill switch: RENOR_SEARCH_EVIDENCE (default ON since the 2026-09-22 owner
|
|
20
|
+
* ship decision — the evidence second pass measured +28 net on LongMemEval-S
|
|
21
|
+
* product runs: 389/500 evidence vs 361/500 baseline, p≈1e-8, official clean
|
|
22
|
+
* re-validation; the kill switch RENOR_SEARCH_EVIDENCE=0 restores the pre-ship
|
|
23
|
+
* behavior byte-for-byte). When off, search returns
|
|
24
|
+
* the same hits array as before this change. When on, the default search /
|
|
25
|
+
* get_context / search_memory path runs the same extractor as the dedicated
|
|
26
|
+
* `evidence` tool (full source-note hydration, 20-note backfill, 6k cap,
|
|
27
|
+
* content-addressed cache) and, on a successful extraction, prepends one
|
|
28
|
+
* synthetic hit so array-only clients still see the table.
|
|
29
|
+
*
|
|
30
|
+
* Retrieval ranking and stored chunk text are never rewritten.
|
|
31
|
+
*/
|
|
32
|
+
exports.EVIDENCE_HIT_PATH = '_renor/evidence';
|
|
33
|
+
function searchEvidenceEnabled(env = process.env) {
|
|
34
|
+
const raw = (env.RENOR_SEARCH_EVIDENCE ?? '').trim().toLowerCase();
|
|
35
|
+
if (raw === '0' || raw === 'false' || raw === 'no' || raw === 'off')
|
|
36
|
+
return false;
|
|
37
|
+
return raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on' || raw === '';
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* One sentence per skip reason, saying whether anything is actually missing. The reader is the
|
|
41
|
+
* user's agent, which can change no setting: the variable or key behind a state stays in `reason`
|
|
42
|
+
* and the engine's docs, not in the sentence.
|
|
43
|
+
*/
|
|
44
|
+
const EVIDENCE_NOTES = {
|
|
45
|
+
'flag-off': 'Evidence second pass is switched off for this deployment. Nothing is missing: the hits below are the whole result.',
|
|
46
|
+
'mode-off': 'Evidence second pass is switched off for this deployment. Nothing is missing: the hits below are the whole result.',
|
|
47
|
+
'extractor-unavailable': 'This deployment has no evidence model configured, so no dated item table is prepended. Nothing is broken and nothing is missing: the hits below are the whole result.',
|
|
48
|
+
'intent': 'The evidence gate did not select this query, so no dated item table was prepended. Nothing is missing: the hits below are the whole result.',
|
|
49
|
+
'no-sessions': 'The ranked hits carried no dated source session to extract items from. Nothing is missing: the hits below are the whole result.',
|
|
50
|
+
'extractor-parse-error': 'The evidence model answered in a shape the parser rejects, so no dated item table was prepended. Retrieval is unaffected: the hits below are the whole result.',
|
|
51
|
+
'extractor-error': 'The evidence model call failed, so no dated item table was prepended. Retrieval is unaffected: the hits below are the whole result.',
|
|
52
|
+
// The two below exist only with bounded-wait evidence on (RENOR_EVIDENCE_ASYNC): the table is missing from this
|
|
53
|
+
// response, not from the product. get_context quotes them too, so they name no position in the payload.
|
|
54
|
+
'pending': 'The evidence model did not finish within the time this deployment waits for it, so this response has no dated item table. Retrieval is unaffected. The extraction continues in the background; when it succeeds its table is cached, and the same request over the same notes returns it.',
|
|
55
|
+
'queue-full': 'The evidence model already has as many extractions waiting as this deployment allows, so none was started for this request and this response has no dated item table. Retrieval is unaffected. The same request starts one once the queue drains.',
|
|
56
|
+
// Only a workspace that opted in to the Ollama extractor has a daily allowance of model calls.
|
|
57
|
+
'daily-cap': 'This workspace has used its daily allowance of evidence-model calls, so this response has no dated item table. Retrieval is unaffected: the hits below are the whole result. The allowance starts again at 00:00 UTC.',
|
|
58
|
+
};
|
|
59
|
+
const EVIDENCE_NOTE_DEFAULT = 'The evidence second pass ran and extracted no items. Nothing is missing: the hits below are the whole result.';
|
|
60
|
+
const EVIDENCE_NOTE_OK = 'The evidence second pass extracted items; the dated item table is prepended as the first hit above.';
|
|
61
|
+
function evidenceState(evidence) {
|
|
62
|
+
if (evidence.status === 'ok')
|
|
63
|
+
return 'ok';
|
|
64
|
+
if (evidence.reason === 'flag-off' || evidence.reason === 'mode-off')
|
|
65
|
+
return 'off';
|
|
66
|
+
if (evidence.reason === 'extractor-unavailable')
|
|
67
|
+
return 'unavailable';
|
|
68
|
+
if (evidence.reason === 'extractor-parse-error' || evidence.reason === 'extractor-error')
|
|
69
|
+
return 'failed';
|
|
70
|
+
if (evidence.reason === 'pending')
|
|
71
|
+
return 'pending';
|
|
72
|
+
if (evidence.reason === 'queue-full')
|
|
73
|
+
return 'busy';
|
|
74
|
+
if (evidence.reason === 'daily-cap')
|
|
75
|
+
return 'capped';
|
|
76
|
+
return 'not-applied';
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The same attachment, plus `state` and `note`. Nothing existing is renamed,
|
|
80
|
+
* dropped or revalued — only the two fields a reader needs to tell an intended
|
|
81
|
+
* configuration from a fault.
|
|
82
|
+
*/
|
|
83
|
+
function productEvidenceReport(evidence) {
|
|
84
|
+
return {
|
|
85
|
+
...evidence,
|
|
86
|
+
state: evidenceState(evidence),
|
|
87
|
+
note: evidence.status === 'ok'
|
|
88
|
+
? EVIDENCE_NOTE_OK
|
|
89
|
+
: ((evidence.reason !== undefined ? EVIDENCE_NOTES[evidence.reason] : undefined) ?? EVIDENCE_NOTE_DEFAULT),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function collectEvidence(args) {
|
|
93
|
+
const decision = (0, gate_1.evidenceGate)(args.options.mode, args.query, args.force === true);
|
|
94
|
+
if (!decision.run) {
|
|
95
|
+
return { status: 'skipped', reason: decision.reason };
|
|
96
|
+
}
|
|
97
|
+
const sessions = await (0, secondPass_1.loadEvidenceSessionsFromVault)(args.hits, args.vaultPath, {
|
|
98
|
+
transformBody: args.transformBody,
|
|
99
|
+
maxSessions: args.options.maxSessions,
|
|
100
|
+
charCap: args.options.charCap,
|
|
101
|
+
// Body-relevance selection input (RENOR_EVIDENCE_BODY_RANK); ignored when
|
|
102
|
+
// the flag is off.
|
|
103
|
+
query: args.query,
|
|
104
|
+
});
|
|
105
|
+
if (sessions.length === 0) {
|
|
106
|
+
return { status: 'skipped', reason: 'no-sessions' };
|
|
107
|
+
}
|
|
108
|
+
if (!args.extractor) {
|
|
109
|
+
return { status: 'skipped', reason: 'extractor-unavailable' };
|
|
110
|
+
}
|
|
111
|
+
const extractor = args.extractor;
|
|
112
|
+
// The cached table at once, else the failure this very request met within EVIDENCE_FAILURE_TTL_MS: it is said again
|
|
113
|
+
// (fromCache) instead of being paid for again. A table wins over a failure remembered for the same key.
|
|
114
|
+
const key = extractor.cacheKey(args.query, sessions);
|
|
115
|
+
const cached = extractor.replay(key);
|
|
116
|
+
if (cached)
|
|
117
|
+
return evidenceOk(decision.reason, cached);
|
|
118
|
+
const failed = extractor.replayFailure(key);
|
|
119
|
+
if (failed)
|
|
120
|
+
return { status: 'skipped', reason: 'extractor-parse-error', fromCache: true, model: failed.model, error: failed.error };
|
|
121
|
+
const extract = () => extractEvidence(extractor, key, args.query, sessions, decision.reason, args.parseRetries);
|
|
122
|
+
const background = args.options.background;
|
|
123
|
+
if (!background)
|
|
124
|
+
return extract();
|
|
125
|
+
// Bounded wait (RENOR_EVIDENCE_ASYNC): this prompt's one extraction, waited for at most background.waitMs. The
|
|
126
|
+
// flight is keyed by cache file too, so it always fills the cache this search reads.
|
|
127
|
+
const flight = background.flights.join(`${args.options.cachePath ?? ''}\0${key}`, extract);
|
|
128
|
+
if (!flight)
|
|
129
|
+
return { status: 'skipped', reason: 'queue-full' };
|
|
130
|
+
background.hold?.(flight);
|
|
131
|
+
const settled = await (0, background_1.waitAtMost)(flight, background.waitMs);
|
|
132
|
+
if (!settled)
|
|
133
|
+
return { status: 'skipped', reason: 'pending' };
|
|
134
|
+
// A joined flight may have been started by a search whose gate gave another reason.
|
|
135
|
+
return settled.value.status === 'ok' ? { ...settled.value, reason: decision.reason } : settled.value;
|
|
136
|
+
}
|
|
137
|
+
function evidenceOk(reason, result) {
|
|
138
|
+
return {
|
|
139
|
+
status: 'ok',
|
|
140
|
+
reason,
|
|
141
|
+
fromCache: result.fromCache,
|
|
142
|
+
model: result.model,
|
|
143
|
+
items: result.items,
|
|
144
|
+
sessions: result.sessions,
|
|
145
|
+
usage: result.usage,
|
|
146
|
+
latencyMs: result.latencyMs,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* One extraction, retried on a reply the parser rejects; any other failure is thrown. When the last attempt, too, was
|
|
151
|
+
* answered with a reply that is not a table, that failure is remembered under `key`; an error before any reply never is.
|
|
152
|
+
*/
|
|
153
|
+
async function extractEvidence(extractor, key, query, sessions, reason, parseRetries) {
|
|
154
|
+
const maxAttempts = Math.max(1, parseRetries ?? 3);
|
|
155
|
+
let lastError;
|
|
156
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
157
|
+
try {
|
|
158
|
+
return evidenceOk(reason, await extractor.extract(query, sessions));
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
// The workspace's daily allowance is spent: no call was made, and the search goes on without the table.
|
|
162
|
+
if (err instanceof optin_1.EvidenceCapError)
|
|
163
|
+
return { status: 'skipped', reason: 'daily-cap', model: extractor.model };
|
|
164
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
165
|
+
const parseFailure = /JSON|items list|non-empty item|must be an object/i.test(lastError.message);
|
|
166
|
+
if (!parseFailure || attempt === maxAttempts)
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const parseFailure = lastError && /JSON|items list|non-empty item|must be an object/i.test(lastError.message);
|
|
171
|
+
if (parseFailure && lastError) {
|
|
172
|
+
const error = lastError.message.slice(0, 200);
|
|
173
|
+
if (lastError instanceof secondPass_1.EvidenceReplyError)
|
|
174
|
+
extractor.rememberFailure(key, error);
|
|
175
|
+
return {
|
|
176
|
+
status: 'skipped',
|
|
177
|
+
reason: 'extractor-parse-error',
|
|
178
|
+
model: extractor.model,
|
|
179
|
+
error,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
throw lastError ?? new Error('evidence extraction failed');
|
|
183
|
+
}
|
|
184
|
+
/** H10-v4 fact-row mapping used by the frozen QA facts-table renderer. */
|
|
185
|
+
function evidenceItemsToFactRows(items, event = 'second-pass-v4') {
|
|
186
|
+
return items.map(item => {
|
|
187
|
+
const amount = String(item.amount ?? '').trim();
|
|
188
|
+
const match = amount.match(/^\$?([\d,]+(?:\.\d+)?)\s*(.*)$/);
|
|
189
|
+
let value = 1;
|
|
190
|
+
let unit = amount;
|
|
191
|
+
if (amount && match) {
|
|
192
|
+
value = match[1].replace(/,/g, '');
|
|
193
|
+
unit = match[2] ?? '';
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
value,
|
|
197
|
+
unit,
|
|
198
|
+
verb: item.action ?? '',
|
|
199
|
+
object: item.item ?? '',
|
|
200
|
+
event,
|
|
201
|
+
confirm: 1,
|
|
202
|
+
dates: [item.date ?? ''],
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function renderEvidenceTable(items) {
|
|
207
|
+
const lines = items.map(item => {
|
|
208
|
+
const action = item.action?.trim() ? ` | ${item.action.trim()}` : '';
|
|
209
|
+
const amount = item.amount?.trim() ? ` | ${item.amount.trim()}` : '';
|
|
210
|
+
const date = item.date?.trim() ? ` | ${item.date.trim()}` : '';
|
|
211
|
+
return `- ${item.item}${action}${amount}${date}`;
|
|
212
|
+
});
|
|
213
|
+
return [
|
|
214
|
+
'User-asserted items extracted from full source notes (with dates):',
|
|
215
|
+
...lines,
|
|
216
|
+
'Use this table when relevant; cite only values present here. If the table does not contain the value needed to answer, use the excerpts instead.',
|
|
217
|
+
].join('\n');
|
|
218
|
+
}
|
|
219
|
+
function evidenceItemsToHit(items) {
|
|
220
|
+
return {
|
|
221
|
+
notePath: exports.EVIDENCE_HIT_PATH,
|
|
222
|
+
chunkId: -1,
|
|
223
|
+
headingPath: 'Evidence',
|
|
224
|
+
text: renderEvidenceTable(items),
|
|
225
|
+
score: 1,
|
|
226
|
+
reason: 'evidence',
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function prependEvidenceHit(hits, attachment) {
|
|
230
|
+
if (attachment.status !== 'ok' || !attachment.items?.length)
|
|
231
|
+
return hits;
|
|
232
|
+
if (hits.some(hit => hit.notePath === exports.EVIDENCE_HIT_PATH && hit.reason === 'evidence'))
|
|
233
|
+
return hits;
|
|
234
|
+
return [evidenceItemsToHit(attachment.items), ...hits];
|
|
235
|
+
}
|
|
236
|
+
async function attachSearchEvidence(args) {
|
|
237
|
+
if (!(args.enabled ?? searchEvidenceEnabled())) {
|
|
238
|
+
return { hits: args.hits, evidence: { status: 'skipped', reason: 'flag-off' } };
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const evidence = await collectEvidence({
|
|
242
|
+
transformBody: args.transformBody,
|
|
243
|
+
query: args.query,
|
|
244
|
+
hits: args.hits,
|
|
245
|
+
vaultPath: args.vaultPath,
|
|
246
|
+
options: args.options,
|
|
247
|
+
extractor: args.extractor,
|
|
248
|
+
force: args.force,
|
|
249
|
+
});
|
|
250
|
+
return { hits: prependEvidenceHit(args.hits, evidence), evidence };
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
// Evidence is optional augmentation. Provider/network/cache failures must
|
|
254
|
+
// never take down the base search path, but they are said, with the
|
|
255
|
+
// extractor that failed, never left as a silently missing table (#222).
|
|
256
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
257
|
+
return {
|
|
258
|
+
hits: args.hits,
|
|
259
|
+
evidence: {
|
|
260
|
+
status: 'skipped',
|
|
261
|
+
reason: 'extractor-error',
|
|
262
|
+
...(args.extractor ? { model: args.extractor.model } : {}),
|
|
263
|
+
error: message.slice(0, 200),
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EvidenceFlights = exports.EVIDENCE_BACKGROUND_QUEUE = exports.EVIDENCE_BACKGROUND_CONCURRENCY = exports.EVIDENCE_WAIT_MS_MAX = exports.EVIDENCE_WAIT_MS_DEFAULT = void 0;
|
|
4
|
+
exports.evidenceAsyncEnabled = evidenceAsyncEnabled;
|
|
5
|
+
exports.resolveEvidenceWaitMs = resolveEvidenceWaitMs;
|
|
6
|
+
exports.resolveEvidenceBackground = resolveEvidenceBackground;
|
|
7
|
+
exports.sharedEvidenceFlights = sharedEvidenceFlights;
|
|
8
|
+
exports.waitAtMost = waitAtMost;
|
|
9
|
+
/**
|
|
10
|
+
* Bounded-wait evidence for get_context and search_memory (RENOR_EVIDENCE_ASYNC, default OFF).
|
|
11
|
+
*
|
|
12
|
+
* The evidence second pass runs inside the read tools, so a search waits for the extractor: about 1-4 s with today's
|
|
13
|
+
* model, 5-27 s on average with the better ones measured on 2026-10-03 (p95 up to 76 s). With this flag off nothing
|
|
14
|
+
* here runs and every search waits, as before. With it on, a search waits at most RENOR_EVIDENCE_WAIT_MS:
|
|
15
|
+
*
|
|
16
|
+
* - a table already in the evidence cache (keyed on model + exact prompt) is returned at once;
|
|
17
|
+
* - otherwise the extraction starts, and if it does not finish within the wait the response has no table and says
|
|
18
|
+
* so (`state: "pending"`); the extraction carries on in the background and its table lands in the cache, so the
|
|
19
|
+
* same query over the same notes returns it;
|
|
20
|
+
* - one extraction per prompt (single-flight): a search for a prompt already being extracted joins that extraction;
|
|
21
|
+
* - at most EVIDENCE_BACKGROUND_CONCURRENCY extractions run and EVIDENCE_BACKGROUND_QUEUE wait; a search past that
|
|
22
|
+
* starts nothing and says so (`state: "busy"`).
|
|
23
|
+
*
|
|
24
|
+
* Only the MVP read tools read the flag (resolveEngineEvidence). The CLI, which is the benchmark's entry point, the
|
|
25
|
+
* HTTP API and the stdio MCP server never do, so no benchmark run can change behaviour because of it.
|
|
26
|
+
*/
|
|
27
|
+
exports.EVIDENCE_WAIT_MS_DEFAULT = 2_000;
|
|
28
|
+
exports.EVIDENCE_WAIT_MS_MAX = 60_000;
|
|
29
|
+
/** Extractions running at once, per process (one process serves one workspace). */
|
|
30
|
+
exports.EVIDENCE_BACKGROUND_CONCURRENCY = 2;
|
|
31
|
+
/** Extractions waiting for a free slot, per process. */
|
|
32
|
+
exports.EVIDENCE_BACKGROUND_QUEUE = 16;
|
|
33
|
+
/** RENOR_EVIDENCE_ASYNC: on only for 1 / true / yes / on; unset and anything else is off. */
|
|
34
|
+
function evidenceAsyncEnabled(env = process.env) {
|
|
35
|
+
const raw = (env.RENOR_EVIDENCE_ASYNC ?? '').trim().toLowerCase();
|
|
36
|
+
return raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on';
|
|
37
|
+
}
|
|
38
|
+
/** RENOR_EVIDENCE_WAIT_MS: whole milliseconds, 0 to 60,000; unset or unreadable is the default. */
|
|
39
|
+
function resolveEvidenceWaitMs(env = process.env) {
|
|
40
|
+
const raw = (env.RENOR_EVIDENCE_WAIT_MS ?? '').trim();
|
|
41
|
+
const ms = raw === '' ? Number.NaN : Number(raw);
|
|
42
|
+
if (!Number.isFinite(ms) || ms < 0)
|
|
43
|
+
return exports.EVIDENCE_WAIT_MS_DEFAULT;
|
|
44
|
+
return Math.min(Math.floor(ms), exports.EVIDENCE_WAIT_MS_MAX);
|
|
45
|
+
}
|
|
46
|
+
/** The bounded-wait settings when the flag is on, else undefined: a search then waits for the extractor, as before. */
|
|
47
|
+
function resolveEvidenceBackground(env = process.env) {
|
|
48
|
+
if (!evidenceAsyncEnabled(env))
|
|
49
|
+
return undefined;
|
|
50
|
+
return { waitMs: resolveEvidenceWaitMs(env), flights: sharedEvidenceFlights() };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The extractions in the air, one per key, with at most `concurrency` running and `queue` waiting.
|
|
54
|
+
*
|
|
55
|
+
* Every search of a process must share one of these: the MVP server builds a fresh tool set (and extractor) for each
|
|
56
|
+
* SSE session, so flights kept per extractor would neither join a second session's identical search nor bound the
|
|
57
|
+
* process's spend.
|
|
58
|
+
*/
|
|
59
|
+
class EvidenceFlights {
|
|
60
|
+
concurrency;
|
|
61
|
+
queue;
|
|
62
|
+
inFlight = new Map();
|
|
63
|
+
waiting = [];
|
|
64
|
+
running = 0;
|
|
65
|
+
/**
|
|
66
|
+
* started: extractions begun; joined: searches that shared one; refused: searches turned away by a full queue;
|
|
67
|
+
* failed: extractions that ended without a table.
|
|
68
|
+
*/
|
|
69
|
+
counts = { started: 0, joined: 0, refused: 0, failed: 0 };
|
|
70
|
+
constructor(limits = {}) {
|
|
71
|
+
this.concurrency = Math.max(1, limits.concurrency ?? exports.EVIDENCE_BACKGROUND_CONCURRENCY);
|
|
72
|
+
this.queue = Math.max(0, limits.queue ?? exports.EVIDENCE_BACKGROUND_QUEUE);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The one extraction of `key`: the flight already in the air, or a new one that `run` performs now or once a slot is
|
|
76
|
+
* free. Null when the queue is full, and then nothing is started. The key is deleted when the flight ends, after
|
|
77
|
+
* `run` has written the cache, so a later search finds the table there instead of starting again.
|
|
78
|
+
*/
|
|
79
|
+
join(key, run) {
|
|
80
|
+
const current = this.inFlight.get(key);
|
|
81
|
+
if (current) {
|
|
82
|
+
this.counts.joined += 1;
|
|
83
|
+
return current;
|
|
84
|
+
}
|
|
85
|
+
if (this.running >= this.concurrency && this.waiting.length >= this.queue) {
|
|
86
|
+
this.counts.refused += 1;
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const slot = new Promise(resolve => {
|
|
90
|
+
if (this.running < this.concurrency) {
|
|
91
|
+
this.running += 1;
|
|
92
|
+
resolve();
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
this.waiting.push(() => {
|
|
96
|
+
this.running += 1;
|
|
97
|
+
resolve();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
const flight = slot.then(run).finally(() => {
|
|
102
|
+
this.inFlight.delete(key);
|
|
103
|
+
this.running -= 1;
|
|
104
|
+
this.waiting.shift()?.();
|
|
105
|
+
});
|
|
106
|
+
// A flight outlives the searches that started it, so its end is observed here: when it fails after they stopped
|
|
107
|
+
// waiting nobody else holds the promise, and a rejection must never surface as an unhandled one; and an operator
|
|
108
|
+
// reading the log is the only one left to learn that it failed. The reason code only: never the query or the
|
|
109
|
+
// provider's message, which may quote the user's notes.
|
|
110
|
+
flight.then(outcome => {
|
|
111
|
+
if (outcome.status !== 'ok')
|
|
112
|
+
this.ended(outcome.reason ?? 'skipped');
|
|
113
|
+
}, () => this.ended('extractor-error'));
|
|
114
|
+
this.inFlight.set(key, flight);
|
|
115
|
+
this.counts.started += 1;
|
|
116
|
+
return flight;
|
|
117
|
+
}
|
|
118
|
+
ended(reason) {
|
|
119
|
+
this.counts.failed += 1;
|
|
120
|
+
process.stderr.write(`[evidence] a background extraction ended without a table (${reason})\n`);
|
|
121
|
+
}
|
|
122
|
+
/** Resolves once nothing is running or waiting (tests, benchmarks). */
|
|
123
|
+
async idle() {
|
|
124
|
+
while (this.inFlight.size > 0)
|
|
125
|
+
await Promise.allSettled([...this.inFlight.values()]);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
exports.EvidenceFlights = EvidenceFlights;
|
|
129
|
+
let shared;
|
|
130
|
+
/** The process's flights, created on first use. */
|
|
131
|
+
function sharedEvidenceFlights() {
|
|
132
|
+
shared ??= new EvidenceFlights();
|
|
133
|
+
return shared;
|
|
134
|
+
}
|
|
135
|
+
/** `promise`'s value if it settles within `ms`, else undefined. A later rejection is not this caller's to handle. */
|
|
136
|
+
function waitAtMost(promise, ms) {
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
const timer = setTimeout(() => resolve(undefined), ms);
|
|
139
|
+
promise.then(value => {
|
|
140
|
+
clearTimeout(timer);
|
|
141
|
+
resolve({ value });
|
|
142
|
+
}, err => {
|
|
143
|
+
clearTimeout(timer);
|
|
144
|
+
reject(err);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.EvidenceCache = exports.EVIDENCE_FAILURE_TTL_MS = exports.EVIDENCE_SCHEMA_VERSION = void 0;
|
|
7
|
+
exports.dropEvidenceForStems = dropEvidenceForStems;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
12
|
+
/**
|
|
13
|
+
* Content-addressed cache for the H10 second-pass evidence extraction.
|
|
14
|
+
*
|
|
15
|
+
* Key = sha256(EVIDENCE_SCHEMA_VERSION \0 model \0 prompt), where prompt is the
|
|
16
|
+
* exact rendered extractor prompt; a `bounds:` segment precedes the prompt only
|
|
17
|
+
* when RENOR_EVIDENCE_MAX_TOKENS or RENOR_EVIDENCE_REASONING_EFFORT is set. Any
|
|
18
|
+
* question, session-text, selection, or prompt-template change invalidates only
|
|
19
|
+
* the affected entries. The same question over unchanged session content
|
|
20
|
+
* replays for zero model cost.
|
|
21
|
+
*
|
|
22
|
+
* Stores parsed items (the extractor's JSON must parse before an entry is
|
|
23
|
+
* written — fail-fast extraction never caches a malformed response). A failed
|
|
24
|
+
* extraction is remembered apart from the tables, under the same key, for
|
|
25
|
+
* EVIDENCE_FAILURE_TTL_MS (table evidence_failures).
|
|
26
|
+
*
|
|
27
|
+
* Design follows the eval cache (src/eval/evalCache.ts): SQLite + WAL +
|
|
28
|
+
* busy_timeout=10000 + INSERT OR IGNORE.
|
|
29
|
+
*/
|
|
30
|
+
exports.EVIDENCE_SCHEMA_VERSION = 'evidence-v1';
|
|
31
|
+
/**
|
|
32
|
+
* How long a failed extraction is remembered: 24 hours.
|
|
33
|
+
*
|
|
34
|
+
* Measured 2026-10-03 (GLM-5.3-Flash on Nebius, dev100): 39% of extraction
|
|
35
|
+
* spend went to replies that reasoned to the provider's 8,192-token cap and
|
|
36
|
+
* returned no content. Only tables were cached, so every repeat of such a
|
|
37
|
+
* search paid for its three attempts again, and waited minutes for them.
|
|
38
|
+
*
|
|
39
|
+
* Only a reply is remembered: the model answered the last attempt, and its
|
|
40
|
+
* answer is not a table (secondPass.ts EvidenceReplyError). An error before any
|
|
41
|
+
* reply (a 429, a 5xx, a timeout, a refused request, no connection) says
|
|
42
|
+
* nothing about the request and is never remembered: the next search asks
|
|
43
|
+
* again. The key is the table's (model, bounds, exact prompt), so anything that
|
|
44
|
+
* could change the outcome by itself (other notes, another question, model or
|
|
45
|
+
* bound) is a new key and is asked at once. What a day gives up is luck, the
|
|
46
|
+
* same request answered better next time, for at most one paid failure per
|
|
47
|
+
* request per day. DELETE FROM evidence_failures forgets them all at once.
|
|
48
|
+
*/
|
|
49
|
+
exports.EVIDENCE_FAILURE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
50
|
+
class EvidenceCache {
|
|
51
|
+
db;
|
|
52
|
+
lookupStmt;
|
|
53
|
+
insertStmt;
|
|
54
|
+
touchStmt;
|
|
55
|
+
failureLookupStmt;
|
|
56
|
+
failureTouchStmt;
|
|
57
|
+
failureSweepStmt;
|
|
58
|
+
failurePutStmt;
|
|
59
|
+
constructor(dbPath) {
|
|
60
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(dbPath), { recursive: true });
|
|
61
|
+
this.db = new better_sqlite3_1.default(dbPath);
|
|
62
|
+
this.db.pragma('journal_mode = WAL');
|
|
63
|
+
this.db.pragma('busy_timeout = 10000');
|
|
64
|
+
this.db.exec(`
|
|
65
|
+
CREATE TABLE IF NOT EXISTS evidence_cache (
|
|
66
|
+
cache_key TEXT PRIMARY KEY,
|
|
67
|
+
schema_version TEXT NOT NULL,
|
|
68
|
+
model TEXT NOT NULL,
|
|
69
|
+
question TEXT NOT NULL,
|
|
70
|
+
sessions_json TEXT NOT NULL,
|
|
71
|
+
items_json TEXT NOT NULL,
|
|
72
|
+
prompt_tokens INTEGER NOT NULL DEFAULT 0,
|
|
73
|
+
completion_tokens INTEGER NOT NULL DEFAULT 0,
|
|
74
|
+
created_at TEXT NOT NULL,
|
|
75
|
+
last_used_at TEXT,
|
|
76
|
+
hits INTEGER NOT NULL DEFAULT 0
|
|
77
|
+
)
|
|
78
|
+
`);
|
|
79
|
+
// Cache lookups are content-addressed by the cache_key primary key. Older
|
|
80
|
+
// evidence-v1 databases created a secondary question index that no runtime
|
|
81
|
+
// query uses; remove it so inserts do not maintain a redundant B-tree.
|
|
82
|
+
this.db.exec('DROP INDEX IF EXISTS idx_evidence_cache_question');
|
|
83
|
+
this.db.exec(`
|
|
84
|
+
CREATE TABLE IF NOT EXISTS evidence_failures (
|
|
85
|
+
cache_key TEXT PRIMARY KEY,
|
|
86
|
+
model TEXT NOT NULL,
|
|
87
|
+
error TEXT NOT NULL,
|
|
88
|
+
failed_at_ms INTEGER NOT NULL,
|
|
89
|
+
hits INTEGER NOT NULL DEFAULT 0
|
|
90
|
+
)
|
|
91
|
+
`);
|
|
92
|
+
this.lookupStmt = this.db.prepare('SELECT items_json, sessions_json, model, question, prompt_tokens, completion_tokens, created_at, hits FROM evidence_cache WHERE cache_key = ?');
|
|
93
|
+
this.insertStmt = this.db.prepare(`INSERT OR IGNORE INTO evidence_cache
|
|
94
|
+
(cache_key, schema_version, model, question, sessions_json, items_json,
|
|
95
|
+
prompt_tokens, completion_tokens, created_at, last_used_at, hits)
|
|
96
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)`);
|
|
97
|
+
this.touchStmt = this.db.prepare('UPDATE evidence_cache SET hits = hits + 1, last_used_at = ? WHERE cache_key = ?');
|
|
98
|
+
this.failureLookupStmt = this.db.prepare('SELECT model, error, failed_at_ms FROM evidence_failures WHERE cache_key = ? AND failed_at_ms > ?');
|
|
99
|
+
this.failureTouchStmt = this.db.prepare('UPDATE evidence_failures SET hits = hits + 1 WHERE cache_key = ?');
|
|
100
|
+
this.failureSweepStmt = this.db.prepare('DELETE FROM evidence_failures WHERE failed_at_ms <= ?');
|
|
101
|
+
this.failurePutStmt = this.db.prepare('INSERT OR REPLACE INTO evidence_failures (cache_key, model, error, failed_at_ms, hits) VALUES (?, ?, ?, ?, 0)');
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* SHA-256 key over schema version, model, any request bounds, and the exact
|
|
105
|
+
* rendered prompt. With no bound set it is the legacy key, so every existing
|
|
106
|
+
* entry still replays; a bound adds one segment, so a bounded arm and an
|
|
107
|
+
* unbounded arm never share an entry.
|
|
108
|
+
*/
|
|
109
|
+
static computeKey(model, prompt, bounds = {}) {
|
|
110
|
+
const bound = [
|
|
111
|
+
...(bounds.maxTokens !== undefined ? [`max_tokens=${bounds.maxTokens}`] : []),
|
|
112
|
+
...(bounds.reasoningEffort !== undefined ? [`reasoning_effort=${bounds.reasoningEffort}`] : []),
|
|
113
|
+
].join(';');
|
|
114
|
+
return node_crypto_1.default.createHash('sha256')
|
|
115
|
+
.update(`${exports.EVIDENCE_SCHEMA_VERSION}\0${model}\0${bound ? `bounds:${bound}\0` : ''}${prompt}`)
|
|
116
|
+
.digest('hex');
|
|
117
|
+
}
|
|
118
|
+
/** Lookup. Returns null on miss. Touches hit counters on a hit. */
|
|
119
|
+
get(cacheKey) {
|
|
120
|
+
const row = this.lookupStmt.get(cacheKey);
|
|
121
|
+
if (!row)
|
|
122
|
+
return null;
|
|
123
|
+
this.touchStmt.run(new Date().toISOString(), cacheKey);
|
|
124
|
+
return {
|
|
125
|
+
items: JSON.parse(row.items_json),
|
|
126
|
+
sessions: JSON.parse(row.sessions_json),
|
|
127
|
+
model: row.model,
|
|
128
|
+
question: row.question,
|
|
129
|
+
promptTokens: row.prompt_tokens,
|
|
130
|
+
completionTokens: row.completion_tokens,
|
|
131
|
+
createdAt: row.created_at,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** Store a freshly extracted, already-parsed entry. Existing keys are never overwritten. */
|
|
135
|
+
put(cacheKey, entry) {
|
|
136
|
+
this.insertStmt.run(cacheKey, exports.EVIDENCE_SCHEMA_VERSION, entry.model, entry.question, JSON.stringify(entry.sessions), JSON.stringify(entry.items), entry.promptTokens, entry.completionTokens, entry.createdAt, entry.createdAt);
|
|
137
|
+
}
|
|
138
|
+
/** The failure remembered under `cacheKey` less than EVIDENCE_FAILURE_TTL_MS ago, or null. Touches its hit counter. */
|
|
139
|
+
getFailure(cacheKey) {
|
|
140
|
+
const row = this.failureLookupStmt.get(cacheKey, Date.now() - exports.EVIDENCE_FAILURE_TTL_MS);
|
|
141
|
+
if (!row)
|
|
142
|
+
return null;
|
|
143
|
+
this.failureTouchStmt.run(cacheKey);
|
|
144
|
+
return { model: row.model, error: row.error, failedAt: row.failed_at_ms };
|
|
145
|
+
}
|
|
146
|
+
/** Remember a failed extraction, replacing any earlier one under the key; expired failures are dropped. */
|
|
147
|
+
putFailure(cacheKey, entry) {
|
|
148
|
+
this.failureSweepStmt.run(entry.failedAt - exports.EVIDENCE_FAILURE_TTL_MS);
|
|
149
|
+
this.failurePutStmt.run(cacheKey, entry.model, entry.error, entry.failedAt);
|
|
150
|
+
}
|
|
151
|
+
stats() {
|
|
152
|
+
const row = this.db.prepare('SELECT COUNT(*) as entries, COALESCE(SUM(hits), 0) as hits, COALESCE(SUM(prompt_tokens), 0) as promptTokens, COALESCE(SUM(completion_tokens), 0) as completionTokens FROM evidence_cache').get();
|
|
153
|
+
return row;
|
|
154
|
+
}
|
|
155
|
+
close() {
|
|
156
|
+
this.db.close();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
exports.EvidenceCache = EvidenceCache;
|
|
160
|
+
/**
|
|
161
|
+
* Drop every cached extraction that read one of `stems` (note paths): a permanent deletion takes the text these rows
|
|
162
|
+
* extracted from a deleted memory (memorymap/memoryMap.ts, purgeRetired). Nothing to do when the cache file or its table
|
|
163
|
+
* does not exist. The cache is keyed by the exact prompt, so a dropped row is only asked again when a later search reads
|
|
164
|
+
* the same files.
|
|
165
|
+
*/
|
|
166
|
+
function dropEvidenceForStems(cachePath, stems) {
|
|
167
|
+
if (stems.length === 0 || !node_fs_1.default.existsSync(cachePath))
|
|
168
|
+
return;
|
|
169
|
+
const db = new better_sqlite3_1.default(cachePath);
|
|
170
|
+
try {
|
|
171
|
+
db.pragma('busy_timeout = 10000');
|
|
172
|
+
if (!db.prepare(`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'evidence_cache'`).get())
|
|
173
|
+
return;
|
|
174
|
+
const drop = db.prepare(`DELETE FROM evidence_cache WHERE json_valid(sessions_json) AND EXISTS (
|
|
175
|
+
SELECT 1 FROM json_each(sessions_json) s WHERE json_extract(s.value, '$.stem') = ?)`);
|
|
176
|
+
db.transaction(() => { for (const stem of stems)
|
|
177
|
+
drop.run(stem); })();
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
db.close();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EVIDENCE_INTENT = void 0;
|
|
4
|
+
exports.resolveEvidenceMode = resolveEvidenceMode;
|
|
5
|
+
exports.hasEvidenceIntent = hasEvidenceIntent;
|
|
6
|
+
exports.evidenceGate = evidenceGate;
|
|
7
|
+
function resolveEvidenceMode(env = process.env) {
|
|
8
|
+
const raw = (env.RENOR_EVIDENCE_MODE ?? '').trim().toLowerCase();
|
|
9
|
+
if (raw === '')
|
|
10
|
+
return 'off';
|
|
11
|
+
if (raw === 'off' || raw === 'false' || raw === '0' || raw === 'no')
|
|
12
|
+
return 'off';
|
|
13
|
+
if (raw === 'always' || raw === 'on' || raw === '1' || raw === 'yes' || raw === 'all')
|
|
14
|
+
return 'always';
|
|
15
|
+
if (raw === 'auto')
|
|
16
|
+
return 'auto';
|
|
17
|
+
throw new Error(`Invalid RENOR_EVIDENCE_MODE "${raw}": must be off | auto | always`);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Gold-blind aggregation/enumeration intent. Copied from the eval runner's
|
|
21
|
+
* AGGREGATE_INTENT and widened with count/list cues; never fixture-specific.
|
|
22
|
+
*/
|
|
23
|
+
exports.EVIDENCE_INTENT = /\b(how many|how much|in total|total|altogether|combined|all of the|all the|count|number of|list|enumerate|every)\b/i;
|
|
24
|
+
function hasEvidenceIntent(query) {
|
|
25
|
+
return exports.EVIDENCE_INTENT.test(query);
|
|
26
|
+
}
|
|
27
|
+
function evidenceGate(mode, query, force = false) {
|
|
28
|
+
if (mode === 'off')
|
|
29
|
+
return { run: false, reason: 'mode-off' };
|
|
30
|
+
if (force)
|
|
31
|
+
return { run: true, reason: 'forced' };
|
|
32
|
+
if (mode === 'always')
|
|
33
|
+
return { run: true, reason: 'mode-always' };
|
|
34
|
+
if (hasEvidenceIntent(query))
|
|
35
|
+
return { run: true, reason: 'intent' };
|
|
36
|
+
return { run: false, reason: 'intent' };
|
|
37
|
+
}
|