2026-06-15 23:32:29 +08:00
|
|
|
import assert from 'node:assert/strict';
|
|
|
|
|
import { test } from 'vitest';
|
|
|
|
|
|
|
|
|
|
import { createAgentTitleMarkerStripper } from '../src/title-marker.js';
|
|
|
|
|
|
|
|
|
|
function createStripper() {
|
|
|
|
|
const titles: string[] = [];
|
|
|
|
|
const stripper = createAgentTitleMarkerStripper({
|
|
|
|
|
enabled: true,
|
|
|
|
|
emitTitle: (title) => titles.push(title),
|
|
|
|
|
});
|
|
|
|
|
return { stripper, titles };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test('title marker stripper parses prefix marker and answer from one delta', () => {
|
|
|
|
|
const { stripper, titles } = createStripper();
|
|
|
|
|
|
|
|
|
|
const visible = stripper.strip('\n<od-title>Foo</od-title>\nAnswer');
|
|
|
|
|
|
|
|
|
|
assert.equal(visible, '\n\nAnswer');
|
|
|
|
|
assert.deepEqual(titles, ['Foo']);
|
|
|
|
|
assert.equal(stripper.flush(), '');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('title marker stripper parses markers split across deltas', () => {
|
|
|
|
|
const { stripper, titles } = createStripper();
|
|
|
|
|
|
|
|
|
|
assert.equal(stripper.strip('Before <od-'), 'Before ');
|
|
|
|
|
assert.equal(stripper.strip('title>Split Title</od-title> After'), ' After');
|
|
|
|
|
|
|
|
|
|
assert.deepEqual(titles, ['Split Title']);
|
|
|
|
|
assert.equal(stripper.flush(), '');
|
|
|
|
|
});
|
|
|
|
|
|
2026-09-07 17:08:24 +00:00
|
|
|
test('title marker stripper drops malformed marker content without throwing', () => {
|
|
|
|
|
const { stripper, titles } = createStripper();
|
|
|
|
|
|
|
|
|
|
assert.equal(stripper.strip('Lead <od-title>unfinished'), 'Lead ');
|
|
|
|
|
assert.equal(stripper.flush(), '');
|
|
|
|
|
assert.deepEqual(titles, []);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 红测:**没请求生成标题时,标记也不许漏进正文**。
|
|
|
|
|
*
|
|
|
|
|
* 线上量到的:`<od-title>编辑级羊皮纸单页</od-title>` 原样出现在聊天正文第一行。
|
|
|
|
|
* 真因是调用点写的 `enabled: Boolean(titleGenerationRequested)` —— 不请求标题就把
|
|
|
|
|
* 剥离器整个关掉;而模型是按系统提示词吐这个标记的,它并不知道这一轮我们要不要标题。
|
|
|
|
|
*
|
|
|
|
|
* 「要不要拿它命名会话」和「它能不能出现在正文里」是两件事:后者永远是否。
|
|
|
|
|
*/
|
|
|
|
|
test('title marker never leaks into visible text, even when no title was requested', () => {
|
2026-06-15 23:32:29 +08:00
|
|
|
const titles: string[] = [];
|
|
|
|
|
const stripper = createAgentTitleMarkerStripper({
|
|
|
|
|
enabled: false,
|
|
|
|
|
emitTitle: (title) => titles.push(title),
|
|
|
|
|
});
|
|
|
|
|
|
2026-09-07 17:08:24 +00:00
|
|
|
assert.equal(stripper.strip('<od-title>Foo</od-title>Answer'), 'Answer');
|
2026-06-15 23:32:29 +08:00
|
|
|
assert.equal(stripper.flush(), '');
|
2026-09-07 17:08:24 +00:00
|
|
|
// 没请求标题就不该往上报,但正文必须干净
|
2026-06-15 23:32:29 +08:00
|
|
|
assert.deepEqual(titles, []);
|
|
|
|
|
});
|
2026-08-24 08:53:36 +00:00
|
|
|
|
|
|
|
|
// A Run that never asked for a title still has to consume the marker: the
|
|
|
|
|
// directive lives in the agent's own session history, so a resumed CLI can
|
|
|
|
|
// repeat it on a later turn. server.ts therefore keeps `enabled` on for every
|
|
|
|
|
// Run and only swaps the announce callback.
|
|
|
|
|
test('title marker stripper consumes the marker without announcing a title', () => {
|
|
|
|
|
const titles: string[] = [];
|
|
|
|
|
const stripper = createAgentTitleMarkerStripper({
|
|
|
|
|
enabled: true,
|
|
|
|
|
emitTitle: () => {},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
assert.equal(
|
|
|
|
|
stripper.strip('Lead in\n<od-title>Leaked Title</od-title>\nReal answer'),
|
|
|
|
|
'Lead in\n\nReal answer',
|
|
|
|
|
);
|
|
|
|
|
assert.equal(stripper.flush(), '');
|
|
|
|
|
assert.deepEqual(titles, []);
|
|
|
|
|
});
|