216 lines
5.8 KiB
TypeScript
216 lines
5.8 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
import { registerDirigentTools } from '../plugin/tools/register-tools.ts';
|
|
|
|
type RegisteredTool = {
|
|
name: string;
|
|
parameters?: Record<string, unknown>;
|
|
handler: (params: Record<string, unknown>, ctx?: Record<string, unknown>) => Promise<any>;
|
|
};
|
|
|
|
function makeApi() {
|
|
const tools = new Map<string, RegisteredTool>();
|
|
return {
|
|
config: {
|
|
channels: {
|
|
discord: {
|
|
accounts: {
|
|
bot: { token: 'discord-bot-token' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
logger: {
|
|
info: (_msg: string) => {},
|
|
warn: (_msg: string) => {},
|
|
},
|
|
registerTool(def: RegisteredTool) {
|
|
tools.set(def.name, def);
|
|
},
|
|
tools,
|
|
};
|
|
}
|
|
|
|
function pickDefined(obj: Record<string, unknown>) {
|
|
return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== undefined));
|
|
}
|
|
|
|
test('plain private channel create works unchanged without discussion params', async () => {
|
|
const api = makeApi();
|
|
let initDiscussionCalls = 0;
|
|
|
|
const originalFetch = globalThis.fetch;
|
|
globalThis.fetch = (async (_url: string | URL | Request, _init?: RequestInit) => {
|
|
return new Response(JSON.stringify({ id: 'created-channel-1', name: 'plain-room' }), { status: 200 });
|
|
}) as typeof fetch;
|
|
|
|
try {
|
|
registerDirigentTools({
|
|
api: api as any,
|
|
baseConfig: {},
|
|
pickDefined,
|
|
discussionService: {
|
|
async initDiscussion() {
|
|
initDiscussionCalls += 1;
|
|
return {};
|
|
},
|
|
async handleCallback() {
|
|
return { ok: true };
|
|
},
|
|
},
|
|
});
|
|
|
|
const tool = api.tools.get('dirigent_discord_control');
|
|
assert.ok(tool);
|
|
assert.ok(tool!.parameters);
|
|
|
|
const result = await tool!.handler({
|
|
action: 'channel-private-create',
|
|
guildId: 'guild-1',
|
|
name: 'plain-room',
|
|
allowedUserIds: ['user-1'],
|
|
}, {
|
|
agentId: 'agent-a',
|
|
sessionKey: 'session-a',
|
|
});
|
|
|
|
assert.equal(result.isError, undefined);
|
|
assert.match(result.content[0].text, /"discussionMode": false/);
|
|
assert.equal(initDiscussionCalls, 0);
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
test('private channel create rejects callbackChannelId without discussGuide', async () => {
|
|
const api = makeApi();
|
|
|
|
registerDirigentTools({
|
|
api: api as any,
|
|
baseConfig: {},
|
|
pickDefined,
|
|
discussionService: {
|
|
async initDiscussion() {
|
|
return {};
|
|
},
|
|
async handleCallback() {
|
|
return { ok: true };
|
|
},
|
|
},
|
|
});
|
|
|
|
const tool = api.tools.get('dirigent_discord_control');
|
|
assert.ok(tool);
|
|
assert.ok(tool!.parameters);
|
|
|
|
const result = await tool!.handler({
|
|
action: 'channel-private-create',
|
|
guildId: 'guild-1',
|
|
name: 'discussion-room',
|
|
callbackChannelId: 'origin-1',
|
|
}, {
|
|
agentId: 'agent-a',
|
|
sessionKey: 'session-a',
|
|
});
|
|
|
|
assert.equal(result.isError, true);
|
|
assert.equal(result.content[0].text, 'discussGuide is required when callbackChannelId is provided');
|
|
});
|
|
|
|
test('discussion-mode channel create initializes discussion metadata', async () => {
|
|
const api = makeApi();
|
|
const initCalls: Array<Record<string, unknown>> = [];
|
|
|
|
const originalFetch = globalThis.fetch;
|
|
globalThis.fetch = (async (_url: string | URL | Request, _init?: RequestInit) => {
|
|
return new Response(JSON.stringify({ id: 'discussion-channel-1', name: 'discussion-room' }), { status: 200 });
|
|
}) as typeof fetch;
|
|
|
|
try {
|
|
registerDirigentTools({
|
|
api: api as any,
|
|
baseConfig: {},
|
|
pickDefined,
|
|
discussionService: {
|
|
async initDiscussion(params) {
|
|
initCalls.push(params as Record<string, unknown>);
|
|
return {};
|
|
},
|
|
async handleCallback() {
|
|
return { ok: true };
|
|
},
|
|
},
|
|
});
|
|
|
|
const tool = api.tools.get('dirigent_discord_control');
|
|
assert.ok(tool);
|
|
assert.ok(tool!.parameters);
|
|
|
|
const result = await tool!.handler({
|
|
action: 'channel-private-create',
|
|
guildId: 'guild-1',
|
|
name: 'discussion-room',
|
|
callbackChannelId: 'origin-1',
|
|
discussGuide: 'Decide the callback contract.',
|
|
}, {
|
|
agentId: 'agent-a',
|
|
sessionKey: 'session-a',
|
|
workspaceRoot: '/workspace/agent-a',
|
|
});
|
|
|
|
assert.equal(result.isError, undefined);
|
|
assert.match(result.content[0].text, /"discussionMode": true/);
|
|
assert.equal(initCalls.length, 1);
|
|
assert.deepEqual(initCalls[0], {
|
|
discussionChannelId: 'discussion-channel-1',
|
|
originChannelId: 'origin-1',
|
|
initiatorAgentId: 'agent-a',
|
|
initiatorSessionId: 'session-a',
|
|
initiatorWorkspaceRoot: '/workspace/agent-a',
|
|
discussGuide: 'Decide the callback contract.',
|
|
});
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
test('discuss-callback registers and forwards channel/session/agent context', async () => {
|
|
const api = makeApi();
|
|
const callbackCalls: Array<Record<string, unknown>> = [];
|
|
|
|
registerDirigentTools({
|
|
api: api as any,
|
|
baseConfig: {},
|
|
pickDefined,
|
|
discussionService: {
|
|
async initDiscussion() {
|
|
return {};
|
|
},
|
|
async handleCallback(params) {
|
|
callbackCalls.push(params as Record<string, unknown>);
|
|
return { ok: true, summaryPath: '/workspace/summary.md' };
|
|
},
|
|
},
|
|
});
|
|
|
|
const tool = api.tools.get('discuss-callback');
|
|
assert.ok(tool);
|
|
assert.ok(tool!.parameters);
|
|
|
|
const result = await tool!.handler({ summaryPath: 'plans/summary.md' }, {
|
|
channelId: 'discussion-1',
|
|
agentId: 'agent-a',
|
|
sessionKey: 'session-a',
|
|
});
|
|
|
|
assert.equal(result.isError, undefined);
|
|
assert.deepEqual(callbackCalls, [{
|
|
channelId: 'discussion-1',
|
|
summaryPath: 'plans/summary.md',
|
|
callerAgentId: 'agent-a',
|
|
callerSessionKey: 'session-a',
|
|
}]);
|
|
assert.match(result.content[0].text, /"summaryPath": "\/workspace\/summary.md"/);
|
|
});
|