11import type { OpenClawConfig } from "../runtime-api.js" ;
2+ import { createMSTeamsConversationStoreFs } from "./conversation-store-fs.js" ;
23import { deleteGraphRequest , fetchGraphJson , postGraphJson , resolveGraphToken } from "./graph.js" ;
34
45type GraphMessageBody = {
@@ -47,6 +48,34 @@ function stripTargetPrefix(raw: string): string {
4748 return trimmed ;
4849}
4950
51+ /**
52+ * Resolve a target to a Graph-compatible conversation ID.
53+ * `user:<aadId>` targets are looked up in the conversation store to find the
54+ * actual `19:xxx@thread.*` chat ID that Graph API requires.
55+ * Conversation IDs and `teamId/channelId` pairs pass through unchanged.
56+ */
57+ async function resolveGraphConversationId ( to : string ) : Promise < string > {
58+ const trimmed = to . trim ( ) ;
59+ const isUserTarget = / ^ u s e r : / i. test ( trimmed ) ;
60+ const cleaned = stripTargetPrefix ( trimmed ) ;
61+
62+ // teamId/channelId or already a conversation ID (19:xxx) — use directly
63+ if ( ! isUserTarget ) {
64+ return cleaned ;
65+ }
66+
67+ // user:<aadId> — look up the conversation store for the real chat ID
68+ const store = createMSTeamsConversationStoreFs ( ) ;
69+ const found = await store . findByUserId ( cleaned ) ;
70+ if ( ! found ) {
71+ throw new Error (
72+ `No conversation found for user:${ cleaned } . ` +
73+ "The bot must receive a message from this user before Graph API operations work." ,
74+ ) ;
75+ }
76+ return found . conversationId ;
77+ }
78+
5079function resolveConversationPath ( to : string ) : {
5180 kind : "chat" | "channel" ;
5281 basePath : string ;
@@ -91,7 +120,8 @@ export async function getMessageMSTeams(
91120 params : GetMessageMSTeamsParams ,
92121) : Promise < GetMessageMSTeamsResult > {
93122 const token = await resolveGraphToken ( params . cfg ) ;
94- const { basePath } = resolveConversationPath ( params . to ) ;
123+ const conversationId = await resolveGraphConversationId ( params . to ) ;
124+ const { basePath } = resolveConversationPath ( conversationId ) ;
95125 const path = `${ basePath } /messages/${ encodeURIComponent ( params . messageId ) } ` ;
96126 const msg = await fetchGraphJson < GraphMessage > ( { token, path } ) ;
97127 return {
@@ -116,7 +146,8 @@ export async function pinMessageMSTeams(
116146 params : PinMessageMSTeamsParams ,
117147) : Promise < { ok : true ; pinnedMessageId ?: string } > {
118148 const token = await resolveGraphToken ( params . cfg ) ;
119- const conv = resolveConversationPath ( params . to ) ;
149+ const conversationId = await resolveGraphConversationId ( params . to ) ;
150+ const conv = resolveConversationPath ( conversationId ) ;
120151
121152 if ( conv . kind === "channel" ) {
122153 // Graph v1.0 doesn't have channel pin — use the pinnedMessages pattern on chat
@@ -153,7 +184,8 @@ export async function unpinMessageMSTeams(
153184 params : UnpinMessageMSTeamsParams ,
154185) : Promise < { ok : true } > {
155186 const token = await resolveGraphToken ( params . cfg ) ;
156- const conv = resolveConversationPath ( params . to ) ;
187+ const conversationId = await resolveGraphConversationId ( params . to ) ;
188+ const conv = resolveConversationPath ( conversationId ) ;
157189 const path = `${ conv . basePath } /pinnedMessages/${ encodeURIComponent ( params . pinnedMessageId ) } ` ;
158190 await deleteGraphRequest ( { token, path } ) ;
159191 return { ok : true } ;
@@ -175,7 +207,8 @@ export async function listPinsMSTeams(
175207 params : ListPinsMSTeamsParams ,
176208) : Promise < ListPinsMSTeamsResult > {
177209 const token = await resolveGraphToken ( params . cfg ) ;
178- const conv = resolveConversationPath ( params . to ) ;
210+ const conversationId = await resolveGraphConversationId ( params . to ) ;
211+ const conv = resolveConversationPath ( conversationId ) ;
179212 const path = `${ conv . basePath } /pinnedMessages?$expand=message` ;
180213 const res = await fetchGraphJson < GraphPinnedMessagesResponse > ( { token, path } ) ;
181214 const pins = ( res . value ?? [ ] ) . map ( ( pin ) => ( {
0 commit comments