diff --git a/extensions/discord/skills/discord/SKILL.md b/extensions/discord/skills/discord/SKILL.md index 10186a04d993..7e738d44b5fd 100644 --- a/extensions/discord/skills/discord/SKILL.md +++ b/extensions/discord/skills/discord/SKILL.md @@ -1,136 +1,43 @@ --- name: discord -description: "Discord message-tool ops: send/read/edit/delete, react, poll, pin, thread, search, presence, media/components." +description: "Discord messaging workflows through OpenClaw's message tool." metadata: { "openclaw": { "emoji": "🎮", "requires": { "config": ["channels.discord.token"] } } } allowed-tools: ["message"] --- # Discord -Use the `message` tool with `channel: "discord"`. No separate Discord tool. +Use the `message` tool with `channel: "discord"`. The tool schema lists the actions enabled by the current account's `channels.discord.actions.*` gates; do not assume unavailable actions. -## Rules +## Workflow -- Respect `channels.discord.actions.*` gates. -- Prefer explicit `guildId`, `channelId`, `messageId`, `userId`. -- Multi-account: pass `accountId` when needed. -- Send targets: `to: "channel:"` or `to: "user:"`. -- Mention users as `<@USER_ID>`. -- Avoid Markdown tables in outbound Discord messages. -- Prefer components v2 for rich UI; do not mix v2 `components` with legacy `embeds`. +- Prefer stable `guildId`, `channelId`, `messageId`, and `userId` values from context. Pass `accountId` when more than one Discord account could apply. +- Resolve the exact message before editing, deleting, pinning, moderating, or reacting when the user's reference is ambiguous. +- Keep thread replies in their existing thread. A forum parent cannot receive components; send components to the created forum thread instead. +- Confirm destructive or moderation actions unless the user already specified the exact target and action. -## Common actions +## Interactive components -Send: - -```json -{ "action": "send", "channel": "discord", "to": "channel:123", "message": "hello", "silent": true } -``` - -Send media: +`components` must be a structured object or native component array, never a placeholder string. Do not combine components v2 with legacy `embeds`. ```json { "action": "send", "channel": "discord", "to": "channel:123", - "message": "see attachment", - "media": "file:///tmp/example.png" + "message": "Choose an option", + "components": { + "blocks": [ + { + "type": "actions", + "buttons": [ + { "label": "Approve", "style": "success", "callbackData": "approve" }, + { "label": "Decline", "style": "danger", "callbackData": "decline" } + ] + } + ] + } } ``` -Components v2: - -```json -{ - "action": "send", - "channel": "discord", - "to": "channel:123", - "message": "Status", - "components": "[Carbon v2 components]" -} -``` - -React: - -```json -{ "action": "react", "channel": "discord", "channelId": "123", "messageId": "456", "emoji": "👍" } -``` - -Read: - -```json -{ "action": "read", "channel": "discord", "to": "channel:123", "limit": 20 } -``` - -Edit/delete: - -```json -{ - "action": "edit", - "channel": "discord", - "channelId": "123", - "messageId": "456", - "message": "fixed typo" -} -``` - -```json -{ "action": "delete", "channel": "discord", "channelId": "123", "messageId": "456" } -``` - -Poll: - -```json -{ - "action": "poll", - "channel": "discord", - "to": "channel:123", - "pollQuestion": "Lunch?", - "pollOption": ["Pizza", "Sushi"], - "pollDurationHours": 24 -} -``` - -Pin: - -```json -{ "action": "pin", "channel": "discord", "channelId": "123", "messageId": "456" } -``` - -Thread: - -```json -{ - "action": "thread-create", - "channel": "discord", - "channelId": "123", - "messageId": "456", - "threadName": "bug triage" -} -``` - -Search: - -```json -{ - "action": "search", - "channel": "discord", - "guildId": "999", - "query": "release notes", - "channelIds": ["123"], - "limit": 10 -} -``` - -Presence, often gated: - -```json -{ - "action": "set-presence", - "channel": "discord", - "activityType": "playing", - "activityName": "OpenClaw", - "status": "online" -} -``` +Discord mention syntax, component availability, and form hints are injected automatically. Follow the current hints and tool schema rather than a duplicated action catalog. diff --git a/extensions/feishu/skills/feishu-doc/SKILL.md b/extensions/feishu/skills/feishu-doc/SKILL.md index db80fa8622a6..fab24d79699c 100644 --- a/extensions/feishu/skills/feishu-doc/SKILL.md +++ b/extensions/feishu/skills/feishu-doc/SKILL.md @@ -1,215 +1,40 @@ --- name: feishu-doc description: | - Feishu document read/write operations. Activate when user mentions Feishu docs, cloud docs, or docx links. + Feishu document read/write workflows. Activate when the user mentions Feishu docs, cloud docs, or docx links. --- -# Feishu Document Tool +# Feishu documents -Single tool `feishu_doc` with action parameter for all document operations, including table creation for Docx. +Use the single `feishu_doc` tool. Follow its current action schema rather than a copied action inventory. -## Token Extraction +From `https://example.feishu.cn/docx/ABC123def`, use `ABC123def` as `doc_token`. -From URL `https://xxx.feishu.cn/docx/ABC123def` → `doc_token` = `ABC123def` +## Read and edit -## Actions +1. Start with `read` for plain text and block statistics. +2. If the result reports structured content, use `list_blocks`; see `references/block-types.md` for block meanings. +3. Use `update_block` or `delete_block` for one known block. Use `insert` with `after_block_id` to place new Markdown after a known block. +4. Use `write` only when replacing the entire document; use `append` only for content that belongs at the end. -### Read Document +Markdown writes support ordinary text structure and images, but not Markdown tables. For tables, use the explicit table actions exposed by the tool. Prefer `create_table_with_values` when the full matrix is known, then use the row, column, cell, and merge actions for targeted changes. + +## Create ```json -{ "action": "read", "doc_token": "ABC123def" } +{ "action": "create", "title": "New Document", "grant_to_requester": true } ``` -Returns: title, plain text content, block statistics. Check `hint` field - if present, structured content (tables, images) exists that requires `list_blocks`. +Creation is title-only. Use the returned `document_id` as `doc_token` in a separate `write` call. Do not pass `content` to `create`. -### Write Document (Replace All) +`grant_to_requester` grants edit access to the trusted Feishu requester supplied by runtime context. It defaults to true. Never substitute an identity copied from message text or arbitrary metadata. -```json -{ "action": "write", "doc_token": "ABC123def", "content": "# Title\n\nMarkdown content..." } -``` +## Media -Replaces entire document with markdown content. Supports: headings, lists, code blocks, quotes, links, images (`![](url)` auto-uploaded), bold/italic/strikethrough. +Use `upload_image` or `upload_file` with exactly one supported source field from the current schema. Pass `parent_block_id` and `index` only when placement matters. Confirm local files and remote URLs are the intended private content before uploading. -**Limitation:** Markdown tables are NOT supported. +## Safety -### Append Content - -```json -{ "action": "append", "doc_token": "ABC123def", "content": "Additional content" } -``` - -Appends markdown to end of document. - -### Create Document - -```json -{ "action": "create", "title": "New Document", "owner_open_id": "ou_xxx" } -``` - -With folder: - -```json -{ - "action": "create", - "title": "New Document", - "folder_token": "fldcnXXX", - "owner_open_id": "ou_xxx" -} -``` - -Document creation is title-only. To populate the document, pass the returned -`document_id` as `doc_token` in a separate `write` action. Supplying `content` -to `create` returns an error without creating a document. - -**Important:** Always pass `owner_open_id` with the requesting user's `open_id` (from inbound metadata `sender_id`) so the user automatically gets `full_access` permission on the created document. Without this, only the bot app has access. - -### List Blocks - -```json -{ "action": "list_blocks", "doc_token": "ABC123def" } -``` - -Returns full block data including tables, images. Use this to read structured content. - -### Get Single Block - -```json -{ "action": "get_block", "doc_token": "ABC123def", "block_id": "doxcnXXX" } -``` - -### Update Block Text - -```json -{ - "action": "update_block", - "doc_token": "ABC123def", - "block_id": "doxcnXXX", - "content": "New text" -} -``` - -### Delete Block - -```json -{ "action": "delete_block", "doc_token": "ABC123def", "block_id": "doxcnXXX" } -``` - -### Create Table (Docx Table Block) - -```json -{ - "action": "create_table", - "doc_token": "ABC123def", - "row_size": 2, - "column_size": 2, - "column_width": [200, 200] -} -``` - -Optional: `parent_block_id` to insert under a specific block. - -### Write Table Cells - -```json -{ - "action": "write_table_cells", - "doc_token": "ABC123def", - "table_block_id": "doxcnTABLE", - "values": [ - ["A1", "B1"], - ["A2", "B2"] - ] -} -``` - -### Create Table With Values (One-step) - -```json -{ - "action": "create_table_with_values", - "doc_token": "ABC123def", - "row_size": 2, - "column_size": 2, - "column_width": [200, 200], - "values": [ - ["A1", "B1"], - ["A2", "B2"] - ] -} -``` - -Optional: `parent_block_id` to insert under a specific block. - -### Upload Image to Docx (from URL or local file) - -```json -{ - "action": "upload_image", - "doc_token": "ABC123def", - "url": "https://example.com/image.png" -} -``` - -Or local path with position control: - -```json -{ - "action": "upload_image", - "doc_token": "ABC123def", - "file_path": "/tmp/image.png", - "parent_block_id": "doxcnParent", - "index": 5 -} -``` - -Optional `index` (0-based) inserts the image at a specific position among sibling blocks. Omit to append at end. - -**Note:** Image display size is determined by the uploaded image's pixel dimensions. For small images (e.g. 480x270 GIFs), scale to 800px+ width before uploading to ensure proper display. - -### Upload File Attachment to Docx (from URL or local file) - -```json -{ - "action": "upload_file", - "doc_token": "ABC123def", - "url": "https://example.com/report.pdf" -} -``` - -Or local path: - -```json -{ - "action": "upload_file", - "doc_token": "ABC123def", - "file_path": "/tmp/report.pdf", - "filename": "Q1-report.pdf" -} -``` - -Rules: - -- exactly one of `url` / `file_path` -- optional `filename` override -- optional `parent_block_id` - -## Reading Workflow - -1. Start with `action: "read"` - get plain text + statistics -2. Check `block_types` in response for Table, Image, Code, etc. -3. If structured content exists, use `action: "list_blocks"` for full data - -## Configuration - -```yaml -channels: - feishu: - tools: - doc: true # default: true -``` - -**Note:** `feishu_wiki` depends on this tool - wiki page content is read/written via `feishu_doc`. - -## Permissions - -Required: `docx:document`, `docx:document:readonly`, `docx:document.block:convert`, `drive:drive` +- Resolve exact document and block IDs before destructive edits. +- Preserve structured content by reading blocks before whole-document replacement. +- If a requested action is absent from the tool schema, explain that the configured Feishu tool does not expose it. diff --git a/extensions/feishu/skills/feishu-doc/references/block-types.md b/extensions/feishu/skills/feishu-doc/references/block-types.md index 8ce599fe869a..97b9d8a17d84 100644 --- a/extensions/feishu/skills/feishu-doc/references/block-types.md +++ b/extensions/feishu/skills/feishu-doc/references/block-types.md @@ -1,6 +1,6 @@ # Feishu Block Types Reference -Complete reference for Feishu document block types. Use with `feishu_doc_list_blocks`, `feishu_doc_update_block`, and `feishu_doc_delete_block`. +Complete reference for Feishu document block types. Use the single `feishu_doc` tool with its `list_blocks`, `update_block`, and `delete_block` actions. ## Block Type Table @@ -59,10 +59,11 @@ Complete reference for Feishu document block types. Use with `feishu_doc_list_bl ### Text-based blocks (2-17, 19) -Update text content using `feishu_doc_update_block`: +Update text content with `feishu_doc`: ```json { + "action": "update_block", "doc_token": "ABC123", "block_id": "block_xxx", "content": "New text content" @@ -71,13 +72,11 @@ Update text content using `feishu_doc_update_block`: ### Image blocks (27) -Images cannot be updated directly via `update_block`. Use `feishu_doc_write` or `feishu_doc_append` with markdown to add new images. +Images cannot be updated directly via `update_block`. Use `upload_image` to add a replacement image, then delete the old block only after the new upload succeeds. ### Table blocks (31) -**Important:** Table blocks CANNOT be created via the `documentBlockChildren.create` API (error 1770029). This affects `feishu_doc_write` and `feishu_doc_append` - markdown tables will be skipped with a warning. - -Tables can only be read (via `list_blocks`) and individual cells (type 32) can be updated, but new tables cannot be inserted programmatically via markdown. +Markdown tables are not converted by `write`, `append`, or `insert`. Create tables with `create_table` or `create_table_with_values`; use the table row, column, cell, and merge actions exposed by the current schema for later edits. ### Container blocks (24, 25, 35) @@ -87,17 +86,13 @@ Grid and QuoteContainer are layout containers. Edit their child blocks instead. ### Replace specific paragraph -1. `feishu_doc_list_blocks` - find the block_id -2. `feishu_doc_update_block` - update its content +1. Call `feishu_doc` with `action: "list_blocks"` to find the block ID. +2. Call `feishu_doc` with `action: "update_block"` to replace its text. ### Insert content at specific location -Currently, the API only supports appending to document end. For insertion at specific positions, consider: - -1. Read existing content -2. Delete affected blocks -3. Rewrite with new content in desired order +Call `feishu_doc` with `action: "insert"`, the target `doc_token`, Markdown `content`, and the preceding block's ID as `after_block_id`. ### Delete multiple blocks -Blocks must be deleted one at a time. Delete child blocks before parent containers. +Call `delete_block` for exact block IDs. Delete child blocks before parent containers and confirm broad deletions first. diff --git a/extensions/feishu/skills/feishu-drive/SKILL.md b/extensions/feishu/skills/feishu-drive/SKILL.md index d8904f71a499..a93334376810 100644 --- a/extensions/feishu/skills/feishu-drive/SKILL.md +++ b/extensions/feishu/skills/feishu-drive/SKILL.md @@ -1,114 +1,28 @@ --- name: feishu-drive description: | - Feishu cloud storage file management. Activate when user mentions cloud space, folders, drive. + Feishu cloud-storage and comment workflows. Activate when the user mentions cloud space, folders, Drive files, or document comments. --- -# Feishu Drive Tool +# Feishu Drive -Single tool `feishu_drive` for cloud storage operations. +Use the single `feishu_drive` tool and its current action schema. -## Token Extraction +From `https://example.feishu.cn/drive/folder/ABC123`, use `ABC123` as `folder_token`. -From URL `https://xxx.feishu.cn/drive/folder/ABC123` → `folder_token` = `ABC123` +## Files and folders -## Actions +- Start from a folder shared with the bot. Bot credentials normally have no usable personal root folder. +- For paginated folder listings, keep the same `folder_token` and pass the returned `page_token` until no continuation token remains. +- Use `info` with the exact file token and type returned by Drive or wiki discovery. +- Resolve the exact source and destination before moving or deleting. Confirm destructive deletes when the user's intent or target is unclear. +- Create subfolders inside a shared folder; creating at the account root normally fails for bots. -### List Folder Contents +## Comments -```json -{ "action": "list" } -``` +- Use `list_comments`, then `list_comment_replies` with the exact `comment_id` to inspect a discussion. +- Use `add_comment` for a document-level comment. Include `block_id` only when the user wants a comment on one known Docx block. +- Use `reply_comment` for an existing comment thread. +- Preserve the file type and pagination fields returned by the tool. The schema is authoritative for which file types each comment action accepts. -Requests the account root (no `folder_token`). Bot credentials normally have no root folder, so -use a folder that has been shared with the bot instead. - -```json -{ "action": "list", "folder_token": "fldcnXXX", "page_size": 100 } -``` - -Returns one page of files with token, name, type, url, timestamps, and `next_page_token` when -another page is available. To continue, pass the returned token with the same folder token: - -```json -{ - "action": "list", - "folder_token": "fldcnXXX", - "page_size": 100, - "page_token": "next-page-token" -} -``` - -`page_size` must be between 1 and 200. Pagination requires a concrete shared `folder_token`; -root-list cursors are not forwarded. - -### Get File Info - -```json -{ "action": "info", "file_token": "ABC123", "type": "docx" } -``` - -Looks up file metadata directly by token and type, regardless of which shared folder contains it. -Shortcuts are the exception: Feishu's metadata API does not support the `shortcut` type, so shortcut -info retains the root-directory lookup behavior. - -`type`: `doc`, `docx`, `sheet`, `bitable`, `folder`, `file`, `mindnote`, `shortcut` - -### Create Folder - -```json -{ "action": "create_folder", "name": "New Folder" } -``` - -In parent folder: - -```json -{ "action": "create_folder", "name": "New Folder", "folder_token": "fldcnXXX" } -``` - -### Move File - -```json -{ "action": "move", "file_token": "ABC123", "type": "docx", "folder_token": "fldcnXXX" } -``` - -### Delete File - -```json -{ "action": "delete", "file_token": "ABC123", "type": "docx" } -``` - -## File Types - -| Type | Description | -| ---------- | ----------------------- | -| `doc` | Old format document | -| `docx` | New format document | -| `sheet` | Spreadsheet | -| `bitable` | Multi-dimensional table | -| `folder` | Folder | -| `file` | Uploaded file | -| `mindnote` | Mind map | -| `shortcut` | Shortcut | - -## Configuration - -```yaml -channels: - feishu: - tools: - drive: true # default: true -``` - -## Permissions - -- `drive:drive` - Full access (create, move, delete) -- `drive:drive:readonly` - Read only (list and root-level info fallback) -- `drive:drive.metadata:readonly` - Direct `info` lookup outside the root (not needed with `drive:drive`) - -## Known Limitations - -- **Bots have no root folder**: Feishu bots use `tenant_access_token` and don't have their own "My Space". The root folder concept only exists for user accounts. This means: - - `create_folder` without `folder_token` will fail (400 error) - - Bot can only access files/folders that have been **shared with it** - - **Workaround**: User must first create a folder manually and share it with the bot, then bot can create subfolders inside it +Only expose or forward file and comment contents needed for the user's request; shared Drive data may be private. diff --git a/extensions/feishu/skills/feishu-perm/SKILL.md b/extensions/feishu/skills/feishu-perm/SKILL.md index 1ce5db8b86c5..b7e60caaf4ae 100644 --- a/extensions/feishu/skills/feishu-perm/SKILL.md +++ b/extensions/feishu/skills/feishu-perm/SKILL.md @@ -1,119 +1,19 @@ --- name: feishu-perm description: | - Feishu permission management for documents and files. Activate when user mentions sharing, permissions, collaborators. + Feishu collaborator and permission workflows. Activate when the user explicitly asks to inspect or change sharing, permissions, or collaborators. --- -# Feishu Permission Tool +# Feishu permissions -Single tool `feishu_perm` for managing file/document permissions. +Use the single `feishu_perm` tool and its current action schema. This tool is disabled by default because it changes access to user data; if it is unavailable, explain that `channels.feishu.tools.perm` must be enabled. -## Actions +## Workflow -### List Collaborators +1. Resolve the exact file token and type. +2. Use `list` to inspect current collaborators before changing access. +3. For `add`, resolve the collaborator's exact identifier and choose the least permission that satisfies the request. +4. For `remove`, confirm the exact collaborator and file when the request is ambiguous or broad. +5. Report the resulting permission change without exposing unrelated collaborator data. -```json -{ "action": "list", "token": "ABC123", "type": "docx" } -``` - -Returns: members with member_type, member_id, perm, name. - -### Add Collaborator - -```json -{ - "action": "add", - "token": "ABC123", - "type": "docx", - "member_type": "email", - "member_id": "user@example.com", - "perm": "edit" -} -``` - -### Remove Collaborator - -```json -{ - "action": "remove", - "token": "ABC123", - "type": "docx", - "member_type": "email", - "member_id": "user@example.com" -} -``` - -## Token Types - -| Type | Description | -| ---------- | ----------------------- | -| `doc` | Old format document | -| `docx` | New format document | -| `sheet` | Spreadsheet | -| `bitable` | Multi-dimensional table | -| `folder` | Folder | -| `file` | Uploaded file | -| `wiki` | Wiki node | -| `mindnote` | Mind map | - -## Member Types - -| Type | Description | -| ------------------ | ------------------ | -| `email` | Email address | -| `openid` | User open_id | -| `userid` | User user_id | -| `unionid` | User union_id | -| `openchat` | Group chat open_id | -| `opendepartmentid` | Department open_id | - -## Permission Levels - -| Perm | Description | -| ------------- | ------------------------------------ | -| `view` | View only | -| `edit` | Can edit | -| `full_access` | Full access (can manage permissions) | - -## Examples - -Share document with email: - -```json -{ - "action": "add", - "token": "doxcnXXX", - "type": "docx", - "member_type": "email", - "member_id": "alice@company.com", - "perm": "edit" -} -``` - -Share folder with group: - -```json -{ - "action": "add", - "token": "fldcnXXX", - "type": "folder", - "member_type": "openchat", - "member_id": "oc_xxx", - "perm": "view" -} -``` - -## Configuration - -```yaml -channels: - feishu: - tools: - perm: true # default: false (disabled) -``` - -**Note:** This tool is disabled by default because permission management is a sensitive operation. Enable explicitly if needed. - -## Permissions - -Required: `drive:permission` +Never infer an email, user ID, department, or chat from a display name alone. Follow the current schema for supported member types, token types, and permission levels. diff --git a/extensions/feishu/skills/feishu-wiki/SKILL.md b/extensions/feishu/skills/feishu-wiki/SKILL.md index ad68d8bffa1a..c87259d1ff5b 100644 --- a/extensions/feishu/skills/feishu-wiki/SKILL.md +++ b/extensions/feishu/skills/feishu-wiki/SKILL.md @@ -1,122 +1,26 @@ --- name: feishu-wiki description: | - Feishu knowledge base navigation. Activate when user mentions knowledge base, wiki, or wiki links. + Feishu knowledge-base navigation workflows. Activate when the user mentions a knowledge base, wiki, or wiki link. --- -# Feishu Wiki Tool +# Feishu wiki -Single tool `feishu_wiki` for knowledge base operations. +Use the single `feishu_wiki` tool and its current action schema. -Wiki `space_id` values are opaque strings. Always keep them quoted in tool calls, even when they contain only digits; passing a long numeric-looking ID as a number can corrupt the suffix due to JavaScript number precision limits. +From `https://example.feishu.cn/wiki/ABC123def`, use `ABC123def` as `token`. Treat every `space_id` as an opaque quoted string, even when it contains only digits. -## Token Extraction +## Navigate -From URL `https://xxx.feishu.cn/wiki/ABC123def` → `token` = `ABC123def` +- Use `spaces` to enumerate accessible knowledge spaces and `nodes` for a space or parent node. +- Continue pagination with the returned `page_token` while `has_more` is true, keeping the same space and parent. +- Use `search` when the user provides a query but not an exact node. +- Use `get` to resolve a wiki token to its `node_token`, `obj_token`, and `obj_type`. -## Actions +## Create and organize -### List Knowledge Spaces +Wiki creation supports only `docx`, `sheet`, and `bitable`; `docx` is the default. Resolve an exact space and parent before create, move, or rename operations, and confirm ambiguous or destructive reorganizations. -```json -{ "action": "spaces" } -``` +## Wiki content workflow -Returns one page of accessible wiki spaces plus `has_more` and `page_token`. -Continue with the returned `page_token` while `has_more` is true: - -```json -{ "action": "spaces", "page_token": "next-page-token" } -``` - -### List Nodes - -```json -{ "action": "nodes", "space_id": "7xxx" } -``` - -With parent: - -```json -{ "action": "nodes", "space_id": "7xxx", "parent_node_token": "wikcnXXX" } -``` - -Returns one page of nodes plus `has_more` and `page_token`. Continue with the -same `space_id` and `parent_node_token`, adding the returned `page_token`, while -`has_more` is true. Both list actions accept optional `page_size` from 1 to 50. - -### Get Node Details - -```json -{ "action": "get", "token": "ABC123def" } -``` - -Returns: `node_token`, `obj_token`, `obj_type`, etc. Use `obj_token` with `feishu_doc` to read/write the document. - -### Create Node - -```json -{ "action": "create", "space_id": "7xxx", "title": "New Page" } -``` - -With type and parent: - -```json -{ - "action": "create", - "space_id": "7xxx", - "title": "Sheet", - "obj_type": "sheet", - "parent_node_token": "wikcnXXX" -} -``` - -`obj_type`: `docx` (default), `sheet`, `bitable`, `mindnote`, `file`, `doc`, `slides` - -### Move Node - -```json -{ "action": "move", "space_id": "7xxx", "node_token": "wikcnXXX" } -``` - -To different location: - -```json -{ - "action": "move", - "space_id": "7xxx", - "node_token": "wikcnXXX", - "target_space_id": "7yyy", - "target_parent_token": "wikcnYYY" -} -``` - -### Rename Node - -```json -{ "action": "rename", "space_id": "7xxx", "node_token": "wikcnXXX", "title": "New Title" } -``` - -## Wiki-Doc Workflow - -To edit a wiki page: - -1. Get node: `{ "action": "get", "token": "wiki_token" }` → returns `obj_token` -2. Read doc: `feishu_doc { "action": "read", "doc_token": "obj_token" }` -3. Write doc: `feishu_doc { "action": "write", "doc_token": "obj_token", "content": "..." }` - -## Configuration - -```yaml -channels: - feishu: - tools: - wiki: true # default: true - doc: true # required - wiki content uses feishu_doc -``` - -**Dependency:** This tool requires `feishu_doc` to be enabled. Wiki pages are documents - use `feishu_wiki` to navigate, then `feishu_doc` to read/edit content. - -## Permissions - -Required: `wiki:wiki` or `wiki:wiki:readonly` +Wiki navigation is independent of the document tool. When `get` returns a `docx` object, use its `obj_token` as `doc_token` with `feishu_doc` to read or edit the page. Other object types require a currently available tool that supports that type; do not treat them as documents. diff --git a/extensions/imessage/skills/imsg/SKILL.md b/extensions/imessage/skills/imsg/SKILL.md index dc4b5ca72f5a..516f433746ae 100644 --- a/extensions/imessage/skills/imsg/SKILL.md +++ b/extensions/imessage/skills/imsg/SKILL.md @@ -25,7 +25,7 @@ metadata: # imsg -Use `imsg` when an OpenClaw agent must act through the user's local macOS Messages.app account: inspect iMessage/SMS history, choose the correct DM or group, send messages/files, reply, react, vote in polls, or use private-API iMessage features. +Use the generic `message` tool first for actions that its current iMessage schema exposes. Use `imsg` when the task needs local Messages.app history, target discovery, watching, or an administrative/private-API capability that is not exposed by `message`. Do not use this skill for Telegram, Signal, WhatsApp, Discord, Slack, or for replying inside the current OpenClaw conversation when the configured channel already routes the reply. @@ -33,7 +33,7 @@ Do not use this skill for Telegram, Signal, WhatsApp, Discord, Slack, or for rep 1. Resolve the conversation first. 2. Choose DM, existing group, or new group. -3. Pick the lowest-capability command that can do the requested action. +3. Prefer an available `message` action; otherwise pick the lowest-capability `imsg` command that preserves the requested semantics. 4. Confirm any send or visible state change unless the user already gave exact recipient, content, and action. 5. Execute with stable identifiers: prefer `--chat-id` for normal sends/watch/history and `--chat` chat GUID for bridge actions. @@ -74,7 +74,7 @@ Do not make `jq` a hard prerequisite for the skill; it is only a convenient form ## Capability Choice -Use standard commands for reads, target resolution, and plain sends: +Use `imsg` standard commands when the current `message` schema does not cover the operation or when local history is needed: - Read/list/search/watch: `chats`, `group`, `history`, `search`, `watch` - Basic text/file send: `send` diff --git a/extensions/qqbot/skills/qqbot-remind/SKILL.md b/extensions/qqbot/skills/qqbot-remind/SKILL.md index 6f8d07734bdd..f738140a5813 100644 --- a/extensions/qqbot/skills/qqbot-remind/SKILL.md +++ b/extensions/qqbot/skills/qqbot-remind/SKILL.md @@ -36,81 +36,7 @@ metadata: { "openclaw": { "emoji": "⏰", "requires": { "config": ["channels.qqb 1. 调用 `qqbot_remind`:`{ "action": "add", "content": "喝水", "time": "5m" }` 2. 工具返回成功后,回复用户:`⏰ 好的,5分钟后提醒你喝水~` ---- - -## 备用方案(直接使用 `cron` 工具) - -> 仅当 `qqbot_remind` 工具不可用但 `cron` 工具可用时使用以下方式。 - -### 核心规则 - -> **payload.kind 必须是 `"agentTurn"`,绝对不能用 `"systemEvent"`!** -> `systemEvent` 只在 AI 会话内部注入文本,用户收不到 QQ 消息。 - -**不可更改字段**: - -| 字段 | 固定值 | 原因 | -| -------------------- | ------------- | ---------------------------- | -| `payload.kind` | `"agentTurn"` | `systemEvent` 不会发 QQ 消息 | -| `delivery.mode` | `"announce"` | 主动投递模式 | -| `delivery.channel` | `"qqbot"` | QQ 通道标识 | -| `delivery.to` | 目标地址 | 从当前会话上下文获取 | -| `delivery.accountId` | 当前账户 ID | 多账号场景下不可省略 | -| `sessionTarget` | `"isolated"` | 隔离会话避免污染 | - -> `schedule.at` 必须是**绝对 ISO 8601 时间戳**(如 `"2026-07-29T09:00:00.000Z"`),不支持 `"5m"` 等相对字符串。 -> 计算方式:`当前时间戳ms + 延迟毫秒`。 - -### 一次性提醒(schedule.kind = "at") - -```json -{ - "action": "add", - "job": { - "name": "{任务名}", - "schedule": { "kind": "at", "at": "{当前时间戳ms + N*60000 转换为 ISO 8601}" }, - "sessionTarget": "isolated", - "wakeMode": "now", - "deleteAfterRun": true, - "payload": { - "kind": "agentTurn", - "message": "你是一个暖心的提醒助手。请用温暖、有趣的方式提醒用户:{提醒内容}。要求:(1) 不要回复HEARTBEAT_OK (2) 不要解释你是谁 (3) 直接输出一条暖心的提醒消息 (4) 可以加一句简短的鸡汤或关怀的话 (5) 控制在2-3句话以内 (6) 用emoji点缀" - }, - "delivery": { - "mode": "announce", - "channel": "qqbot", - "to": "qqbot:c2c:{openid}", - "accountId": "{accountId}" - } - } -} -``` - -### 周期提醒(schedule.kind = "cron") - -```json -{ - "action": "add", - "job": { - "name": "{任务名}", - "schedule": { "kind": "cron", "expr": "0 8 * * *", "tz": "{用户确认的 IANA 时区}" }, - "sessionTarget": "isolated", - "wakeMode": "now", - "payload": { - "kind": "agentTurn", - "message": "你是一个暖心的提醒助手。请用温暖、有趣的方式提醒用户:{提醒内容}。要求:(1) 不要回复HEARTBEAT_OK (2) 不要解释你是谁 (3) 直接输出一条暖心的提醒消息 (4) 可以加一句简短的鸡汤或关怀的话 (5) 控制在2-3句话以内 (6) 用emoji点缀" - }, - "delivery": { - "mode": "announce", - "channel": "qqbot", - "to": "qqbot:c2c:{openid}", - "accountId": "{accountId}" - } - } -} -``` - -> 周期任务**不加** `deleteAfterRun`。群聊 `delivery.to` 格式为 `"qqbot:group:{group_openid}"`。 +`qqbot_remind` 不可用时,不要绕过它直接创建 Gateway 任务。说明当前无法安全注册 QQ 提醒,并建议用户检查 QQBot 工具配置。 --- diff --git a/extensions/slack/skills/slack/SKILL.md b/extensions/slack/skills/slack/SKILL.md index 711139bba54d..ac04a2336a64 100644 --- a/extensions/slack/skills/slack/SKILL.md +++ b/extensions/slack/skills/slack/SKILL.md @@ -1,78 +1,20 @@ --- name: slack -description: "Slack tool actions: send/read/edit/delete messages, react, pin/unpin, list pins/reactions/emoji, member info." +description: "Slack messaging workflows through OpenClaw's message tool." metadata: { "openclaw": { "emoji": "💬", "requires": { "config": ["channels.slack"] } } } +allowed-tools: ["message"] --- # Slack -Use the `slack` tool. Reuse `channelId` and Slack timestamp message IDs from context when present. +Use the `message` tool with `channel: "slack"`. The tool schema lists the actions enabled for the current Slack account; do not assume an action that is not present. -## Inputs +## Workflow -- `channelId`: Slack channel ID. -- `messageId`: Slack timestamp, e.g. `1712023032.1234`. -- `to`: `channel:` or `user:` for sends. -- `emoji`: Unicode or `:name:` for reactions. +- Prefer stable Slack IDs from context. Sends outside the current conversation use `channel:` or `user:` targets. +- Keep replies in the current thread unless the user asks for a top-level post. For another thread, pass its Slack timestamp as `threadId`; use the same timestamp as `messageId` for message-specific actions. +- Before editing, deleting, pinning, or reacting to an ambiguous message, read the conversation and resolve its exact ID. +- When multiple Slack accounts are configured, pass `accountId` rather than guessing. +- Confirm destructive deletes when the target or intent is unclear. -## Actions - -```json -{ "action": "sendMessage", "to": "channel:C123", "content": "Hello" } -``` - -```json -{ "action": "readMessages", "channelId": "C123", "limit": 20 } -``` - -```json -{ - "action": "react", - "channelId": "C123", - "messageId": "1712023032.1234", - "emoji": ":white_check_mark:" -} -``` - -```json -{ "action": "reactions", "channelId": "C123", "messageId": "1712023032.1234" } -``` - -```json -{ - "action": "editMessage", - "channelId": "C123", - "messageId": "1712023032.1234", - "content": "Updated text" -} -``` - -```json -{ "action": "deleteMessage", "channelId": "C123", "messageId": "1712023032.1234" } -``` - -```json -{ "action": "pinMessage", "channelId": "C123", "messageId": "1712023032.1234" } -``` - -```json -{ "action": "unpinMessage", "channelId": "C123", "messageId": "1712023032.1234" } -``` - -```json -{ "action": "listPins", "channelId": "C123" } -``` - -```json -{ "action": "memberInfo", "userId": "U123" } -``` - -```json -{ "action": "emojiList" } -``` - -## Safety - -- Confirm destructive deletes when context is unclear. -- Keep outbound messages short; avoid Markdown tables. -- Prefer thread/message IDs over fuzzy channel names. +Slack formatting, mentions, tables, charts, and interactive controls are described automatically by the current `message` tool hints. Follow those hints instead of maintaining a second action or formatting catalog here. diff --git a/extensions/tlon/package.json b/extensions/tlon/package.json index 60ee8ab18da1..b91aebfc0bbf 100644 --- a/extensions/tlon/package.json +++ b/extensions/tlon/package.json @@ -10,7 +10,7 @@ "dependencies": { "@aws-sdk/client-s3": "3.1095.0", "@aws-sdk/s3-request-presigner": "3.1095.0", - "@tloncorp/tlon-skill": "0.4.4", + "@tloncorp/tlon-skill": "0.4.5", "@urbit/aura": "3.0.0", "zod": "4.4.3" }, diff --git a/extensions/whatsapp/skills/wacli/SKILL.md b/extensions/whatsapp/skills/wacli/SKILL.md index fffc28e99480..b749dcc1f0a8 100644 --- a/extensions/whatsapp/skills/wacli/SKILL.md +++ b/extensions/whatsapp/skills/wacli/SKILL.md @@ -20,7 +20,7 @@ metadata: { "id": "go", "kind": "go", - "module": "github.com/steipete/wacli/cmd/wacli@latest", + "module": "github.com/openclaw/wacli/cmd/wacli@latest", "bins": ["wacli"], "label": "Install wacli (go)", }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 274fbb6e9425..234b41d00a6d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1907,8 +1907,8 @@ importers: specifier: 3.1095.0 version: 3.1095.0 '@tloncorp/tlon-skill': - specifier: 0.4.4 - version: 0.4.4 + specifier: 0.4.5 + version: 0.4.5 '@urbit/aura': specifier: 3.0.0 version: 3.0.0 @@ -5226,32 +5226,32 @@ packages: resolution: {integrity: sha512-5Kc5CM2Ysn3vTTArBs2vESUt0AQiWZA86yc1TI3B+lxXmtEq133C1nxXNOgnzhrivdPZIh3zLj5gDnZjoLL5GA==} engines: {node: '>=12.17.0'} - '@tloncorp/tlon-skill-darwin-arm64@0.4.4': - resolution: {integrity: sha512-yr7Lg4KJPWInBFarXhJV+Z8LxFFLGJAgoBY6d6sc7zVW40oKUM+OQjKomKaNy8H4aYk0CnZkXlN+CTOn3/LD6g==} + '@tloncorp/tlon-skill-darwin-arm64@0.4.5': + resolution: {integrity: sha512-urM3HnsoQR/yqT8qxs4DZu/B6bWS0EdNKcJXme5FdTsuX4NfOhbMAdjl+aasu2G86GF3q/fgH00g75DNiIFB9w==} cpu: [arm64] os: [darwin] hasBin: true - '@tloncorp/tlon-skill-darwin-x64@0.4.4': - resolution: {integrity: sha512-h9yL4fwkLa2zXqROWV6QuqVB4FlQOA9iQfahST15MAjyQRBqJEt2zxjuXHbkvlZ9SKm4vS5J7YLXI6yXLvULGA==} + '@tloncorp/tlon-skill-darwin-x64@0.4.5': + resolution: {integrity: sha512-w+pr1NwiD6524WAIHwpQ0IjtqV1K7SUw2aqaG27lvluPukzolYWl3fcliJzY2AOeCwe2r3JkCq5JD1oiZGkLjQ==} cpu: [x64] os: [darwin] hasBin: true - '@tloncorp/tlon-skill-linux-arm64@0.4.4': - resolution: {integrity: sha512-OBzY/ACHo9/wecN0XTkepK2mGwDnqcs+T7vEhjHPKr4aD+YGBvdgA0abeXcSsz9EXKVnRnaDMo9QDYcROy4DIw==} + '@tloncorp/tlon-skill-linux-arm64@0.4.5': + resolution: {integrity: sha512-t/N4nUF2abzQA22ONbjSdOEyQ0k6nWaA/wZ3OZ0Gzmsfa4caEbMwiHS/nY9po9p3Gb8IFXW7ZTMwMe7w2E3XlA==} cpu: [arm64] os: [linux] hasBin: true - '@tloncorp/tlon-skill-linux-x64@0.4.4': - resolution: {integrity: sha512-KCSfVFDBvR/oaDoa/C4Zyvw8eZTq0GDEcaWfn0tbLF3oWDNziSG1a/VFsQzDhHQWI4n0oq2402/HEbMMAI0Fvw==} + '@tloncorp/tlon-skill-linux-x64@0.4.5': + resolution: {integrity: sha512-1u5cdmnjSeYGhgTSMTDrpenPOxCu7B5Q62cuviBeYaS7lvvNTNwynIwuO9QR3Q98r7rMMyzWExhDHJ+sRlSbsw==} cpu: [x64] os: [linux] hasBin: true - '@tloncorp/tlon-skill@0.4.4': - resolution: {integrity: sha512-kjFEqZNieQZR7aSgdM54GRWfMYtukuZlMzlW6ThNWKwrX2YDl8ieOZ7dA0gGcp2TpPHbtTpj83Ro1DMPlcGZ5A==} + '@tloncorp/tlon-skill@0.4.5': + resolution: {integrity: sha512-rsQ4Qqm1oCGLz7rW/TFyVc+8KANifGNrY8jTEu4AbIgFfGR9m3I8tdRjSdAkVW7ZCZ7VDI/RlZhCN8s/dNz+Kw==} hasBin: true '@tokenizer/inflate@0.4.1': @@ -12387,24 +12387,24 @@ snapshots: '@tinyhttp/content-disposition@2.2.4': optional: true - '@tloncorp/tlon-skill-darwin-arm64@0.4.4': + '@tloncorp/tlon-skill-darwin-arm64@0.4.5': optional: true - '@tloncorp/tlon-skill-darwin-x64@0.4.4': + '@tloncorp/tlon-skill-darwin-x64@0.4.5': optional: true - '@tloncorp/tlon-skill-linux-arm64@0.4.4': + '@tloncorp/tlon-skill-linux-arm64@0.4.5': optional: true - '@tloncorp/tlon-skill-linux-x64@0.4.4': + '@tloncorp/tlon-skill-linux-x64@0.4.5': optional: true - '@tloncorp/tlon-skill@0.4.4': + '@tloncorp/tlon-skill@0.4.5': optionalDependencies: - '@tloncorp/tlon-skill-darwin-arm64': 0.4.4 - '@tloncorp/tlon-skill-darwin-x64': 0.4.4 - '@tloncorp/tlon-skill-linux-arm64': 0.4.4 - '@tloncorp/tlon-skill-linux-x64': 0.4.4 + '@tloncorp/tlon-skill-darwin-arm64': 0.4.5 + '@tloncorp/tlon-skill-darwin-x64': 0.4.5 + '@tloncorp/tlon-skill-linux-arm64': 0.4.5 + '@tloncorp/tlon-skill-linux-x64': 0.4.5 '@tokenizer/inflate@0.4.1(supports-color@10.2.2)': dependencies: diff --git a/src/plugins/contracts/channel-provider-skills.contract.test.ts b/src/plugins/contracts/channel-provider-skills.contract.test.ts new file mode 100644 index 000000000000..d2b5f031d9c9 --- /dev/null +++ b/src/plugins/contracts/channel-provider-skills.contract.test.ts @@ -0,0 +1,88 @@ +import { readFileSync } from "node:fs"; +import { dirname, relative, resolve } from "node:path"; +import { describe, expect, it } from "vitest"; +import { listGitTrackedFiles } from "../../test-utils/repo-files.js"; + +type PluginManifest = { + channels?: unknown; + skills?: unknown; +}; + +const RETIRED_SKILL_PATTERNS = [ + { + pattern: /use\s+the\s+[`'"]slack[`'"]\s+tool/iu, + replacement: "the `message` tool", + }, + { + pattern: + /["']action["']\s*:\s*["'](?:sendMessage|readMessages|editMessage|deleteMessage|pinMessage|unpinMessage|listPins|memberInfo|emojiList)["']/u, + replacement: "a canonical `message` action", + }, + { pattern: /owner_open_id/u, replacement: "grant_to_requester" }, + { pattern: /feishu_doc_list_blocks/u, replacement: "feishu_doc action=list_blocks" }, + { pattern: /feishu_doc_update_block/u, replacement: "feishu_doc action=update_block" }, + { pattern: /feishu_doc_delete_block/u, replacement: "feishu_doc action=delete_block" }, + { + pattern: /["']components["']\s*:\s*["']\[Carbon v2 components\]["']/u, + replacement: "structured components", + }, + { pattern: /## 备用方案(直接使用 `cron` 工具)/u, replacement: "qqbot_remind only" }, + { + pattern: /github\.com\/steipete\/wacli\/cmd\/wacli@latest/u, + replacement: "github.com/openclaw/wacli/cmd/wacli@latest", + }, +] as const; + +function listRepositoryOwnedChannelSkillFiles(): string[] { + const trackedFiles = listGitTrackedFiles({ pathspecs: ["extensions"] }) ?? []; + const trackedFileSet = new Set(trackedFiles); + const skillFiles = new Set(); + + for (const manifestPath of trackedFiles.filter((file) => + /^extensions\/[^/]+\/openclaw\.plugin\.json$/u.test(file), + )) { + const manifest = JSON.parse( + readFileSync(resolve(process.cwd(), manifestPath), "utf8"), + ) as PluginManifest; + if (!Array.isArray(manifest.channels) || !Array.isArray(manifest.skills)) { + continue; + } + + const pluginDir = dirname(manifestPath); + for (const skillRoot of manifest.skills) { + if (typeof skillRoot !== "string" || skillRoot.includes("node_modules")) { + continue; + } + const relativeRoot = relative(process.cwd(), resolve(pluginDir, skillRoot)).replaceAll( + "\\", + "/", + ); + for (const file of trackedFileSet) { + if (file.startsWith(`${relativeRoot}/`) && file.endsWith(".md")) { + skillFiles.add(file); + } + } + } + } + + return [...skillFiles].toSorted(); +} + +describe("bundled channel-provider skill contracts", () => { + it("does not teach retired tool, action, parameter, or install contracts", () => { + const failures: string[] = []; + const skillFiles = listRepositoryOwnedChannelSkillFiles(); + + expect(skillFiles.length).toBeGreaterThan(0); + for (const file of skillFiles) { + const source = readFileSync(resolve(process.cwd(), file), "utf8"); + for (const entry of RETIRED_SKILL_PATTERNS) { + if (entry.pattern.test(source)) { + failures.push(`${file}: replace ${entry.pattern.source} with ${entry.replacement}`); + } + } + } + + expect(failures).toStrictEqual([]); + }); +});