|
|
|
|
@ -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 }; |
|
|
|
|
|