| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- import { Agent } from './api/models';
- import { formatRelayQuestion, formatRelayReply, parseRelayIntent } from './relay-intent';
- /**
- * 容器中转 —— 识别「让 A 去问 B」的指令。
- *
- * 实测依据(2026-10-29):
- * · 容器有能力主动发 Matrix 消息(lj 实发成功并回读验证)
- * · 但 lj-test 发送失败(PUT 返回空体),且两端互相看不见对方
- * · 所以让容器互发不可靠 —— 由 App 搬运更稳
- */
- const agent = (id: string, name: string): Agent => ({
- id,
- name,
- status: 'idle' as Agent['status'],
- matrixReady: true,
- unread: 0,
- });
- const A = agent('a', 'lj');
- const B = agent('b', 'lj-test');
- const ALL = [A, B];
- describe('parseRelayIntent · 动词式', () => {
- it('「问 X:问题」', () => {
- const r = parseRelayIntent('问 lj-test:数据库用的什么版本', ALL)!;
- expect(r.target.id).toBe(B.id);
- expect(r.question).toBe('数据库用的什么版本');
- });
- it('「让 X 做某事」', () => {
- const r = parseRelayIntent('让 lj-test 检查一下接口', ALL)!;
- expect(r.target.id).toBe(B.id);
- expect(r.question).toBe('检查一下接口');
- });
- it('「问问 X」/「问一下 X」', () => {
- expect(parseRelayIntent('问问 lj-test:进度如何', ALL)!.question).toBe('进度如何');
- expect(parseRelayIntent('问一下 lj-test,进度如何', ALL)!.question).toBe('进度如何');
- });
- it('全角冒号与逗号也能分隔', () => {
- expect(parseRelayIntent('问 lj-test:进度如何', ALL)!.question).toBe('进度如何');
- expect(parseRelayIntent('问 lj-test,进度如何', ALL)!.question).toBe('进度如何');
- });
- });
- describe('parseRelayIntent · @ 式', () => {
- it('「@X 问题」', () => {
- const r = parseRelayIntent('@lj-test 这个问题你怎么看', ALL)!;
- expect(r.target.id).toBe(B.id);
- expect(r.question).toBe('这个问题你怎么看');
- });
- });
- describe('parseRelayIntent · 🔴 前缀陷阱', () => {
- /*
- * `lj` 是 `lj-test` 的前缀。若按短名优先匹配,
- * 「问 lj-test:xxx」会被误判成问 lj —— 实测容器间也有同样问题。
- */
- it('「问 lj-test」不会被误判成问 lj', () => {
- const r = parseRelayIntent('问 lj-test:接口怎么样了', ALL)!;
- expect(r.target.id).toBe(B.id);
- expect(r.target.name).toBe('lj-test');
- });
- it('「问 lj」正确命中短名那个', () => {
- const r = parseRelayIntent('问 lj:今天状态如何', ALL)!;
- expect(r.target.id).toBe(A.id);
- });
- it('短名出现在问题正文里不会误触发', () => {
- // 「问 lj-test」命中 lj-test;问题里的 lj 只是正文
- const r = parseRelayIntent('问 lj-test:lj 那边的接口好了吗', ALL)!;
- expect(r.target.id).toBe(B.id);
- expect(r.question).toBe('lj 那边的接口好了吗');
- });
- });
- describe('parseRelayIntent · 不该命中的情况', () => {
- it('普通对话不触发', () => {
- expect(parseRelayIntent('帮我做个登录页', ALL)).toBeNull();
- expect(parseRelayIntent('这个接口怎么写', ALL)).toBeNull();
- });
- // 只有名字没有问题时,转过去也没意义
- it('只有名字没问题时不触发', () => {
- expect(parseRelayIntent('问 lj-test', ALL)).toBeNull();
- expect(parseRelayIntent('问 lj-test:', ALL)).toBeNull();
- expect(parseRelayIntent('@lj-test', ALL)).toBeNull();
- });
- it('空文本 / 无候选容器时不触发', () => {
- expect(parseRelayIntent('', ALL)).toBeNull();
- expect(parseRelayIntent('问 lj-test:xxx', [])).toBeNull();
- });
- it('名字以正则元字符结尾也能正确匹配', () => {
- const dot = agent('c', 'bot.v2');
- const r = parseRelayIntent('问 bot.v2:你好', [dot])!;
- expect(r.target.id).toBe('c');
- expect(r.question).toBe('你好');
- });
- it('多行问题完整保留', () => {
- const r = parseRelayIntent('问 lj-test:第一行\n第二行', ALL)!;
- expect(r.question).toBe('第一行\n第二行');
- });
- });
- describe('formatRelayQuestion', () => {
- it('标明是代问,并说明答复会转回', () => {
- const s = formatRelayQuestion(A, '用户9836', '数据库什么版本');
- expect(s).toContain('lj');
- expect(s).toContain('用户9836');
- expect(s).toContain('数据库什么版本');
- expect(s).toContain('转回');
- });
- });
- describe('formatRelayReply', () => {
- it('标明来源与原问题', () => {
- const s = formatRelayReply(B, '数据库什么版本', 'MySQL 8.0');
- expect(s).toContain('lj-test');
- expect(s).toContain('数据库什么版本');
- expect(s).toContain('MySQL 8.0');
- });
- it('超长问题截断', () => {
- const s = formatRelayReply(B, '一'.repeat(200), '答');
- expect(s).toContain('…');
- });
- it('对方没给内容时如实说明', () => {
- expect(formatRelayReply(B, 'q', ' ')).toContain('没有给出内容');
- });
- });
|