feat: companion 核心的人設注入、語氣、心情標記與互動記憶
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import { redactSecrets } from './secrets.ts'
|
||||
import type { Fs } from './types.ts'
|
||||
|
||||
export type Turn = { role: 'user' | 'assistant'; text: string; at: number }
|
||||
export type InteractionMemory = {
|
||||
recent: Turn[]
|
||||
/** 壓縮後的摘要行與累計關係變化 */
|
||||
summary: string[]
|
||||
relationDelta: number
|
||||
}
|
||||
|
||||
export const RECENT_MAX = 30
|
||||
export const COMPRESS_COUNT = 20
|
||||
const SUMMARY_MAX = 40
|
||||
const LINE_MAX = 60
|
||||
|
||||
export const empty = (): InteractionMemory => ({ recent: [], summary: [], relationDelta: 0 })
|
||||
|
||||
// 互動記憶不存大段程式碼;只留對話文字
|
||||
export function dialogueOnly(text: string): string {
|
||||
return text
|
||||
.replace(/```[\s\S]*?(?:```|$)/g, ' ')
|
||||
.replace(/`[^`\n]{40,}`/g, ' ')
|
||||
.replace(/[ \t]+/g, ' ')
|
||||
.trim()
|
||||
}
|
||||
|
||||
export function clean(text: string): string {
|
||||
return redactSecrets(dialogueOnly(text))
|
||||
}
|
||||
|
||||
export function add(m: InteractionMemory, turn: Turn, relationDelta: number): InteractionMemory {
|
||||
const text = clean(turn.text)
|
||||
if (!text) return { ...m, relationDelta: m.relationDelta + relationDelta }
|
||||
let next: InteractionMemory = {
|
||||
...m,
|
||||
recent: [...m.recent, { ...turn, text }],
|
||||
relationDelta: m.relationDelta + relationDelta,
|
||||
}
|
||||
if (next.recent.length > RECENT_MAX) next = compress(next)
|
||||
return next
|
||||
}
|
||||
|
||||
/** 把最舊的逐字紀錄壓縮成摘要行 */
|
||||
export function compress(m: InteractionMemory): InteractionMemory {
|
||||
const old = m.recent.slice(0, COMPRESS_COUNT)
|
||||
const lines = old
|
||||
.filter(t => t.role === 'user')
|
||||
.map(t => (t.text.length > LINE_MAX ? t.text.slice(0, LINE_MAX) + '…' : t.text))
|
||||
return { ...m, recent: m.recent.slice(COMPRESS_COUNT), summary: [...m.summary, ...lines].slice(-SUMMARY_MAX) }
|
||||
}
|
||||
|
||||
export async function load(fs: Fs, path: string): Promise<InteractionMemory> {
|
||||
const raw = await fs.read(path)
|
||||
if (!raw) return empty()
|
||||
try {
|
||||
return { ...empty(), ...JSON.parse(raw) }
|
||||
} catch {
|
||||
return empty()
|
||||
}
|
||||
}
|
||||
|
||||
export const save = (fs: Fs, path: string, m: InteractionMemory) => fs.write(path, JSON.stringify(m))
|
||||
Reference in New Issue
Block a user