feat: companion 記憶權重、反覆思量與檢索

- 權重 W = I_eff × F × A,暫時/可升級/長期記憶的衰減與封存,任何記憶不刪除
- 未解負向事件反覆思量、開放迴路逾時、危險內容不固化
- 檢索改為關鍵字 + RRF × W + 心情一致加權,保留外部檢索器介面
- 版本 0.0.4

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 09:01:55 +00:00
co-authored by Claude Sonnet 5.5
parent 46f58a3ff8
commit 6f7b4d91ea
7 changed files with 599 additions and 80 deletions
@@ -362,6 +362,33 @@ describe('記憶、待辦與關心', () => {
expect(await c.prompt()).toContain('payment')
})
test('被罵的記憶是未解負向事件,道歉後解除;記憶不會被刪除', async () => {
const { c, fs } = setup()
await c.ensureActivated()
await c.onPrompt('你很煩,真的很煩人')
const path = `${ROOT}/companions/miko/memory.json`
const read = () => JSON.parse(fs.files.get(path)!) as { items: { negative?: boolean; resolvedAt?: number; status: string }[] }
expect(read().items[0]!.negative).toBe(true)
expect(read().items[0]!.resolvedAt).toBeUndefined()
await c.onPrompt('對不起,剛剛我太衝動了')
await c.onTurnComplete(false, '沒關係啦。<評估:apology>')
expect(read().items[0]!.resolvedAt !== undefined).toBe(true)
expect(read().items[0]!.status).toBe('active')
})
test('回覆中的提問成為開放迴路,2 回合沒回應就逾時', async () => {
const { c, fs } = setup()
await c.ensureActivated()
await c.onTurnComplete(false, '好喔。你今天吃飯了嗎?')
const path = `${ROOT}/companions/miko/memory.json`
const loops = () => (JSON.parse(fs.files.get(path)!) as { loops: { status: string }[] }).loops
expect(loops()[0]!.status).toBe('open')
await c.onPrompt('幫我看看建置')
expect(loops()[0]!.status).toBe('open')
await c.onPrompt('順便跑一下測試')
expect(loops()[0]!.status).toBe('timeout')
})
test('太短的提示不寫入記憶', async () => {
const { c, fs } = setup()
await c.ensureActivated()