mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-08 23:13:17 +08:00
135 lines
4.8 KiB
JavaScript
135 lines
4.8 KiB
JavaScript
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');
|
||
});
|