Browse Source

Фаза 4: recallCount fix - computeInjection (чистая) + applyInjection (мутация), дедуп /memory/inject по fingerprint

master
Serge Shutov 3 days ago
parent
commit
1b0b78ae08
  1. 39
      bridge/memory.js
  2. 8
      bridge/server.js

39
bridge/memory.js

@ -297,7 +297,9 @@ function query(state, args) { @@ -297,7 +297,9 @@ function query(state, args) {
throw new Error('unknown op: ' + op);
}
function getInjection(state, chatId, workspaceKey) {
// computeInjection — pure function. Does NOT mutate state, does NOT touch recallCount.
// Returns the block to inject plus the entry ids so applyInjection can account for them.
function computeInjection(state, chatId, workspaceKey) {
const entries = [];
for (const e of state.entries) {
if (e.key && e.key.indexOf('directive_') === 0 && e.scope === 'workspace' && e.state === 'active') {
@ -310,18 +312,35 @@ function getInjection(state, chatId, workspaceKey) { @@ -310,18 +312,35 @@ function getInjection(state, chatId, workspaceKey) {
if (e.state === 'done') continue;
if (e.track === 'agent' && e.scope === 'user-global') { entries.push(e); continue; }
}
// Recall accounting: every entry that goes into the injected block counts.
const nowTs = nowIso();
for (const e of entries) {
e.lastRecalled = nowTs;
e.recallCount = (e.recallCount || 0) + 1;
}
const texts = entries.map(function (e) { return String(e.text); });
const fpBase = entries.map(function (e) { return e.id + ':' + (e.version || 1); }).sort().join('|');
const fp = crypto.createHash('sha256').update(fpBase).digest('hex').slice(0, 16);
const block = texts.length ? '[DSHMEM v2]\n' + texts.join('\n\n') : '';
if (chatId && state.chats && state.chats[chatId]) state.chats[chatId].lastInjectAt = nowIso();
return { block: block, fingerprint: fp, count: entries.length, bytes: byteLen(block) };
const entryIds = entries.map(function (e) { return e.id; });
return { block: block, fingerprint: fp, count: entries.length, bytes: byteLen(block), entryIds: entryIds };
}
// applyInjection — explicit mutation. Call ONLY when the block is actually delivered
// to the model (fingerprint differs from what this chat already received).
function applyInjection(state, inj, chatId) {
if (!inj || !Array.isArray(inj.entryIds)) return { applied: 0 };
const nowTs = nowIso();
const ids = new Set(inj.entryIds);
let applied = 0;
for (const e of state.entries) {
if (ids.has(e.id)) {
e.lastRecalled = nowTs;
e.recallCount = (e.recallCount || 0) + 1;
applied++;
}
}
if (chatId) {
if (!state.chats) state.chats = {};
if (!state.chats[chatId]) state.chats[chatId] = {};
state.chats[chatId].lastInjectAt = nowTs;
state.chats[chatId].lastInjFp = inj.fingerprint;
}
return { applied: applied };
}
module.exports = { loadState, saveState, query, getInjection, emptyState, stateFile, templateFile, newId, byteLen, ALWAYS_LIMIT, TAGS_LIMIT, DIRECTIVES_PER_CHAT };
module.exports = { loadState, saveState, query, computeInjection, applyInjection, emptyState, stateFile, templateFile, newId, byteLen, ALWAYS_LIMIT, TAGS_LIMIT, DIRECTIVES_PER_CHAT };

8
bridge/server.js

@ -245,7 +245,13 @@ async function handle(req, res, pathname, body) { @@ -245,7 +245,13 @@ async function handle(req, res, pathname, body) {
const st = memoryModule.loadState(memDir);
const chatId = body && body.chatId ? String(body.chatId) : '';
const wsKey = body && body.workspaceKey ? String(body.workspaceKey) : String(config.allowedRoots[0] || '');
const inj = memoryModule.getInjection(st, chatId, wsKey);
const inj = memoryModule.computeInjection(st, chatId, wsKey);
const chat = (chatId && st.chats) ? st.chats[chatId] : null;
const lastFp = chat ? String(chat.lastInjFp || '') : '';
if (lastFp && lastFp === inj.fingerprint) {
return { ok: true, data: { block: '', fingerprint: inj.fingerprint, count: 0, bytes: 0, deduped: true } };
}
memoryModule.applyInjection(st, inj, chatId);
memoryModule.saveState(memDir, st);
return { ok: true, data: inj };
}

Loading…
Cancel
Save