diff --git a/plugins/workiq-preview/skills/workiq-preview/SKILL.md b/plugins/workiq-preview/skills/workiq-preview/SKILL.md index 2c3a62b..8c64864 100644 --- a/plugins/workiq-preview/skills/workiq-preview/SKILL.md +++ b/plugins/workiq-preview/skills/workiq-preview/SKILL.md @@ -1,6 +1,6 @@ --- name: workiq-preview -description: WorkIQ - Microsoft 365 tool surface for agents. Use for any workplace question or write action where data lives in M365. Supports semantic `ask` plus structured tools (`fetch`, create/update/delete, actions, functions, path/schema discovery) for mail, meetings/calendar, documents/files, Teams chats/channels, OneDrive/SharePoint, and people. Read triggers, "what did [person] say", priorities/top of mind, meeting decisions/action items, summarize thread/chat, find emails/docs, list meetings/messages/files/channels, project status/updates, "what changed since". Write triggers, send/reply/forward email, create/update/accept/decline meetings, mark read, delete drafts/items, send/post/reply/react in Teams, set presence, upload/download via web URL. Discovery triggers, available endpoints/paths, fields, required/updatable properties, request body, operation parameters, schema/data model. When in doubt about workplace context, try WorkIQ first. Prefer `ask` for synthesis; use entity tools for exact reads/writes. +description: WorkIQ tools for Microsoft 365 workplace data and actions. Use for email, calendar events and meetings, files, SharePoint, OneDrive, Teams, people, Planner, and other M365 requests. Triggers include cancel meeting or event, accept or decline meetings, create or update events, create an upload session or replace an existing OneDrive file, find or summarize workplace content, send or reply to mail, manage files or tasks, and discover M365 paths or schemas. Prefer `ask` for synthesis and structured entity tools for exact reads and writes. compatibility: > Uses the hosted WorkIQ MCP endpoint. No local package is required for MCP tool calls. @@ -55,8 +55,18 @@ See [Resolving tool names in your host](#resolving-tool-names-in-your-host) belo | Get a personal contact by name | "Get the contact card for Morgan Avery" | `fetch` (`/me/contacts?$filter=...`) — subject to server policy | | List or manage Outlook categories | "What Outlook categories do I have?" | `fetch` (`/me/outlook/masterCategories`); writes subject to server policy | | Org chart / direct reports / manager lookup | "Who are Rob's direct reports?" | `fetch` (`/users/{id}/directReports`) | -| What's new/changed/removed since a point in time | "What's new in my Inbox since this morning?", "What's changed on my calendar since yesterday?", "What's been added to my contacts recently?" | `call_function` (delta — `/me/mailFolders/inbox/messages/delta`, `/me/calendarView/delta?...`, `/me/contacts/delta`). **Never call delta via `fetch`** — see `references/call-function-work-iq.md` | +| Signed-in user's profile photo metadata | "Show my profile photo dimensions and content type" | `fetch` `/me?$select=id`, then `fetch` `/users/{id}/photo?$select=id,width,height`. Do not use the policy-denied `/me/photo` alias, request `/$value`, or put `@odata.mediaContentType` in `$select`; read the media content type annotation returned with the metadata. | +| Finding a 30-minute slot for the whole team | "Find a 30-min slot when the whole team is free this week" | Do not use `ask`. Resolve `/me`, `/me/manager`, and the manager's `/users/{managerId}/directReports` with at most two `fetch` calls, then call `do_action` `/me/calendar/getSchedule` exactly once with all schedulable addresses and `AvailabilityViewInterval: 30`. Compute the earliest common working-hours slot from that response; skip `search_paths`, `get_schema`, `findMeetingTimes`, and a second verification action. | +| What's new/changed/removed since a point in time | "What's new in my Inbox since this morning?", "What's changed on my calendar since yesterday?", "What's been added to my contacts recently?" | `call_function` (delta — `/me/mailFolders/inbox/messages/delta`, `/me/calendarView/delta?...`, `/me/contacts/delta`, `/teams/{teamId}/channels/{channelId}/messages/delta`). **Never call delta via `fetch`** — see `references/call-function-work-iq.md` | | Sending mail, accepting/declining meetings | "Send this draft", "Accept the 2pm meeting" | `do_action` | +| Tentatively accepting a meeting by title | "Mark the Office hours sync as tentative" | `fetch` the exact event ID, then `do_action` `/me/events/{id}/tentativelyAccept` with `{"sendResponse":false}`. Do not include an empty `comment`; do not call `get_schema` for this known contract. | +| Declining a meeting by title without a response message | "Decline the upcoming Daily standup invite" | `fetch` the exact event ID, then `do_action` `/me/events/{id}/decline` with `{"sendResponse":false}`. Omit `comment`; do not call `get_schema` or retry alternate payloads. | +| Cancelling an organizer-owned meeting by title | "Cancel the Friday staff meeting I organized" | `fetch` the exact event ID, then `do_action` `/me/events/{id}/cancel` with `{"Comment":""}`. This is a known contract: do not call `search_paths` or `get_schema`. A `202` response confirms acceptance; do not fetch again solely to verify. | +| Creating an upload session for an existing OneDrive file | "Create an upload session to replace my file; do not upload content" | `call_function` once with `/me/drive/root/search(q='{urlEncodedExactName}')?$select=id,name,parentReference,file&$top=10` to resolve the exact driveItem and retain `parentReference.driveId` plus item `id`, then `do_action` `/drives/{driveId}/items/{itemId}/createUploadSession` with `{}`. This is a validated deployed contract: skip `search_paths` and `get_schema`, do not add an `item` wrapper, and do not upload file content. | +| Copying a named OneDrive file to a named folder | "Copy Q3 plan.txt to Shared" | Use two `call_function` calls to `/me/drive/root/search(q='{urlEncodedExactName}')?$select=id,name,parentReference,file,folder&$top=10`, retain the source `parentReference.driveId`, then `do_action` `/drives/{driveId}/items/{sourceId}/copy` with `{"parentReference":{"driveId":"{driveId}","id":"{folderId}"}}`. Skip `search_paths`, `get_schema`, and verification fetches. | +| Renaming a OneDrive file | "Rename Draft.txt to Final.txt" | `call_function` once with `/me/drive/root/search(q='{urlEncodedExactName}')?$select=id,name,parentReference,file&$top=10` to resolve the exact driveItem and retain `parentReference.driveId` plus item `id`, then `update_entity` `/drives/{driveId}/items/{itemId}` with `{"name":"Final.txt"}`. Skip `search_paths` and `get_schema`; do not PATCH `/me/drive/items/{id}`. | +| Reading the first accessible SharePoint site's default drive or lists | "Show the first site's drive metadata", "List the first site's lists" | `fetch` `/sites?search=*&$select=id,displayName,name,webUrl&$top=1`, treat the first returned item as "first accessible", then `fetch` `/sites/{siteId}/drive` or `/sites/{siteId}/lists`. The parameter is `search=*`, **not** `$search=*`; do not use `ask`, guessed search terms, or an empty search. | +| Searching documents across SharePoint team sites | "List documents from SharePoint team sites" | `do_action` `/search/query` for `driveItem` documents, discard personal `-my.sharepoint.com` hits, then one batched `fetch` with `/sites?search={siteSlug}` to resolve site display names. Return exact file name, site display name, and `webUrl`; see `references/do-action-work-iq.md`. | | Creating a calendar event, draft, or task | "Create a calendar event Friday at 3pm" | `create_entity` | **DO NOT say "I don't have access to emails/meetings/messages"** - use WorkIQ instead! @@ -76,7 +86,7 @@ Follow the user's request through to completion. A discovery or read call **alon 1. **Path discovery** ("endpoint", "available operations", "what can I do with X") → `search_paths` first. Continue to the read/write tool if the prompt also asks to act. 2. **Schema inspection** ("schema", "data model", "fields", "what does X take") → `get_schema` first. Continue to the write/action tool if the prompt also asks to act. -3. **Exact entity read or mutation by title/name/channel/thread** → `fetch` to resolve the target's ID, then `update_entity` / `delete_entity` / `do_action`. Do not use `ask` to resolve exact titled events, messages, drafts, folders, Teams chats/channels, or threads. +3. **Exact entity read or mutation by title/name/channel/thread** → `fetch` to resolve the target's ID, then `update_entity` / `delete_entity` / `do_action`. Named OneDrive file search is the exception: use `call_function` `/me/drive/root/search(q='...')`. Do not use `ask` to resolve exact titled events, messages, drafts, folders, Teams chats/channels, or threads. 4. **Semantic summary/status/decisions** → `ask`. If the prompt then asks to draft, send, create, update, delete, forward, or react, continue with the mutation tool — the `ask` answer alone is incomplete. ### Resolve-then-act — concrete examples @@ -88,7 +98,11 @@ When the user asks to delete, update, send, forward, copy, move, or react to som | "Mark email as read" | `fetch` to find the message | `update_entity` `/me/messages/{id}` with `{"isRead": true}` | | "Forward email to X" | `fetch` to find the message | `do_action` `/me/messages/{id}/forward` | | "Send email to X" | — | `do_action` `/me/sendMail` | -| "Copy file to folder" | `fetch` to find file and target folder | `do_action` `/me/drive/items/{id}/copy` | +| "Cancel the X meeting I organized" | `fetch` to find the event and verify `isOrganizer` | `do_action` `/me/events/{id}/cancel` with `{"Comment":""}`; accept `202` as success without a verification fetch | +| "Create an upload session to replace existing file X" | `call_function` once with `/me/drive/root/search(q='{urlEncodedExactName}')?$select=id,name,parentReference,file&$top=10` to resolve the exact driveItem and retain `parentReference.driveId` plus item `id` | `do_action` `/drives/{driveId}/items/{itemId}/createUploadSession` with `{}`; do not add `item`, inspect schema, or upload bytes | +| "Copy file to folder" | Two `call_function` calls to `/me/drive/root/search(q='{urlEncodedExactName}')?$select=id,name,parentReference,file,folder&$top=10`, one for the exact source and one for the exact folder | `do_action` `/drives/{driveId}/items/{sourceId}/copy` with `{"parentReference":{"driveId":"{driveId}","id":"{folderId}"}}`; skip `search_paths`, `get_schema`, and verification fetches | +| "Move file to folder" | Two `call_function` calls to `/me/drive/root/search(q='{urlEncodedExactName}')?$select=id,name,parentReference,file,folder&$top=10`, one for the exact source and one for the exact folder | `update_entity` `/drives/{driveId}/items/{sourceId}` with `{"parentReference":{"id":"{folderId}"}}`. This is an update, not a `/move` action; skip `search_paths`, `get_schema`, verification fetches, and `/move`. | +| "Rename file X to Y" | `call_function` once with `/me/drive/root/search(q='{urlEncodedExactName}')?$select=id,name,parentReference,file&$top=10` to resolve the exact driveItem and retain `parentReference.driveId` plus item `id` | `update_entity` `/drives/{driveId}/items/{itemId}` with `{"name":"Y"}`; skip `search_paths` and `get_schema`, and do not use `/me/drive/items/{id}` | | "Set presence to busy" | — | `do_action` `/me/presence/setUserPreferredPresence` — see `references/teams-work-iq.md` | | "React to Teams message" | `fetch` to find the message | `do_action` `/teams/{teamId}/channels/{channelId}/messages/{messageId}/setReaction` | | "Delete" any entity | `fetch` to find it | `delete_entity` on the entity URL | @@ -202,6 +216,13 @@ The primary tool. Ask any workplace question in plain English. This is an **agen { "question": "What did Rob say about the API design?" } ``` +For a one-shot follow-up or broad catch-up prompt, call `ask` once. If no +`conversationId` is available or Copilot cannot recover the earlier context, +report that limitation instead of rebuilding the conversation with broad +`search_paths`, `get_schema`, actions, or many entity calls. At most, make one +focused `fetch` for a concrete source URL/path returned by `ask`; do not loop +back into `ask` or enumerate sites and drives. + For detailed usage and examples, read `references/ask-work-iq.md`. --- @@ -229,7 +250,7 @@ Entity tools provide **fast, direct access to specific M365 data** via Work IQ A | Teams | `/me/chats`, `/chats/{chatId}/messages`, `/me/joinedTeams`, `/teams/{teamId}/channels/{channelId}/messages`, `/me/presence` | chats vs channels are different surfaces — see `references/teams-work-iq.md` | | People | `/me`, `/users/{id}`, `/users/{id}/directReports`, `/me/manager`, `/me/contacts` | profile, org, contacts — see directory-vs-contacts warning below | | Outlook categories | `/me/outlook/masterCategories` | list/get/create/update/delete — writes commonly policy-denied | -| Files | `/me/drive`, `/drives/{id}`, `/sites/{id}` | list/get JSON metadata only — binary content (file bytes, attachment payloads) is not released yet, see the deny rule below | +| Files | `/me/drive`, `/drives/{id}`, `/sites/{id}` | for a file named in the prompt, call `call_function` once with `/me/drive/root/search(q='{urlEncodedExactName}')` and select the requested metadata; answer from that result and do not follow with `/me/drive/items/{id}`. Binary content is not released, see the deny rule below | | Change tracking | `/me/mailFolders/inbox/messages/delta`, `/me/calendarView/delta?...`, `/me/contacts/delta` | "what's new/changed since" — via `call_function` only, never `fetch` | > **Server may deny families by policy.** Tenants can disable specific path families diff --git a/plugins/workiq-preview/skills/workiq-preview/references/call-function-work-iq.md b/plugins/workiq-preview/skills/workiq-preview/references/call-function-work-iq.md index 8773b1b..8457cfc 100644 --- a/plugins/workiq-preview/skills/workiq-preview/references/call-function-work-iq.md +++ b/plugins/workiq-preview/skills/workiq-preview/references/call-function-work-iq.md @@ -14,6 +14,7 @@ Call an OData function via HTTP GET. Functions are **side-effect-free** named op - When you need a computed result that takes no request body (`delta`, `reminderView`) - Any time the OData path uses function call syntax `functionName(param=value)` and the operation is documented as GET +- When resolving a OneDrive file by exact name with `/me/drive/root/search(q='...')` - **Any "what's new / what's changed / what was added or removed since X" question** — that is a delta query, and this tool is the only correct route for it @@ -48,3 +49,16 @@ and more. { "functionUrl": "/me/mailFolders/inbox/messages/delta" } ``` +### Get metadata for a named OneDrive file + +Use one function call. URL-encode the exact file name, select the metadata the +user needs, and answer directly from the matching driveItem. Do not call +`search_paths` or `get_schema`, and do not follow a successful search with +`/me/drive/items/{id}`. + +```json +{ + "functionUrl": "/me/drive/root/search(q='{urlEncodedExactName}')?$select=id,name,size,createdDateTime,lastModifiedDateTime,webUrl,file,folder,parentReference,createdBy,lastModifiedBy,fileSystemInfo,sharepointIds&$top=10" +} +``` + diff --git a/plugins/workiq-preview/skills/workiq-preview/references/do-action-work-iq.md b/plugins/workiq-preview/skills/workiq-preview/references/do-action-work-iq.md index 23b7dd0..47fdb3c 100644 --- a/plugins/workiq-preview/skills/workiq-preview/references/do-action-work-iq.md +++ b/plugins/workiq-preview/skills/workiq-preview/references/do-action-work-iq.md @@ -17,6 +17,7 @@ POST a WorkIQ action — a named operation that performs a task (send mail, copy - Send mail (vs. creating a draft) — `/me/sendMail`, `/me/messages/{id}/send` - Accept / decline / tentatively accept a meeting — `/me/events/{id}/{accept|decline|tentativelyAccept}` +- Cancel an organizer-owned meeting and notify attendees — `/me/events/{id}/cancel` - Copy or move a message — `/me/messages/{id}/{copy|move}` - Forward or reply — `/me/messages/{id}/{forward|reply}` - Compute free/busy across multiple users — `/me/calendar/getSchedule` @@ -58,6 +59,27 @@ Vs. `create_entity`: use `do_action` for verbs (send, copy, move, accept, reply, } ``` +### Copy a named OneDrive file to a named folder + +Resolve the exact source file and target folder with two `call_function` +searches. Retain the source item's `parentReference.driveId`, source `id`, and +target folder `id`. The deployed copy contract is drive-scoped; do not use the +policy-denied `/me/drive/items/{id}/copy` alias. This known contract does not +need `search_paths`, `get_schema`, or a verification fetch. A `202` response +confirms that the asynchronous copy was accepted. + +```json +{ + "actionUrl": "/drives/{driveId}/items/{sourceId}/copy", + "jsonBody": { + "parentReference": { + "driveId": "{driveId}", + "id": "{folderId}" + } + } +} +``` + ### Accept a meeting invitation ```json { @@ -66,6 +88,32 @@ Vs. `create_entity`: use `do_action` for verbs (send, copy, move, accept, reply, } ``` +### Tentatively accept a meeting invitation + +Resolve the titled event ID first, then use the known deployed contract below. +Do not call `get_schema`. When no response message is needed, omit `comment` +entirely: an empty comment with `sendResponse:false` is rejected. + +```json +{ + "actionUrl": "/me/events/{id}/tentativelyAccept", + "jsonBody": {"sendResponse": false} +} +``` + +### Cancel an organizer-owned meeting +Resolve the exact event ID and verify `isOrganizer` first. This request shape is +a known deployed contract, so do not call `search_paths` or `get_schema` first. +A `202` response confirms that cancellation was accepted; do not fetch the event +again solely to verify cancellation. + +```json +{ + "actionUrl": "/me/events/{id}/cancel", + "jsonBody": {"Comment": ""} +} +``` + ### Decline a meeting invitation ```json { @@ -74,6 +122,18 @@ Vs. `create_entity`: use `do_action` for verbs (send, copy, move, accept, reply, } ``` +When the user asks to decline by title without requesting a response message, +resolve the exact event ID first and use the known no-message contract below. +Omit `comment`: an empty comment with `sendResponse:false` is rejected. Do not +call `get_schema` or retry alternate payloads. + +```json +{ + "actionUrl": "/me/events/{id}/decline", + "jsonBody": {"sendResponse": false} +} +``` + ### Forward a message ```json { @@ -100,6 +160,76 @@ Vs. `create_entity`: use `do_action` for verbs (send, copy, move, accept, reply, `availabilityViewInterval` is optional minutes (default 30, min 5, max 1440). `schedules` is a string array of SMTP addresses (users, distribution lists, rooms, or equipment). +#### Find a 30-minute slot for my whole team + +This is a structured calendar calculation, not semantic synthesis. Do not call +`ask`, `search_paths`, `get_schema`, or `findMeetingTimes`. + +1. Resolve the roster with at most two `fetch` calls: + - Fetch `/me?$select=id,displayName,mail,userPrincipalName` and + `/me/manager?$select=id,displayName,mail,userPrincipalName` together. + - Fetch `/users/{managerId}/directReports?$select=id,displayName,mail,userPrincipalName`. + - Treat the manager plus those direct reports as the whole team. Keep one + non-empty `mail` or `userPrincipalName` per person and remove duplicates. +2. Call `/me/calendar/getSchedule` exactly once for the remaining working-time + window this week. Use `AvailabilityViewInterval: 30`. +3. Find the earliest working-hours interval whose corresponding availability + view is free for every returned schedule. Do not make a second action call + solely to verify the chosen interval. + +```json +{ + "actionUrl": "/me/calendar/getSchedule", + "jsonBody": { + "Schedules": ["manager@contoso.com", "member1@contoso.com"], + "StartTime": {"dateTime": "YYYY-MM-DDT09:00:00", "timeZone": "China Standard Time"}, + "EndTime": {"dateTime": "YYYY-MM-DDT17:00:00", "timeZone": "China Standard Time"}, + "AvailabilityViewInterval": 30 + } +} +``` + +Replace each `YYYY-MM-DD` with the current remaining-workweek boundary at +runtime; never reuse a literal date from this example. + +### Search documents across SharePoint team sites + +Use Microsoft Search for a bounded cross-site document query. This response can +contain personal OneDrive hits and does not provide team-site display names, so +discard resources whose `webUrl` host contains `-my.sharepoint.com`, derive each +remaining site slug from its team-site `webUrl`, then make one batched `fetch` +to `/sites?search={siteSlug}&$select=id,displayName,name,webUrl&$top=5` for the +unique slugs. Return at most five exact file names, resolved site display names, +and `webUrl` values. + +```json +{ + "actionUrl": "/search/query", + "jsonBody": { + "requests": [ + { + "entityTypes": ["driveItem"], + "query": {"queryString": "IsDocument:True"}, + "from": 0, + "size": 25, + "fields": [ + "name", + "webUrl", + "parentReference", + "sharepointIds", + "file", + "listItem", + "lastModifiedDateTime" + ] + } + ] + } +} +``` + +This is a known action contract. Do not call `ask`, `search_paths`, or +`get_schema` first, and do not fetch personal OneDrive results. + ### Set my Teams presence to Busy ```json { @@ -120,15 +250,32 @@ Use `setUserPreferredPresence` for user requests ("set me to Busy"). The `setPre For channel messages use the `/teams/{teamId}/channels/{channelId}/messages/{messageId}/setReaction` path. See `references/teams-work-iq.md` for chat-vs-channel resolution. -### Initiate a large file upload session +### Replace an existing file with an upload session +Resolve the existing driveItem with one `call_function` exact-name search and +retain both its `parentReference.driveId` and item `id`. Do not use `fetch` for +this named OneDrive search and do not follow the successful search with another +metadata read. The deployed action accepts an empty body for this operation. Do +not add an `item` wrapper: the current runtime can reject that otherwise +schema-valid optional field with `400 invalidRequest`. This contract is already +known, so skip `search_paths` and `get_schema`. + +```json +{ + "functionUrl": "/me/drive/root/search(q='{urlEncodedExactName}')?$select=id,name,parentReference,file&$top=10" +} +``` + ```json { - "actionUrl": "/me/drive/root:/Projects/big-file.zip:/createUploadSession", - "jsonBody": "{\"item\":{\"@microsoft.graph.conflictBehavior\":\"replace\"}}" + "actionUrl": "/drives/{driveId}/items/{itemId}/createUploadSession", + "jsonBody": {} } ``` -The response returns an `uploadUrl` you can PUT chunks to. **However, this skill does not expose a binary-upload tool** — see the deny rule in `SKILL.md`. Surface the `uploadUrl` to the user so they can complete the upload themselves; do not attempt to PUT bytes from inside the model. +The response returns an `uploadUrl` for a later chunk upload. **However, this +skill does not expose a binary-upload tool** — see the deny rule in `SKILL.md`. +When the user only asks to create the session, report the session metadata and +stop; do not upload file content. ## Common failures (do not retry) diff --git a/plugins/workiq-preview/skills/workiq-preview/references/fetch-work-iq.md b/plugins/workiq-preview/skills/workiq-preview/references/fetch-work-iq.md index b558498..6c433ca 100644 --- a/plugins/workiq-preview/skills/workiq-preview/references/fetch-work-iq.md +++ b/plugins/workiq-preview/skills/workiq-preview/references/fetch-work-iq.md @@ -25,6 +25,11 @@ For exact reads ("show/list/get latest messages", "list members", "show my chats > fails — delta is an OData **function** and must go through `call_function`. See > `references/call-function-work-iq.md`. +> **Named OneDrive search is also a function.** For a file identified by exact +> name, call `/me/drive/root/search(q='...')` through `call_function` once and +> answer from that result. Do not follow it with `/me/drive/items/{id}` merely +> to retrieve the same metadata again. + ## Multi-fetch caveats - The batch result can report an error when **any one** URL fails, even if the other URLs @@ -108,6 +113,25 @@ Never fabricate base64 content, `@odata.mediaContentType`, or an `@microsoft.gra { "entityUrls": ["/me"] } ``` +### Get the signed-in user's profile photo metadata + +Resolve the signed-in user's id, then read the photo through the exposed +user-id path. Do not call the policy-denied `/me/photo` alias, and do not call +`/$value`, which is binary content. + +```json +{ "entityUrls": ["/me?$select=id"] } +``` + +```json +{ "entityUrls": ["/users/{id}/photo?$select=id,width,height"] } +``` + +Do not put `@odata.mediaContentType` or `@odata.type` in `$select`; Graph rejects +those annotations in a select expression. Read the media content type annotation +from the metadata response when a profile photo exists. A `404 ImageNotFound` +means the selected user currently has no profile photo. + ### Get unread emails (top 10) ```json { "entityUrls": ["/me/messages?$top=10&$filter=isRead%20eq%20false&$select=subject,from,receivedDateTime"] } @@ -133,6 +157,39 @@ Never fabricate base64 content, `@odata.mediaContentType`, or an `@microsoft.gra { "entityUrls": ["/me/drive/root/children?$select=name,size,lastModifiedDateTime"] } ``` +### Get the first accessible SharePoint site's default drive or lists + +For prompts that say "the first SharePoint site I can access," use the first +item returned by the exact site search below. Microsoft Graph's site collection +uses the `search` parameter without a `$` prefix. Do not try `$search=*`, an +empty search, guessed terms, or `ask`. + +```json +{ + "entityUrls": [ + "/sites?search=*&$select=id,displayName,name,webUrl&$top=1" + ] +} +``` + +Then use the returned site `id` in exactly one of these reads: + +```json +{ + "entityUrls": [ + "/sites/{siteId}/drive?$select=id,name,driveType,owner,quota,webUrl,createdDateTime,lastModifiedDateTime,description,system" + ] +} +``` + +```json +{ + "entityUrls": [ + "/sites/{siteId}/lists?$select=id,displayName,name,webUrl&$top=200" + ] +} +``` + ### Get Teams channels for a group ```json { "entityUrls": ["/teams/{teamId}/channels"] } diff --git a/plugins/workiq-preview/skills/workiq-preview/references/mail-work-iq.md b/plugins/workiq-preview/skills/workiq-preview/references/mail-work-iq.md index 35725cd..46b7061 100644 --- a/plugins/workiq-preview/skills/workiq-preview/references/mail-work-iq.md +++ b/plugins/workiq-preview/skills/workiq-preview/references/mail-work-iq.md @@ -5,19 +5,19 @@ drafting/sending/replying/forwarding, marking read, copying/moving, and deleting for synthesis questions ("summarize the deadline thread with John"), not for finding, listing, or mutating individual messages. -## Mail delta: prefer `/me/messages/delta` for full-mailbox sync +## Mail delta: use `/me/mailFolders/{id}/messages/delta` (folder-scoped) -For "sync my mail", "fetch the mail delta", or "give me mail changes" with **no folder named**, -route `call_function` to `/me/messages/delta` — full mailbox in one cursor. -`/me/mailFolders/{folderId}/delta` (e.g. `/me/mailFolders/inbox/delta`) is folder-scoped; use it -only when the user names a folder. +Message delta is **always folder-scoped** — there is **no** tenant-wide `/me/messages/delta` +endpoint. For "sync my mail", "fetch the mail delta", or "give me mail changes" with **no folder +named**, default to the inbox cursor `/me/mailFolders/inbox/messages/delta`. When the user names a +folder, target that folder's messages delta, e.g. `/me/mailFolders/{folderId}/messages/delta`. Paginate `@odata.nextLink` until you reach `@odata.deltaLink` (resume token for the next sync) — stopping at the first page is wrong. > **Always `call_function`, never `fetch`.** `delta` is an OData function. Calling -> `/me/messages/delta` through `fetch` returns an `InvalidRequest` or wrong shape; route through -> `call_function` with the function URL. +> `/me/mailFolders/inbox/messages/delta` through `fetch` returns an `InvalidRequest` or wrong +> shape; route through `call_function` with the function URL. ## Finding a message by subject — use `$search`, not `$filter=contains` @@ -58,8 +58,8 @@ folder names are exact-match by design. Use it for `rename` / `move` / `delete` | Permanently delete (bypasses Deleted Items) | `do_action` | `/me/messages/{id}/permanentDelete` | | List folders | `fetch` | `/me/mailFolders` | | Find a folder by name | `fetch` | `/me/mailFolders?$filter=displayName eq 'Specs'` | -| Mail delta (no folder) | `call_function` | `/me/messages/delta` | -| Mail delta (folder-scoped) | `call_function` | `/me/mailFolders/inbox/messages/delta` | +| Mail delta (default / no folder named) | `call_function` | `/me/mailFolders/inbox/messages/delta` | +| Mail delta (specific folder) | `call_function` | `/me/mailFolders/{folderId}/messages/delta` | ## "Draft" vs "send" — pick the right verb diff --git a/plugins/workiq-preview/skills/workiq-preview/references/update-entity-work-iq.md b/plugins/workiq-preview/skills/workiq-preview/references/update-entity-work-iq.md index e723ccb..30ab998 100644 --- a/plugins/workiq-preview/skills/workiq-preview/references/update-entity-work-iq.md +++ b/plugins/workiq-preview/skills/workiq-preview/references/update-entity-work-iq.md @@ -16,6 +16,7 @@ PATCH an existing WorkIQ entity. Only fields in the body are changed; other fiel - Update event subject, time, location - Change task status or due date - Update document metadata +- Move a OneDrive driveItem by changing `parentReference` - Any partial update to an existing M365 entity ## Gotchas @@ -49,6 +50,53 @@ PATCH an existing WorkIQ entity. Only fields in the body are changed; other fiel } ``` +### Rename a OneDrive file + +This is a known drive-scoped update contract. Do not call `search_paths` or +`get_schema`, and do not PATCH `/me/drive/items/{id}` because that alias is not +exposed for update in the deployed WorkIQ policy. + +1. Resolve the exact filename with one `call_function` call: + - `/me/drive/root/search(q='{urlEncodedExactName}')?$select=id,name,parentReference,file&$top=10` +2. Retain the result's `id` and `parentReference.driveId`. +3. Rename it with `update_entity`: + +```json +{ + "entityUrl": "/drives/{driveId}/items/{itemId}", + "jsonBody": {"name": "Final filename.txt"} +} +``` + +Use the complete bounded lookup URL above, including `$top=10`. Stop after the +successful PATCH because its response contains the renamed driveItem. The +workflow is exactly `call_function` then `update_entity`. + +### Move a OneDrive file into a folder + +This is a known update contract, not a `/move` action. Do not call +`search_paths` or `get_schema` for it. + +1. Resolve both names with two `call_function` calls: + - `/me/drive/root/search(q='{urlEncodedSourceName}')?$select=id,name,parentReference,file,folder&$top=10` + - `/me/drive/root/search(q='{urlEncodedFolderName}')?$select=id,name,parentReference,file,folder&$top=10` +2. Confirm the source result has a `file` facet and the target has a `folder` + facet. Keep the source `parentReference.driveId`, source `id`, and target + `id`. Do not put `eTag` or `@odata.etag` in `$select`; Graph rejects those + terms on drive search. +3. Move the source with `update_entity`: + +```json +{ + "entityUrl": "/drives/{driveId}/items/{sourceId}", + "jsonBody": {"parentReference": {"id": "{folderId}"}} +} +``` + +Do not fetch again solely to obtain an etag or verify the move. A successful +drive-scoped PATCH response is sufficient unless the user explicitly requests +verification. + ### Update a Planner task's due date ```json {