新增: 待发箱/唤醒原语/预算随窗口 + 治理门禁与场景护栏

- 待发箱地基: V51 ai_outbox 表 + 手写 OutboxRepo(claim CAS/崩溃残留复位/瞬时失败保留) + outbox.rs 调度器(KV 默认关/Notify 唤醒/会话忙跳过不丢)
- Wake 异步唤醒原语: 三条件(delay/file-appear/process-exit)+纯判定+注入走正典 spawn_agentic_session,默认关
- 上下文预算随模型窗口: model_context.rs 三级优先(池显式>预设表>回退 128K);上界取 1M 而非 128K——max_tokens 仅裁剪预算,卡 128K 等于不治根因
- 文件租约补纯函数与单测(未另造双轨:LEASE 早随 cb260ab 落地) / 跨会话召回补纯函数单测(主体早已落地)
- 子代理: 完整卡已消化 + 子会话压缩层 + P3 批量
- 隧道/中继: 指令离线不丢 + Hello proto 版本 + 心跳与 runbook
- df-mcp: 写工具后落 KV 变更标记(供 GUI 轮询兜底,补独立 stdio 进程无感知)
- 迁移链顶点改单一真值常量 LATEST_SCHEMA_VERSION(修 2 处因 V51 失效的断言)
- 治理: i18n 特殊字符提交门禁(含反引号覆盖 + 10 自测) / 根目录卫生门禁 / 看板归档器 / 单一 lockfile(bun) / swap-install 落 temp/ 且兼容无 Get-FileHash
- 场景护栏: 流式正文可见性 e2e(playwright 免真机事件回放) + 列表续写/外置句柄 node 测试
This commit is contained in:
lxy
2026-10-10 08:09:17 +08:00
parent 9c68f98d89
commit b37279f3a2
32 changed files with 4175 additions and 152 deletions
+109
View File
@@ -0,0 +1,109 @@
// useListContinuation 纯逻辑层单测 —— 仓库无 vitest,走 node 内置测试跑(零第三方依赖):
// node --test tests/useListContinuation.test.mjs
// Node ≥22.6 原生剥离类型,可直接 import .ts(mjs 侧显式带 .ts 后缀)。
// 覆盖:识别(有序/无序/缩进/非列表)/ 围栏守卫 / 续写补号 / 空项退出 / 末位退格退出 / 边界。
import test from 'node:test'
import assert from 'node:assert/strict'
import {
analyzeListContext,
nextMarker,
continueList,
exitListOnBackspace,
} from '../src/composables/ai/useListContinuation.ts'
test('analyzeListContext: 有序标记识别(含缩进/字段)', () => {
const ctx = analyzeListContext(' 12. hello', 10)
assert.equal(ctx.kind, 'ordered')
assert.equal(ctx.indent, ' ')
assert.equal(ctx.marker, '12. ')
assert.equal(ctx.number, 12)
assert.equal(ctx.content, 'hello')
assert.equal(ctx.lineStart, 0)
assert.equal(ctx.markerEnd, 6)
})
test('analyzeListContext: 有序 `)` 标记', () => {
const ctx = analyzeListContext('3) x', 4)
assert.equal(ctx.kind, 'ordered')
assert.equal(ctx.marker, '3) ')
assert.equal(ctx.number, 3)
})
test('analyzeListContext: 无序三种符号', () => {
for (const b of ['-', '*', '+']) {
const ctx = analyzeListContext(`${b} item`, 3)
assert.equal(ctx.kind, 'unordered')
assert.equal(ctx.bullet, b)
assert.equal(ctx.content, 'item')
}
})
test('analyzeListContext: 非列表行返回 null', () => {
assert.equal(analyzeListContext('1.no-space', 2), null) // 标记后无空白
assert.equal(analyzeListContext('*bold*', 3), null) // 强调非列表
assert.equal(analyzeListContext('-', 1), null) // 裸符号
assert.equal(analyzeListContext('plain text', 5), null)
const deep = ' 1. deep' // 4 空格缩进 > 3
assert.equal(analyzeListContext(deep, deep.length), null)
})
test('analyzeListContext: 按光标所在行判定,不误判他行', () => {
const text = '1. list\nplain'
assert.equal(analyzeListContext(text, 9), null) // 光标在第二非列表行
assert.equal(analyzeListContext(text, 3)?.kind, 'ordered')
})
test('围栏守卫: ``` 内不触发(未闭合)', () => {
const text = '```\n- x'
assert.equal(analyzeListContext(text, text.length), null)
assert.equal(continueList(text, text.length), null)
})
test('围栏守卫: 闭合围栏外的列表照常触发', () => {
const text = '```\ncode\n```\n- x'
const ctx = analyzeListContext(text, text.length)
assert.equal(ctx?.kind, 'unordered')
})
test('nextMarker: 有序 +1(保留分隔符)/ 无序同型', () => {
assert.equal(nextMarker(analyzeListContext('1. a', 4)), '2. ')
assert.equal(nextMarker(analyzeListContext('9) a', 4)), '10) ')
assert.equal(nextMarker(analyzeListContext('- a', 3)), '- ')
})
test('continueList: 非空有序项 → 换行补下一序号', () => {
assert.deepEqual(continueList('1. foo', 6), { text: '1. foo\n2. ', caret: 10 })
})
test('continueList: 保留缩进', () => {
assert.deepEqual(continueList(' 1. a', 6), { text: ' 1. a\n 2. ', caret: 12 })
})
test('continueList: 非空无序项 → 换行补同型符号', () => {
assert.deepEqual(continueList('- foo', 5), { text: '- foo\n- ', caret: 8 })
})
test('continueList: 空项 → 退出列表(删标记留空行)', () => {
assert.deepEqual(continueList('1. ', 3), { text: '', caret: 0 })
assert.deepEqual(continueList('a\n2. ', 5), { text: 'a\n', caret: 2 })
})
test('continueList: 非列表语境返回 null(交默认换行)', () => {
assert.equal(continueList('plain', 5), null)
})
test('exitListOnBackspace: 末位退格退出空项', () => {
assert.deepEqual(exitListOnBackspace('1. ', 3), { text: '', caret: 0 })
assert.deepEqual(exitListOnBackspace('a\n- ', 4), { text: 'a\n', caret: 2 })
})
test('exitListOnBackspace: 非空项/非行末不触发', () => {
assert.equal(exitListOnBackspace('1. x', 4), null) // 有正文
assert.equal(exitListOnBackspace('1. ', 1), null) // 光标不在行末
assert.equal(exitListOnBackspace('plain', 5), null)
})
test('退出后文本不再被识别为列表(标记已移除)', () => {
const r = continueList('1. ', 3)
assert.equal(analyzeListContext(r.text, r.caret), null)
})