fix: align discussion workspace and tool schemas

This commit is contained in:
zhi
2026-04-02 11:52:20 +00:00
parent 7bccb660df
commit 895cfe3bab
8 changed files with 88 additions and 10 deletions

View File

@@ -62,6 +62,7 @@ test('handleCallback closes an active discussion and records the resolved summar
api: makeApi() as any,
workspaceRoot: workspace,
forceNoReplyForSession: (sessionKey) => forcedSessions.push(sessionKey),
getDiscussionSessionKeys: () => ['session-beta-helper'],
});
await service.initDiscussion({
@@ -69,6 +70,7 @@ test('handleCallback closes an active discussion and records the resolved summar
originChannelId: 'origin-2',
initiatorAgentId: 'agent-beta',
initiatorSessionId: 'session-beta',
initiatorWorkspaceRoot: workspace,
discussGuide: 'Write the wrap-up.',
});
@@ -84,7 +86,7 @@ test('handleCallback closes an active discussion and records the resolved summar
assert.equal(result.discussion.status, 'closed');
assert.equal(result.discussion.summaryPath, fs.realpathSync.native(summaryAbsPath));
assert.ok(result.discussion.completedAt);
assert.deepEqual(forcedSessions, ['session-beta']);
assert.deepEqual(forcedSessions.sort(), ['session-beta', 'session-beta-helper']);
});
test('handleCallback rejects duplicate callback after the discussion is already closed', async () => {
@@ -142,6 +144,7 @@ test('handleCallback accepts a valid summaryPath inside the initiator workspace'
originChannelId: 'origin-4',
initiatorAgentId: 'agent-delta',
initiatorSessionId: 'session-delta',
initiatorWorkspaceRoot: workspace,
discussGuide: 'Path validation.',
});
@@ -181,6 +184,39 @@ test('handleCallback rejects a missing summary file', async () => {
);
});
test('handleCallback uses the initiator workspace root instead of the process cwd', async () => {
const workspace = makeWorkspace();
const summaryRelPath = path.join('notes', 'initiator-only.md');
const summaryAbsPath = path.join(workspace, summaryRelPath);
fs.mkdirSync(path.dirname(summaryAbsPath), { recursive: true });
fs.writeFileSync(summaryAbsPath, 'initiator workspace file\n');
const differentDefaultWorkspace = makeWorkspace();
const service = createDiscussionService({
api: makeApi() as any,
workspaceRoot: differentDefaultWorkspace,
forceNoReplyForSession: () => {},
});
await service.initDiscussion({
discussionChannelId: 'discussion-workspace-root-1',
originChannelId: 'origin-6a',
initiatorAgentId: 'agent-zeta-root',
initiatorSessionId: 'session-zeta-root',
initiatorWorkspaceRoot: workspace,
discussGuide: 'Use initiator workspace root.',
});
const result = await service.handleCallback({
channelId: 'discussion-workspace-root-1',
summaryPath: summaryRelPath,
callerAgentId: 'agent-zeta-root',
callerSessionKey: 'session-zeta-root',
});
assert.equal(result.summaryPath, fs.realpathSync.native(summaryAbsPath));
});
test('handleCallback rejects .. path traversal outside the initiator workspace', async () => {
const workspace = makeWorkspace();
const outsideDir = makeWorkspace();
@@ -198,6 +234,7 @@ test('handleCallback rejects .. path traversal outside the initiator workspace',
originChannelId: 'origin-6',
initiatorAgentId: 'agent-zeta',
initiatorSessionId: 'session-zeta',
initiatorWorkspaceRoot: workspace,
discussGuide: 'Reject traversal.',
});
@@ -232,6 +269,7 @@ test('handleCallback rejects an absolute path outside the initiator workspace',
originChannelId: 'origin-7',
initiatorAgentId: 'agent-eta',
initiatorSessionId: 'session-eta',
initiatorWorkspaceRoot: workspace,
discussGuide: 'Reject absolute outside path.',
});

View File

@@ -5,6 +5,7 @@ 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>;
};
@@ -62,6 +63,7 @@ test('plain private channel create works unchanged without discussion params', a
const tool = api.tools.get('dirigent_discord_control');
assert.ok(tool);
assert.ok(tool!.parameters);
const result = await tool!.handler({
action: 'channel-private-create',
@@ -100,6 +102,7 @@ test('private channel create rejects callbackChannelId without discussGuide', as
const tool = api.tools.get('dirigent_discord_control');
assert.ok(tool);
assert.ok(tool!.parameters);
const result = await tool!.handler({
action: 'channel-private-create',
@@ -142,6 +145,7 @@ test('discussion-mode channel create initializes discussion metadata', async ()
const tool = api.tools.get('dirigent_discord_control');
assert.ok(tool);
assert.ok(tool!.parameters);
const result = await tool!.handler({
action: 'channel-private-create',
@@ -152,6 +156,7 @@ test('discussion-mode channel create initializes discussion metadata', async ()
}, {
agentId: 'agent-a',
sessionKey: 'session-a',
workspaceRoot: '/workspace/agent-a',
});
assert.equal(result.isError, undefined);
@@ -162,6 +167,7 @@ test('discussion-mode channel create initializes discussion metadata', async ()
originChannelId: 'origin-1',
initiatorAgentId: 'agent-a',
initiatorSessionId: 'session-a',
initiatorWorkspaceRoot: '/workspace/agent-a',
discussGuide: 'Decide the callback contract.',
});
} finally {
@@ -190,6 +196,7 @@ test('discuss-callback registers and forwards channel/session/agent context', as
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',