feat: W7 功能標籤分類、對白檢索與注入 (#8)

用 $.model.complete(Haiku)判斷回應的功能標籤,從角色索引取同標籤對白,
以只增不改的訊息附在使用者訊息之前;失敗一律退回不注入。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jeffery (jiantw83)
2026-10-14 17:44:09 +00:00
co-authored by Claude Sonnet 5.5
parent 1c4e0c5d98
commit d9e50d196b
12 changed files with 385 additions and 4 deletions
@@ -0,0 +1,54 @@
import { describe, expect, test } from 'claude-code/testing'
import type { Complete } from './classify'
import { resolveConfig } from './config'
import { buildInjection } from './inject'
import type { IndexEntry } from './retrieval'
const entry = (id: string, fn: IndexEntry['function']): IndexEntry => ({ id, function: fn, emotion: 'neutral', mode: null, text: `句${id}` })
const indexes = {
'sao-asuna': [entry('a', 'comfort'), entry('b', 'comfort')],
'sao-suguha': [] as IndexEntry[],
}
const answer = (text: string): Complete => async () => ({ isAnswered: true, text })
const deps = (opts: Record<string, string>, complete: Complete, calls = { n: 0 }) => ({
config: resolveConfig(opts),
indexes,
complete: async (r: Parameters<Complete>[0]) => (calls.n++, complete(r)),
now: () => 0,
turn: 0,
})
describe('buildInjection', () => {
const on = { defaultCharacter: 'sao-asuna', intensity: '2' }
test('分類成功時回傳同標籤的對白', async () => {
const r = await buildInjection(deps(on, answer('comfort')), '測試失敗了')
expect(r?.fn).toBe('comfort')
expect(r?.text).toContain('句a')
})
test('沒選角色或強度 0 不呼叫模型', async () => {
const calls = { n: 0 }
expect(await buildInjection(deps({ intensity: '2' }, answer('comfort'), calls), 'x')).toBeNull()
expect(await buildInjection(deps({ ...on, intensity: '0' }, answer('comfort'), calls), 'x')).toBeNull()
expect(calls.n).toBe(0)
})
test('索引為空不呼叫模型', async () => {
const calls = { n: 0 }
expect(await buildInjection(deps({ defaultCharacter: 'sao-suguha', intensity: '2' }, answer('comfort'), calls), 'x')).toBeNull()
expect(calls.n).toBe(0)
})
test('分類失敗退回不注入,但仍回報耗時', async () => {
const r = await buildInjection(deps(on, async () => ({ isAnswered: false })), 'x')
expect(r).toEqual({ text: null, fn: null, ms: 0 })
})
test('該標籤沒有對白時不注入', async () => {
const r = await buildInjection(deps(on, answer('greet')), 'x')
expect(r?.fn).toBe('greet')
expect(r?.text).toBeNull()
})
})