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

@@ -16,16 +16,18 @@ type DiscussionServiceDeps = {
moderatorUserId?: string;
workspaceRoot?: string;
forceNoReplyForSession: (sessionKey: string) => void;
getDiscussionSessionKeys?: (channelId: string) => string[];
};
export function createDiscussionService(deps: DiscussionServiceDeps) {
const workspaceRoot = path.resolve(deps.workspaceRoot || process.cwd());
const defaultWorkspaceRoot = path.resolve(deps.workspaceRoot || process.cwd());
async function initDiscussion(params: {
discussionChannelId: string;
originChannelId: string;
initiatorAgentId: string;
initiatorSessionId: string;
initiatorWorkspaceRoot?: string;
discussGuide: string;
}): Promise<DiscussionMetadata> {
const metadata = createDiscussion({
@@ -34,6 +36,7 @@ export function createDiscussionService(deps: DiscussionServiceDeps) {
originChannelId: params.originChannelId,
initiatorAgentId: params.initiatorAgentId,
initiatorSessionId: params.initiatorSessionId,
initiatorWorkspaceRoot: params.initiatorWorkspaceRoot,
discussGuide: params.discussGuide,
status: "active",
createdAt: new Date().toISOString(),
@@ -71,17 +74,18 @@ export function createDiscussionService(deps: DiscussionServiceDeps) {
}
}
function validateSummaryPath(summaryPath: string): string {
function validateSummaryPath(summaryPath: string, workspaceRoot?: string): string {
if (!summaryPath || !summaryPath.trim()) throw new Error("summaryPath is required");
const resolved = path.resolve(workspaceRoot, summaryPath);
const relative = path.relative(workspaceRoot, resolved);
const effectiveWorkspaceRoot = path.resolve(workspaceRoot || defaultWorkspaceRoot);
const resolved = path.resolve(effectiveWorkspaceRoot, summaryPath);
const relative = path.relative(effectiveWorkspaceRoot, resolved);
if (relative.startsWith("..") || path.isAbsolute(relative)) {
throw new Error("summaryPath must stay inside the initiator workspace");
}
const real = fs.realpathSync.native(resolved);
const realWorkspace = fs.realpathSync.native(workspaceRoot);
const realWorkspace = fs.realpathSync.native(effectiveWorkspaceRoot);
const realRelative = path.relative(realWorkspace, real);
if (realRelative.startsWith("..") || path.isAbsolute(realRelative)) {
throw new Error("summaryPath resolves outside the initiator workspace");
@@ -108,11 +112,17 @@ export function createDiscussionService(deps: DiscussionServiceDeps) {
throw new Error("only the discussion initiator agent may call discuss-callback");
}
const realPath = validateSummaryPath(params.summaryPath);
const realPath = validateSummaryPath(params.summaryPath, metadata.initiatorWorkspaceRoot);
const closed = closeDiscussion(params.channelId, realPath);
if (!closed) throw new Error("failed to close discussion");
deps.forceNoReplyForSession(metadata.initiatorSessionId);
const discussionSessionKeys = new Set<string>([
metadata.initiatorSessionId,
...(deps.getDiscussionSessionKeys?.(metadata.discussionChannelId) || []),
]);
for (const sessionKey of discussionSessionKeys) {
if (sessionKey) deps.forceNoReplyForSession(sessionKey);
}
if (deps.moderatorBotToken) {
const result = await sendModeratorMessage(