fix: align discussion workspace and tool schemas
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -6,6 +6,7 @@ export type DiscussionMetadata = {
|
||||
originChannelId: string;
|
||||
initiatorAgentId: string;
|
||||
initiatorSessionId: string;
|
||||
initiatorWorkspaceRoot?: string;
|
||||
discussGuide: string;
|
||||
status: DiscussionStatus;
|
||||
createdAt: string;
|
||||
|
||||
@@ -16,6 +16,18 @@ export const sessionChannelId = new Map<string, string>();
|
||||
export const sessionAccountId = new Map<string, string>();
|
||||
export const sessionTurnHandled = new Set<string>();
|
||||
export const forceNoReplySessions = new Set<string>();
|
||||
export const discussionChannelSessions = new Map<string, Set<string>>();
|
||||
|
||||
export function recordDiscussionSession(channelId: string, sessionKey: string): void {
|
||||
if (!channelId || !sessionKey) return;
|
||||
const current = discussionChannelSessions.get(channelId) || new Set<string>();
|
||||
current.add(sessionKey);
|
||||
discussionChannelSessions.set(channelId, current);
|
||||
}
|
||||
|
||||
export function getDiscussionSessionKeys(channelId: string): string[] {
|
||||
return [...(discussionChannelSessions.get(channelId) || new Set<string>())];
|
||||
}
|
||||
|
||||
export function pruneDecisionMap(now = Date.now()): void {
|
||||
for (const [k, v] of sessionDecision.entries()) {
|
||||
|
||||
@@ -21,6 +21,7 @@ type BeforeModelResolveDeps = {
|
||||
sessionAllowed: Map<string, boolean>;
|
||||
sessionChannelId: Map<string, string>;
|
||||
sessionAccountId: Map<string, string>;
|
||||
recordDiscussionSession?: (channelId: string, sessionKey: string) => void;
|
||||
forceNoReplySessions: Set<string>;
|
||||
policyState: { channelPolicies: Record<string, unknown> };
|
||||
DECISION_TTL_MS: number;
|
||||
@@ -43,6 +44,7 @@ export function registerBeforeModelResolveHook(deps: BeforeModelResolveDeps): vo
|
||||
sessionAllowed,
|
||||
sessionChannelId,
|
||||
sessionAccountId,
|
||||
recordDiscussionSession,
|
||||
forceNoReplySessions,
|
||||
policyState,
|
||||
DECISION_TTL_MS,
|
||||
@@ -92,6 +94,7 @@ export function registerBeforeModelResolveHook(deps: BeforeModelResolveDeps): vo
|
||||
|
||||
if (derived.channelId) {
|
||||
sessionChannelId.set(key, derived.channelId);
|
||||
recordDiscussionSession?.(derived.channelId, key);
|
||||
if (discussionService?.isClosedDiscussion(derived.channelId)) {
|
||||
sessionAllowed.set(key, false);
|
||||
api.logger.info(`dirigent: before_model_resolve forcing no-reply for closed discussion channel=${derived.channelId} session=${key}`);
|
||||
|
||||
@@ -22,7 +22,9 @@ import { enterMultiMessageMode, exitMultiMessageMode, isMultiMessageMode } from
|
||||
import {
|
||||
DECISION_TTL_MS,
|
||||
forceNoReplySessions,
|
||||
getDiscussionSessionKeys,
|
||||
pruneDecisionMap,
|
||||
recordDiscussionSession,
|
||||
sessionAccountId,
|
||||
sessionAllowed,
|
||||
sessionChannelId,
|
||||
@@ -127,6 +129,7 @@ export default {
|
||||
forceNoReplyForSession: (sessionKey: string) => {
|
||||
if (sessionKey) forceNoReplySessions.add(sessionKey);
|
||||
},
|
||||
getDiscussionSessionKeys,
|
||||
});
|
||||
|
||||
// Register tools
|
||||
@@ -162,6 +165,7 @@ export default {
|
||||
sessionAllowed,
|
||||
sessionChannelId,
|
||||
sessionAccountId,
|
||||
recordDiscussionSession,
|
||||
forceNoReplySessions,
|
||||
policyState,
|
||||
DECISION_TTL_MS,
|
||||
|
||||
@@ -13,6 +13,7 @@ type ToolDeps = {
|
||||
originChannelId: string;
|
||||
initiatorAgentId: string;
|
||||
initiatorSessionId: string;
|
||||
initiatorWorkspaceRoot?: string;
|
||||
discussGuide: string;
|
||||
}) => Promise<unknown>;
|
||||
handleCallback: (params: {
|
||||
@@ -119,6 +120,7 @@ export function registerDirigentTools(deps: ToolDeps): void {
|
||||
originChannelId: callbackChannelId,
|
||||
initiatorAgentId: String((params.__agentId as string | undefined) || ""),
|
||||
initiatorSessionId: String((params.__sessionKey as string | undefined) || ""),
|
||||
initiatorWorkspaceRoot: typeof params.__workspaceRoot === "string" ? params.__workspaceRoot : undefined,
|
||||
discussGuide,
|
||||
});
|
||||
}
|
||||
@@ -161,7 +163,7 @@ export function registerDirigentTools(deps: ToolDeps): void {
|
||||
api.registerTool({
|
||||
name: "dirigent_discord_control",
|
||||
description: "Create/update Discord private channels using the configured Discord bot token",
|
||||
inputSchema: {
|
||||
parameters: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
@@ -194,6 +196,7 @@ export function registerDirigentTools(deps: ToolDeps): void {
|
||||
...(params as Record<string, unknown>),
|
||||
__agentId: ctx?.agentId,
|
||||
__sessionKey: ctx?.sessionKey,
|
||||
__workspaceRoot: ctx?.workspaceRoot,
|
||||
};
|
||||
return executeDiscordAction(params.action as DiscordControlAction, nextParams);
|
||||
},
|
||||
@@ -202,7 +205,7 @@ export function registerDirigentTools(deps: ToolDeps): void {
|
||||
api.registerTool({
|
||||
name: "discuss-callback",
|
||||
description: "Close a discussion channel and notify the origin work channel with the discussion summary path",
|
||||
inputSchema: {
|
||||
parameters: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
|
||||
Reference in New Issue
Block a user