Files
memind/direct-chat-service.test.mjs
T
john 08feae8bef feat(h5-session): Session Broker、run SSE replay 与 Finish 竞态修复
落地 H5 Session 架构 Patch 1–5(Broker 收口、Router decision、SSE taxonomy、goosed 边界检查),
并新增可选 MEMIND_RUN_STREAM_REPLAY run 事件回放与 H5 假交付 guard;修复 Finish 先于 agent-run
gate 导致 UI 永久 loading 的竞态,接入 verify:h5-session-patches 回归脚本。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 14:19:48 +08:00

471 lines
13 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import {
createDirectChatService,
isDirectChatSessionId,
isPortalDirectChatSnapshot,
sendDirectChatSessionEvents,
shouldExpirePortalDirectChatSnapshot,
} from './direct-chat-service.mjs';
test('direct chat is enabled by default', () => {
const service = createDirectChatService({
userAuth: {},
llmProviderService: {},
sessionSnapshotService: {},
});
assert.equal(service.getStatus().enabled, true);
assert.equal(service.canHandle({
userMessage: { role: 'user', content: [{ type: 'text', text: 'hi' }] },
routingDecision: 'direct_chat',
}), true);
});
test('direct chat registers session through sessionAccess when provided', async () => {
const registered = [];
const service = createDirectChatService({
enabled: true,
userAuth: {},
sessionAccess: {
enabled: true,
async registerAgentSession(userId, sessionId, target) {
registered.push({ userId, sessionId, target });
},
},
llmProviderService: {
async createChatCompletion() {
return {
ok: true,
providerId: 'custom_deepseek',
model: 'deepseek-chat',
reply: 'ok',
usage: { inputTokens: 1, outputTokens: 1 },
};
},
},
sessionSnapshotService: {
async get() {
return null;
},
async save() {},
},
});
const result = await service.run({
userId: 'user-1',
requestId: 'req-access',
userMessage: {
role: 'user',
content: [{ type: 'text', text: 'hi' }],
metadata: { userVisible: true },
},
});
assert.equal(registered.length, 1);
assert.equal(registered[0].target, 'h5-direct');
assert.equal(isDirectChatSessionId(result.sessionId), true);
});
test('direct chat creates a direct session, saves snapshot, and bills returned usage', async () => {
const registered = [];
const saved = [];
const billed = [];
const llmMessages = [];
const service = createDirectChatService({
enabled: true,
userAuth: {
async registerAgentSession(userId, sessionId, target) {
registered.push({ userId, sessionId, target });
},
async getBillingState() {
return { lastInputTokens: 10, lastOutputTokens: 5 };
},
async billSessionUsage(userId, sessionId, tokenState, requestId) {
billed.push({ userId, sessionId, tokenState, requestId });
return { ok: true, costCents: 1 };
},
},
llmProviderService: {
async createChatCompletion({ messages }) {
llmMessages.push(messages);
return {
ok: true,
providerId: 'custom_deepseek',
model: 'deepseek-chat',
reply: '你好,已收到。',
usage: { inputTokens: 3, outputTokens: 4 },
};
},
},
sessionSnapshotService: {
async get() {
return null;
},
async save(sessionId, userId, session, messages) {
saved.push({ sessionId, userId, session, messages });
},
},
memoryV2: {
async resolve() {
return { memories: [{ label: 'preference', text: '喜欢简洁回答' }] };
},
},
});
const result = await service.run({
userId: 'user-1',
requestId: 'req-1',
userMessage: {
id: 'msg-user-1',
role: 'user',
content: [{ type: 'text', text: '打个招呼' }],
metadata: { userVisible: true },
},
});
assert.equal(result.ok, true);
assert.equal(isDirectChatSessionId(result.sessionId), true);
assert.deepEqual(registered, [
{ userId: 'user-1', sessionId: result.sessionId, target: 'h5-direct' },
]);
assert.equal(saved.length, 2);
assert.equal(saved[1].messages.length, 2);
assert.equal(saved[1].messages[1].content[0].text, '你好,已收到。');
assert.match(llmMessages[0][0].content, /喜欢简洁回答/);
assert.deepEqual(billed, [{
userId: 'user-1',
sessionId: result.sessionId,
tokenState: {
accumulatedInputTokens: 13,
accumulatedOutputTokens: 9,
},
requestId: 'req-1',
}]);
});
test('direct chat run can be replayed through finite session events', async () => {
const snapshots = new Map();
const service = createDirectChatService({
enabled: true,
userAuth: {
async registerAgentSession() {},
async getBillingState() {
return null;
},
async billSessionUsage() {
return null;
},
},
llmProviderService: {
async createChatCompletion() {
return {
ok: true,
reply: '这是直连回复。',
usage: null,
};
},
},
sessionSnapshotService: {
async get(sessionId) {
return snapshots.get(sessionId) ?? null;
},
async save(sessionId, userId, session, messages) {
snapshots.set(sessionId, { session, messages, userId });
},
},
});
const result = await service.run({
userId: 'user-1',
requestId: 'req-e2e',
userMessage: {
id: 'user-e2e',
role: 'user',
content: [{ type: 'text', text: '讲个程序员笑话' }],
metadata: { userVisible: true },
},
});
const snapshot = snapshots.get(result.sessionId);
const res = {
statusCode: 200,
headers: {},
chunks: [],
ended: false,
writableEnded: false,
status(code) {
this.statusCode = code;
return this;
},
setHeader(key, value) {
this.headers[key] = value;
},
write(chunk) {
this.chunks.push(chunk);
},
end() {
this.ended = true;
this.writableEnded = true;
},
};
sendDirectChatSessionEvents({ once() {} }, res, snapshot);
const stream = res.chunks.join('');
assert.equal(res.ended, true);
assert.equal(res.headers['Content-Type'], 'text/event-stream; charset=utf-8');
assert.match(stream, /"type":"UpdateConversation"/);
assert.match(stream, /"type":"Finish"/);
assert.match(stream, /这是直连回复。/);
});
test('direct chat rejects non-text messages', () => {
const service = createDirectChatService({
enabled: true,
userAuth: {},
llmProviderService: {},
sessionSnapshotService: {},
});
assert.equal(service.canHandle({
userMessage: {
role: 'user',
content: [{ type: 'image_url', image_url: { url: 'x' } }],
},
}), false);
});
test('direct chat rejects page generation tasks so the execution backend can handle them', () => {
const service = createDirectChatService({
enabled: true,
userAuth: {},
llmProviderService: {},
sessionSnapshotService: {},
});
assert.equal(service.canHandle({
toolMode: 'chat',
userMessage: {
role: 'user',
content: [{ type: 'text', text: '帮我生成一个秋夜诗页面 public/autumn-night-poem.html' }],
},
}), false);
});
test('direct chat rejects themed article page tasks without explicit html path', () => {
const service = createDirectChatService({
enabled: true,
userAuth: {},
llmProviderService: {},
sessionSnapshotService: {},
});
assert.equal(service.canHandle({
toolMode: 'chat',
userMessage: {
role: 'user',
content: [{ type: 'text', text: '帮我写个主题文章' }],
},
}), false);
assert.equal(service.explainCanHandle({
toolMode: 'chat',
userMessage: {
role: 'user',
content: [{ type: 'text', text: '帮我写个主题文章' }],
},
}).reason, 'task_execution_required');
});
test('direct chat allows llm-routed replies on regular agent sessions', () => {
const service = createDirectChatService({
enabled: true,
userAuth: {},
llmProviderService: {},
sessionSnapshotService: {},
});
const userMessage = {
role: 'user',
content: [{ type: 'text', text: '我想对太湖有更多的了解' }],
metadata: { displayText: '我想对太湖有更多的了解' },
};
assert.equal(service.canHandle({
sessionId: '20260704_11',
routingDecision: 'direct_chat',
userMessage,
}), true);
assert.equal(service.explainCanHandle({
sessionId: '20260704_11',
routingDecision: 'direct_chat',
userMessage,
}).reason, null);
});
test('direct chat run saves portal reply into an existing agent session', async () => {
const saved = [];
const service = createDirectChatService({
enabled: true,
userAuth: {
async getBillingState() {
return null;
},
async billSessionUsage() {
return null;
},
},
llmProviderService: {
async createChatCompletion() {
return {
ok: true,
model: 'deepseek-chat',
reply: '太湖是中国第三大淡水湖。',
usage: null,
};
},
},
sessionSnapshotService: {
async get(sessionId) {
return {
session: { id: sessionId, name: 'New Chat', updated_at: '2026-07-04T00:00:00.000Z' },
messages: [{
id: 'user-1',
role: 'user',
content: [{ type: 'text', text: 'hi' }],
metadata: { userVisible: true },
}],
};
},
async save(sessionId, userId, session, messages) {
saved.push({ sessionId, userId, session, messages });
},
},
});
const result = await service.run({
userId: 'user-1',
sessionId: '20260704_11',
requestId: 'req-taihu',
routingDecision: 'direct_chat',
userMessage: {
id: 'user-taihu',
role: 'user',
content: [{ type: 'text', text: '我想对太湖有更多的了解' }],
metadata: { displayText: '我想对太湖有更多的了解', userVisible: true },
},
});
assert.equal(result.sessionId, '20260704_11');
assert.equal(saved.length, 2);
assert.equal(saved[1].messages.at(-1)?.metadata?.source, 'portal-direct-chat');
assert.match(saved[1].messages.at(-1)?.content?.[0]?.text ?? '', /太湖/);
});
test('portal direct chat snapshot replay stays enabled for dedicated h5direct sessions', () => {
const now = new Date().toISOString();
const snapshot = {
session: { updated_at: now },
messages: [
{ role: 'user', content: [{ type: 'text', text: 'hi' }] },
{
role: 'assistant',
metadata: { source: 'portal-direct-chat' },
content: [{ type: 'text', text: '你好' }],
},
],
};
assert.equal(
isPortalDirectChatSnapshot(snapshot, { sessionId: 'h5direct_abc' }),
true,
);
});
test('portal direct chat snapshot is disabled after agent escalation on regular sessions', () => {
const now = new Date().toISOString();
const snapshot = {
session: { updated_at: now },
messages: [
{ role: 'user', content: [{ type: 'text', text: 'hi' }] },
{
role: 'assistant',
metadata: { source: 'portal-direct-chat' },
content: [{ type: 'text', text: '你好' }],
},
{
role: 'user',
content: [{ type: 'text', text: '帮我写一个 200 字散文,做成页面' }],
},
],
};
assert.equal(
isPortalDirectChatSnapshot(snapshot, { sessionId: '20260704_31' }),
false,
);
});
test('portal direct chat snapshot still replays a completed direct turn on regular sessions', () => {
const now = new Date().toISOString();
const snapshot = {
session: { updated_at: now },
messages: [
{ role: 'user', content: [{ type: 'text', text: '我想对太湖有更多的了解' }] },
{
role: 'assistant',
metadata: { source: 'portal-direct-chat' },
content: [{ type: 'text', text: '太湖是中国第三大淡水湖。' }],
},
],
};
assert.equal(
isPortalDirectChatSnapshot(snapshot, { sessionId: '20260704_11' }),
true,
);
});
test('shouldExpirePortalDirectChatSnapshot detects agent runs after a portal direct turn', async () => {
const now = new Date().toISOString();
const snapshot = {
session: { updated_at: now },
meta: { synced_at: 1000 },
messages: [
{ role: 'user', content: [{ type: 'text', text: 'hi' }] },
{
role: 'assistant',
metadata: { source: 'portal-direct-chat' },
content: [{ type: 'text', text: '你好' }],
},
],
};
const pool = {
async query(_sql, params) {
assert.deepEqual(params, ['20260704_31', 1000]);
return [[{ id: 'agent-run-1' }]];
},
};
assert.equal(
await shouldExpirePortalDirectChatSnapshot(pool, '20260704_31', snapshot),
true,
);
});
test('shouldExpirePortalDirectChatSnapshot keeps pure direct chat sessions fast', async () => {
const now = new Date().toISOString();
const snapshot = {
session: { updated_at: now },
meta: { synced_at: 1000 },
messages: [
{ role: 'user', content: [{ type: 'text', text: '太湖' }] },
{
role: 'assistant',
metadata: { source: 'portal-direct-chat' },
content: [{ type: 'text', text: '太湖是中国第三大淡水湖。' }],
},
],
};
const pool = {
async query() {
return [[]];
},
};
assert.equal(
await shouldExpirePortalDirectChatSnapshot(pool, '20260704_11', snapshot),
false,
);
});