257 lines
7.4 KiB
JavaScript
257 lines
7.4 KiB
JavaScript
import Database from 'better-sqlite3'
|
||
|
||
const db = new Database('D:/claude/jarvis/data/jarvis.db')
|
||
|
||
const USER_ID = 'ID:000001'
|
||
const AGENT_ID = 'agent:jarvis'
|
||
const USER_ROOT_MEM_ID = 'person_000001'
|
||
const AGENT_ROOT_MEM_ID = 'agent_jarvis_identity'
|
||
|
||
const userRootAliases = new Set([
|
||
'contact_000001',
|
||
'person_000001',
|
||
'person_id000001_interaction',
|
||
'person_yuanda_identity',
|
||
'user_000001',
|
||
'user_000001_identity',
|
||
'user_000001_profile',
|
||
])
|
||
|
||
function parseJsonArray(value) {
|
||
if (Array.isArray(value)) return value
|
||
if (!value) return []
|
||
try {
|
||
const parsed = JSON.parse(value)
|
||
return Array.isArray(parsed) ? parsed : []
|
||
} catch {
|
||
return []
|
||
}
|
||
}
|
||
|
||
function uniq(values) {
|
||
return [...new Set((values || []).filter(Boolean).map(v => String(v).trim()).filter(Boolean))]
|
||
}
|
||
|
||
function normalizePartyId(id) {
|
||
if (!id) return id
|
||
const text = String(id).trim()
|
||
if (/^ID:\d+$/i.test(text)) return `ID:${text.replace(/^ID:/i, '')}`
|
||
if (/^\d+$/.test(text)) return `ID:${text}`
|
||
return text
|
||
}
|
||
|
||
function ensureRoot(memId, eventType, title, content, entity, tags) {
|
||
const existing = db.prepare(`
|
||
SELECT id, entities, tags, title, content
|
||
FROM memories
|
||
WHERE mem_id = ?
|
||
LIMIT 1
|
||
`).get(memId)
|
||
|
||
if (existing) {
|
||
db.prepare(`
|
||
UPDATE memories
|
||
SET event_type = ?, title = ?, content = ?, detail = ?, entities = ?, tags = ?, timestamp = ?
|
||
WHERE id = ?
|
||
`).run(
|
||
eventType,
|
||
existing.title || title,
|
||
existing.content || content,
|
||
existing.content || content,
|
||
JSON.stringify(uniq([...parseJsonArray(existing.entities), entity])),
|
||
JSON.stringify(uniq([...parseJsonArray(existing.tags), ...tags])),
|
||
new Date().toISOString(),
|
||
existing.id
|
||
)
|
||
return Number(existing.id)
|
||
}
|
||
|
||
const result = db.prepare(`
|
||
INSERT INTO memories (event_type, content, detail, title, mem_id, entities, concepts, tags, links, source_ref, timestamp, parent_id)
|
||
VALUES (?, ?, ?, ?, ?, ?, '[]', ?, '[]', 'identity_migration', ?, NULL)
|
||
`).run(
|
||
eventType,
|
||
content,
|
||
content,
|
||
title,
|
||
memId,
|
||
JSON.stringify([entity]),
|
||
JSON.stringify(tags),
|
||
new Date().toISOString()
|
||
)
|
||
|
||
return Number(result.lastInsertRowid)
|
||
}
|
||
|
||
function inferUser(memory) {
|
||
const text = [memory.mem_id, memory.title, memory.content, memory.detail].filter(Boolean).join(' ')
|
||
const memId = String(memory.mem_id || '').toLowerCase()
|
||
const title = String(memory.title || '')
|
||
return (
|
||
/(?:^|[^a-z0-9])(000001|yuanda)(?:[^a-z0-9]|$)|ID:\s*000001/i.test(text) ||
|
||
/^user_|^person_/.test(memId) ||
|
||
/用户/.test(title)
|
||
)
|
||
}
|
||
|
||
function inferAgent(memory) {
|
||
const text = [memory.mem_id, memory.title, memory.content, memory.detail].filter(Boolean).join(' ')
|
||
const memId = String(memory.mem_id || '').toLowerCase()
|
||
return /Jarvis|Agent_Jarvis|JARVIS/i.test(text) || /jarvis|^agent_/.test(memId)
|
||
}
|
||
|
||
function chooseParent(memory, hasUser, hasAgent) {
|
||
if (memory.mem_id === USER_ROOT_MEM_ID || memory.mem_id === AGENT_ROOT_MEM_ID) return null
|
||
|
||
const text = [memory.mem_id, memory.title, memory.content].filter(Boolean).join(' ')
|
||
|
||
if (hasUser && !hasAgent) return USER_ROOT_MEM_ID
|
||
if (hasAgent && !hasUser) return AGENT_ROOT_MEM_ID
|
||
if (hasUser && hasAgent) {
|
||
if (/用户|ID:\s*000001|\b000001\b|\bYuanda\b/i.test(text)) return USER_ROOT_MEM_ID
|
||
return AGENT_ROOT_MEM_ID
|
||
}
|
||
return null
|
||
}
|
||
|
||
function mergeLinks(rawLinks, additions) {
|
||
const merged = [...parseJsonArray(rawLinks)]
|
||
const seen = new Set(merged.map(link => `${link.target_id}:${link.relation}`))
|
||
for (const link of additions) {
|
||
const key = `${link.target_id}:${link.relation}`
|
||
if (!seen.has(key)) {
|
||
seen.add(key)
|
||
merged.push(link)
|
||
}
|
||
}
|
||
return merged
|
||
}
|
||
|
||
function updateMemory(memory, userRootId, agentRootId) {
|
||
const hasUser = inferUser(memory)
|
||
const hasAgent = inferAgent(memory)
|
||
|
||
if (!hasUser && !hasAgent) return false
|
||
|
||
const entities = uniq([
|
||
...parseJsonArray(memory.entities),
|
||
...(hasUser ? [USER_ID] : []),
|
||
...(hasAgent ? [AGENT_ID] : []),
|
||
])
|
||
|
||
const parentMemId = chooseParent(memory, hasUser, hasAgent)
|
||
let parentId = memory.parent_id
|
||
const linkAdds = []
|
||
|
||
if (parentMemId === USER_ROOT_MEM_ID && memory.id !== userRootId) {
|
||
parentId = userRootId
|
||
linkAdds.push({ target_id: USER_ROOT_MEM_ID, relation: 'child_of' })
|
||
if (hasAgent) linkAdds.push({ target_id: AGENT_ROOT_MEM_ID, relation: 'related_to' })
|
||
} else if (parentMemId === AGENT_ROOT_MEM_ID && memory.id !== agentRootId) {
|
||
parentId = agentRootId
|
||
linkAdds.push({ target_id: AGENT_ROOT_MEM_ID, relation: 'child_of' })
|
||
if (hasUser) linkAdds.push({ target_id: USER_ROOT_MEM_ID, relation: 'related_to' })
|
||
} else {
|
||
if (hasUser && memory.mem_id !== USER_ROOT_MEM_ID) linkAdds.push({ target_id: USER_ROOT_MEM_ID, relation: 'related_to' })
|
||
if (hasAgent && memory.mem_id !== AGENT_ROOT_MEM_ID) linkAdds.push({ target_id: AGENT_ROOT_MEM_ID, relation: 'related_to' })
|
||
}
|
||
|
||
const links = mergeLinks(memory.links, linkAdds).map(link => ({
|
||
...link,
|
||
target_id: userRootAliases.has(link.target_id) ? USER_ROOT_MEM_ID : link.target_id,
|
||
}))
|
||
const dedupedLinks = []
|
||
const seenLinks = new Set()
|
||
for (const link of links) {
|
||
const key = `${link.target_id}:${link.relation}`
|
||
if (!seenLinks.has(key)) {
|
||
seenLinks.add(key)
|
||
dedupedLinks.push(link)
|
||
}
|
||
}
|
||
|
||
db.prepare(`
|
||
UPDATE memories
|
||
SET entities = ?, parent_id = ?, links = ?, timestamp = ?
|
||
WHERE id = ?
|
||
`).run(
|
||
JSON.stringify(entities),
|
||
parentId || null,
|
||
JSON.stringify(dedupedLinks),
|
||
memory.timestamp,
|
||
memory.id
|
||
)
|
||
|
||
return true
|
||
}
|
||
|
||
function main() {
|
||
const now = new Date().toISOString()
|
||
|
||
db.prepare(`UPDATE conversations SET from_id = ? WHERE from_id = '000001'`).run(USER_ID)
|
||
db.prepare(`UPDATE conversations SET to_id = ? WHERE to_id = '000001'`).run(USER_ID)
|
||
|
||
db.prepare(`UPDATE entities SET id = ? WHERE id = '000001'`).run(USER_ID)
|
||
|
||
const userRootId = ensureRoot(
|
||
USER_ROOT_MEM_ID,
|
||
'person',
|
||
'用户 ID:000001 身份标识',
|
||
'用户唯一身份为 ID:000001,别名 Yuanda。',
|
||
USER_ID,
|
||
['identity', 'user', 'alias:Yuanda']
|
||
)
|
||
|
||
const agentRootId = ensureRoot(
|
||
AGENT_ROOT_MEM_ID,
|
||
'object',
|
||
'Agent Jarvis 身份标识',
|
||
'Agent Jarvis 是当前运行中的本地 AI 助手实例。',
|
||
AGENT_ID,
|
||
['identity', 'agent', 'jarvis']
|
||
)
|
||
|
||
const rows = db.prepare(`
|
||
SELECT id, mem_id, event_type, title, content, detail, entities, tags, parent_id, links, timestamp
|
||
FROM memories
|
||
ORDER BY id ASC
|
||
`).all()
|
||
|
||
let updated = 0
|
||
for (const row of rows) {
|
||
if (updateMemory(row, userRootId, agentRootId)) updated++
|
||
}
|
||
|
||
const normalizeUserRootLinks = db.prepare(`
|
||
UPDATE memories
|
||
SET links = REPLACE(links, ?, ?)
|
||
WHERE links LIKE ?
|
||
`)
|
||
|
||
for (const alias of userRootAliases) {
|
||
if (alias === USER_ROOT_MEM_ID) continue
|
||
normalizeUserRootLinks.run(alias, USER_ROOT_MEM_ID, `%${alias}%`)
|
||
}
|
||
|
||
db.prepare(`
|
||
UPDATE memories
|
||
SET entities = ?, tags = ?, timestamp = ?
|
||
WHERE id = ?
|
||
`).run(JSON.stringify([USER_ID]), JSON.stringify(['identity', 'user', 'alias:Yuanda']), now, userRootId)
|
||
|
||
db.prepare(`
|
||
UPDATE memories
|
||
SET entities = ?, tags = ?, timestamp = ?
|
||
WHERE id = ?
|
||
`).run(JSON.stringify([AGENT_ID]), JSON.stringify(['identity', 'agent', 'jarvis']), now, agentRootId)
|
||
|
||
console.log(JSON.stringify({
|
||
ok: true,
|
||
updated_memories: updated,
|
||
user_root_id: userRootId,
|
||
agent_root_id: agentRootId,
|
||
}, null, 2))
|
||
}
|
||
|
||
main()
|