dsh-im-ops/test/agent-preset-session-lifecycle.test.mjs
2026-08-22 04:04:44 +08:00

135 lines
4.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import assert from 'node:assert/strict';
import { mkdtemp, realpath, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import test from 'node:test';
import {
BotWorkspaceStore,
createBotWorkspaceScope,
} from '../src/channels/shared/bot-workspace-store.mjs';
import { ConversationStateStore } from '../src/channels/shared/conversation-state-store.mjs';
import { TextHarnessBridge } from '../src/channels/shared/text-harness-bridge.mjs';
function message(messageId, content) {
return {
messageId,
senderId: 'actor-one',
senderIsBot: false,
kind: 'direct',
conversationId: 'chat-one',
content,
addressed: true,
replyTarget: { id: 'chat-one' },
};
}
test('/preset changes only the sessions created after /new and --default follows Host', async (t) => {
const root = await realpath(await mkdtemp(join(tmpdir(), 'dsh-im-preset-lifecycle-')));
t.after(() => rm(root, { recursive: true, force: true }));
const botId = 'bot-one';
const conversationKey = 'direct:chat-one';
const workspaces = await new BotWorkspaceStore(join(root, 'workspaces.json'), {
defaultWorkspace: root,
}).load();
await workspaces.ensure(botId, { defaultAgentPreset: 'preset-old' });
const state = await new ConversationStateStore(join(root, 'state.json')).load();
const creations = [];
const asks = [];
const sessions = new Set();
const harness = {
async createSession(options) {
const sessionId = `session-${String.fromCharCode(97 + creations.length)}`;
creations.push({ sessionId, options });
sessions.add(sessionId);
return sessionId;
},
async sessionExists(sessionId) {
return sessions.has(sessionId);
},
async ask(sessionId, text) {
asks.push({ sessionId, text });
return `answer-from-${sessionId}`;
},
};
const agentPresetCatalog = {
defaultId: 'preset-old',
items: [
{ id: 'preset-old', label: 'Old preset' },
{ id: 'preset-new', label: 'New preset' },
],
};
const scope = createBotWorkspaceScope(harness, {
botId,
workspaces,
state,
agentPresetCatalog,
});
const sent = [];
const bridge = new TextHarnessBridge({
descriptor: { key: 'test', label: 'Test' },
bot: { async sendText(_target, text) { sent.push(text); } },
harness: scope.harness,
state: scope.state,
logger: { warn() {}, error() {} },
});
await bridge.accept(message('message-one', 'first prompt'));
assert.equal(state.sessionFor(conversationKey), 'session-a');
assert.deepEqual(creations, [{
sessionId: 'session-a',
options: { workspace: root, agentPreset: 'preset-old' },
}]);
await bridge.accept(message('message-list', '/presetlist'));
assert.match(sent.at(-1), /2\. New preset(preset-new)/u);
await bridge.accept(message('message-preset', '/preset 2'));
assert.equal(workspaces.agentPresetFor(botId), 'preset-new');
assert.match(sent.at(-1), /已有会话不变/u);
assert.equal(state.sessionFor(conversationKey), 'session-a');
assert.equal(creations.length, 1, '/preset itself must not create a session');
await bridge.accept(message('message-two', 'still in the current chat'));
assert.equal(creations.length, 1, 'the current chat must reuse session A');
assert.equal(state.sessionFor(conversationKey), 'session-a');
await bridge.accept(message('message-new', '/new'));
assert.equal(state.sessionFor(conversationKey), null);
assert.equal(creations.length, 1, '/new itself must not create a session');
await bridge.accept(message('message-three', 'first prompt after /new'));
assert.equal(state.sessionFor(conversationKey), 'session-b');
assert.deepEqual(creations, [
{
sessionId: 'session-a',
options: { workspace: root, agentPreset: 'preset-old' },
},
{
sessionId: 'session-b',
options: { workspace: root, agentPreset: 'preset-new' },
},
]);
await bridge.accept(message('message-default', '/preset --default'));
assert.equal(workspaces.agentPresetFor(botId), null);
assert.equal(state.sessionFor(conversationKey), 'session-b');
assert.equal(creations.length, 2, '--default itself must not create a session');
await bridge.accept(message('message-new-default', '/new'));
await bridge.accept(message('message-four', 'first prompt using Host default'));
assert.equal(state.sessionFor(conversationKey), 'session-c');
assert.deepEqual(creations.at(-1), {
sessionId: 'session-c',
options: { workspace: root },
});
assert.deepEqual(asks, [
{ sessionId: 'session-a', text: 'first prompt' },
{ sessionId: 'session-a', text: 'still in the current chat' },
{ sessionId: 'session-b', text: 'first prompt after /new' },
{ sessionId: 'session-c', text: 'first prompt using Host default' },
]);
assert.equal(sent.at(-1), 'answer-from-session-c');
});