Skip to content

Commit a74702b

Browse files
EricEric
authored andcommitted
fix(msteams): persist conversation reference during DM pairing
When a user sends their first DM with dmPolicy=pairing, the handler returned early after upsertPairingRequest without calling conversationStore.upsert(). This meant the conversation reference was never saved, so --notify could not reach the user after pairing was approved. Move conversationStore.upsert() into the pairing block before the early return so the reference is persisted immediately. Closes #43323
1 parent 8ea79b6 commit a74702b

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed

extensions/msteams/src/monitor-handler/message-handler.authz.test.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,110 @@ describe("msteams monitor handler authz", () => {
152152

153153
expect(conversationStore.upsert).not.toHaveBeenCalled();
154154
});
155+
156+
it("persists conversation reference on first DM during pairing", async () => {
157+
const upsertPairingRequest = vi.fn(async () => ({ id: "user-aad", channel: "msteams" }));
158+
setMSTeamsRuntime({
159+
logging: { shouldLogVerbose: () => false },
160+
channel: {
161+
debounce: {
162+
resolveInboundDebounceMs: () => 0,
163+
createInboundDebouncer: <T>(params: {
164+
onFlush: (entries: T[]) => Promise<void>;
165+
}): { enqueue: (entry: T) => Promise<void> } => ({
166+
enqueue: async (entry: T) => {
167+
await params.onFlush([entry]);
168+
},
169+
}),
170+
},
171+
pairing: {
172+
readAllowFromStore: vi.fn(async () => []),
173+
upsertPairingRequest,
174+
},
175+
text: {
176+
hasControlCommand: () => false,
177+
},
178+
},
179+
} as unknown as PluginRuntime);
180+
181+
const conversationStore = {
182+
upsert: vi.fn(async () => undefined),
183+
};
184+
185+
const deps: MSTeamsMessageHandlerDeps = {
186+
cfg: {
187+
channels: {
188+
msteams: {
189+
dmPolicy: "pairing",
190+
allowFrom: [],
191+
},
192+
},
193+
} as OpenClawConfig,
194+
runtime: { error: vi.fn() } as unknown as RuntimeEnv,
195+
appId: "test-app",
196+
adapter: {} as MSTeamsMessageHandlerDeps["adapter"],
197+
tokenProvider: {
198+
getAccessToken: vi.fn(async () => "token"),
199+
},
200+
textLimit: 4000,
201+
mediaMaxBytes: 1024 * 1024,
202+
conversationStore:
203+
conversationStore as unknown as MSTeamsMessageHandlerDeps["conversationStore"],
204+
pollStore: {
205+
recordVote: vi.fn(async () => null),
206+
} as unknown as MSTeamsMessageHandlerDeps["pollStore"],
207+
log: {
208+
info: vi.fn(),
209+
debug: vi.fn(),
210+
error: vi.fn(),
211+
} as unknown as MSTeamsMessageHandlerDeps["log"],
212+
};
213+
214+
const handler = createMSTeamsMessageHandler(deps);
215+
await handler({
216+
activity: {
217+
id: "dm-1",
218+
type: "message",
219+
text: "hello",
220+
from: {
221+
id: "user-id",
222+
aadObjectId: "user-aad",
223+
name: "New User",
224+
},
225+
recipient: {
226+
id: "bot-id",
227+
name: "Bot",
228+
},
229+
conversation: {
230+
231+
conversationType: "personal",
232+
},
233+
channelData: {},
234+
channelId: "msteams",
235+
serviceUrl: "https://smba.trafficmanager.net/teams/",
236+
locale: "en-US",
237+
attachments: [],
238+
},
239+
sendActivity: vi.fn(async () => undefined),
240+
} as unknown as Parameters<typeof handler>[0]);
241+
242+
expect(upsertPairingRequest).toHaveBeenCalledWith({
243+
channel: "msteams",
244+
accountId: "default",
245+
id: "user-aad",
246+
meta: { name: "New User" },
247+
});
248+
// Conversation reference must be persisted so --notify works after approval.
249+
expect(conversationStore.upsert).toHaveBeenCalledWith(
250+
251+
expect.objectContaining({
252+
user: { id: "user-id", name: "New User", aadObjectId: "user-aad" },
253+
serviceUrl: "https://smba.trafficmanager.net/teams/",
254+
conversation: expect.objectContaining({
255+
256+
conversationType: "personal",
257+
}),
258+
}),
259+
);
260+
});
155261
});

extensions/msteams/src/monitor-handler/message-handler.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,29 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
210210
allowNameMatching: isDangerousNameMatchingEnabled(msteamsCfg),
211211
});
212212
if (access.decision === "pairing") {
213+
// Persist conversation reference before returning so that
214+
// `--notify` can reach the user after pairing is approved.
215+
const agent = activity.recipient;
216+
const pairingRef: StoredConversationReference = {
217+
activityId: activity.id,
218+
user: { id: from.id, name: from.name, aadObjectId: from.aadObjectId },
219+
agent,
220+
bot: agent ? { id: agent.id, name: agent.name } : undefined,
221+
conversation: {
222+
id: conversationId,
223+
conversationType,
224+
tenantId: conversation?.tenantId,
225+
},
226+
channelId: activity.channelId,
227+
serviceUrl: activity.serviceUrl,
228+
locale: activity.locale,
229+
};
230+
conversationStore.upsert(conversationId, pairingRef).catch((err) => {
231+
log.debug?.("failed to save pairing conversation reference", {
232+
error: formatUnknownError(err),
233+
});
234+
});
235+
213236
const request = await pairing.upsertPairingRequest({
214237
id: senderId,
215238
meta: { name: senderName },

0 commit comments

Comments
 (0)