From f275e9d4b91b70fa542346800dad9f1467372101 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 3 May 2026 15:25:18 -0700 Subject: [PATCH] fix(docs): make slack manifest snippets parseable --- CHANGELOG.md | 2 +- docs/channels/slack.md | 262 ++++++++++++----------- src/docs/channel-config-examples.test.ts | 21 ++ 3 files changed, 155 insertions(+), 130 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6959016b7c0..4a948d499c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,7 @@ Docs: https://docs.openclaw.ai - Google Meet: refresh realtime browser state during status and retry delayed speech after Meet finishes joining, so a just-opened in-call tab no longer leaves speech stuck behind stale `not-in-call` health. - Plugins/install: recover the install ledger from the managed npm root when `plugins/installs.json` is empty or partial, so reinstalling Discord and Codex no longer makes the other installed plugin disappear. - Google Meet: grant Meet media permissions through the Playwright browser context when CDP grants do not affect the attached Chrome page, and report in-call microphone/speaker permission problems instead of marking realtime speech ready. -- Channel docs: keep JSON5 channel config examples parseable and schema-valid, fixing BlueBubbles and QQ Bot snippets that could not be copied into config as shown. Thanks @vincentkoc. +- Channel docs: keep JSON5 channel config examples parseable and schema-valid, fixing BlueBubbles, QQ Bot, and Slack snippets that could not be copied into config or app manifests as shown. Thanks @vincentkoc. - Tlon: expose `groupInviteAllowlist` in the channel config schema and clarify that group invite auto-accept fails closed without an invite allowlist. Thanks @vincentkoc. - Google Chat: update the setup example to use the accepted `groups..enabled` key instead of the legacy `allow` alias, with a schema regression for the documented group shape. Thanks @vincentkoc. - Control UI/WebChat: collapse duplicate in-flight internal text sends onto the active Gateway run so rapid repeat submits do not start fresh `agent:main:main` dispatches. Fixes #75737. Thanks @dsdsddd1 and @BunsDev. diff --git a/docs/channels/slack.md b/docs/channels/slack.md index 6ab601a6d88..6580fcc4f88 100644 --- a/docs/channels/slack.md +++ b/docs/channels/slack.md @@ -283,121 +283,123 @@ The default manifest enables the Slack App Home **Home** tab and subscribes to ` ```json - "slash_commands": [ - { - "command": "/new", - "description": "Start a new session", - "usage_hint": "[model]" - }, - { - "command": "/reset", - "description": "Reset the current session" - }, - { - "command": "/compact", - "description": "Compact the session context", - "usage_hint": "[instructions]" - }, - { - "command": "/stop", - "description": "Stop the current run" - }, - { - "command": "/session", - "description": "Manage thread-binding expiry", - "usage_hint": "idle or max-age " - }, - { - "command": "/think", - "description": "Set the thinking level", - "usage_hint": "" - }, - { - "command": "/verbose", - "description": "Toggle verbose output", - "usage_hint": "on|off|full" - }, - { - "command": "/fast", - "description": "Show or set fast mode", - "usage_hint": "[status|on|off]" - }, - { - "command": "/reasoning", - "description": "Toggle reasoning visibility", - "usage_hint": "[on|off|stream]" - }, - { - "command": "/elevated", - "description": "Toggle elevated mode", - "usage_hint": "[on|off|ask|full]" - }, - { - "command": "/exec", - "description": "Show or set exec defaults", - "usage_hint": "host= security= ask= node=" - }, - { - "command": "/model", - "description": "Show or set the model", - "usage_hint": "[name|#|status]" - }, - { - "command": "/models", - "description": "List providers/models", - "usage_hint": "[provider] [page] [limit=|size=|all]" - }, - { - "command": "/help", - "description": "Show the short help summary" - }, - { - "command": "/commands", - "description": "Show the generated command catalog" - }, - { - "command": "/tools", - "description": "Show what the current agent can use right now", - "usage_hint": "[compact|verbose]" - }, - { - "command": "/agentstatus", - "description": "Show runtime status, including provider usage/quota when available" - }, - { - "command": "/tasks", - "description": "List active/recent background tasks for the current session" - }, - { - "command": "/context", - "description": "Explain how context is assembled", - "usage_hint": "[list|detail|json]" - }, - { - "command": "/whoami", - "description": "Show your sender identity" - }, - { - "command": "/skill", - "description": "Run a skill by name", - "usage_hint": " [input]" - }, - { - "command": "/btw", - "description": "Ask a side question without changing session context", - "usage_hint": "" - }, - { - "command": "/side", - "description": "Ask a side question without changing session context", - "usage_hint": "" - }, - { - "command": "/usage", - "description": "Control the usage footer or show cost summary", - "usage_hint": "off|tokens|full|cost" - } - ] +{ + "slash_commands": [ + { + "command": "/new", + "description": "Start a new session", + "usage_hint": "[model]" + }, + { + "command": "/reset", + "description": "Reset the current session" + }, + { + "command": "/compact", + "description": "Compact the session context", + "usage_hint": "[instructions]" + }, + { + "command": "/stop", + "description": "Stop the current run" + }, + { + "command": "/session", + "description": "Manage thread-binding expiry", + "usage_hint": "idle or max-age " + }, + { + "command": "/think", + "description": "Set the thinking level", + "usage_hint": "" + }, + { + "command": "/verbose", + "description": "Toggle verbose output", + "usage_hint": "on|off|full" + }, + { + "command": "/fast", + "description": "Show or set fast mode", + "usage_hint": "[status|on|off]" + }, + { + "command": "/reasoning", + "description": "Toggle reasoning visibility", + "usage_hint": "[on|off|stream]" + }, + { + "command": "/elevated", + "description": "Toggle elevated mode", + "usage_hint": "[on|off|ask|full]" + }, + { + "command": "/exec", + "description": "Show or set exec defaults", + "usage_hint": "host= security= ask= node=" + }, + { + "command": "/model", + "description": "Show or set the model", + "usage_hint": "[name|#|status]" + }, + { + "command": "/models", + "description": "List providers/models", + "usage_hint": "[provider] [page] [limit=|size=|all]" + }, + { + "command": "/help", + "description": "Show the short help summary" + }, + { + "command": "/commands", + "description": "Show the generated command catalog" + }, + { + "command": "/tools", + "description": "Show what the current agent can use right now", + "usage_hint": "[compact|verbose]" + }, + { + "command": "/agentstatus", + "description": "Show runtime status, including provider usage/quota when available" + }, + { + "command": "/tasks", + "description": "List active/recent background tasks for the current session" + }, + { + "command": "/context", + "description": "Explain how context is assembled", + "usage_hint": "[list|detail|json]" + }, + { + "command": "/whoami", + "description": "Show your sender identity" + }, + { + "command": "/skill", + "description": "Run a skill by name", + "usage_hint": " [input]" + }, + { + "command": "/btw", + "description": "Ask a side question without changing session context", + "usage_hint": "" + }, + { + "command": "/side", + "description": "Ask a side question without changing session context", + "usage_hint": "" + }, + { + "command": "/usage", + "description": "Control the usage footer or show cost summary", + "usage_hint": "off|tokens|full|cost" + } + ] +} ``` @@ -405,20 +407,22 @@ The default manifest enables the Slack App Home **Home** tab and subscribes to ` Use the same `slash_commands` list as Socket Mode above, and add `"url": "https://gateway-host.example.com/slack/events"` to every entry. Example: ```json - "slash_commands": [ - { - "command": "/new", - "description": "Start a new session", - "usage_hint": "[model]", - "url": "https://gateway-host.example.com/slack/events" - }, - { - "command": "/help", - "description": "Show the short help summary", - "url": "https://gateway-host.example.com/slack/events" - } - // ...repeat for every command with the same `url` value - ] +{ + "slash_commands": [ + { + "command": "/new", + "description": "Start a new session", + "usage_hint": "[model]", + "url": "https://gateway-host.example.com/slack/events" + }, + { + "command": "/help", + "description": "Show the short help summary", + "url": "https://gateway-host.example.com/slack/events" + } + // ...repeat for every command with the same `url` value + ] +} ``` diff --git a/src/docs/channel-config-examples.test.ts b/src/docs/channel-config-examples.test.ts index 711c58d6590..42b9bd8ece8 100644 --- a/src/docs/channel-config-examples.test.ts +++ b/src/docs/channel-config-examples.test.ts @@ -11,6 +11,27 @@ function lineNumberAt(source: string, index: number): number { } describe("channel docs config examples", () => { + it("keeps channel docs JSON fences parseable", () => { + const failures: string[] = []; + for (const fileName of fs + .readdirSync(CHANNEL_DOCS_DIR) + .filter((entry) => entry.endsWith(".md"))) { + const docPath = path.join(CHANNEL_DOCS_DIR, fileName); + const markdown = fs.readFileSync(docPath, "utf8"); + const blocks = markdown.matchAll(/```(?:json5|json)\n([\s\S]*?)```/g); + for (const match of blocks) { + const code = match[1] ?? ""; + const location = `${fileName}:${lineNumberAt(markdown, match.index ?? 0)}`; + try { + JSON5.parse(code); + } catch (error) { + failures.push(`${location} JSON5 parse failed: ${String(error)}`); + } + } + } + expect(failures).toEqual([]); + }); + it("keeps OpenClaw channel config snippets parseable and schema-valid", () => { const failures: string[] = []; for (const fileName of fs