diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 43027196714..fa5ceca3f90 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -16,6 +16,14 @@ This directory owns docs authoring, Mintlify link rules, and docs i18n policy. - For docs, UI copy, and picker lists, order services/providers alphabetically unless the section is explicitly describing runtime order or auto-detection order. - Keep bundled plugin naming consistent with the repo-wide plugin terminology rules in the root `AGENTS.md`. +## Internal Docs + +- Long-lived private operator docs belong in `~/Projects/manager/docs/`. +- Repo-local internal scratch/mirror docs may live under ignored `docs/internal/`. +- Never add `docs/internal/**` pages to `docs/docs.json` navigation or link them from public docs. +- `scripts/docs-sync-publish.mjs` excludes and prunes `docs/internal/**` from the public `openclaw/docs` publish repo if a page is force-added later. +- Internal docs may mention repo paths, private app names, 1Password item names, and runbooks, but never include secret values. + ## Docs i18n - Foreign-language docs are not maintained in this repo. The generated publish output lives in the separate `openclaw/docs` repo (often cloned locally as `../openclaw-docs`). diff --git a/docs/concepts/qa-e2e-automation.md b/docs/concepts/qa-e2e-automation.md index 11e2432e735..56757fde9ee 100644 --- a/docs/concepts/qa-e2e-automation.md +++ b/docs/concepts/qa-e2e-automation.md @@ -559,15 +559,17 @@ A green run completes in well under 30 seconds and `slack-qa-report.md` shows bo ### Convex credential pool -Telegram, Discord, and Slack lanes can lease credentials from a shared Convex pool instead of reading the env vars above. Pass `--credential-source convex` (or set `OPENCLAW_QA_CREDENTIAL_SOURCE=convex`); QA Lab acquires an exclusive lease, heartbeats it for the duration of the run, and releases it on shutdown. Pool kinds are `"telegram"`, `"discord"`, and `"slack"`. +Telegram, Discord, Slack, and WhatsApp lanes can lease credentials from a shared Convex pool instead of reading the env vars above. Pass `--credential-source convex` (or set `OPENCLAW_QA_CREDENTIAL_SOURCE=convex`); QA Lab acquires an exclusive lease, heartbeats it for the duration of the run, and releases it on shutdown. Pool kinds are `"telegram"`, `"discord"`, `"slack"`, and `"whatsapp"`. Payload shapes the broker validates on `admin/add`: - Telegram (`kind: "telegram"`): `{ groupId: string, driverToken: string, sutToken: string }` - `groupId` must be a numeric chat-id string. - Discord (`kind: "discord"`): `{ guildId: string, channelId: string, driverBotToken: string, sutBotToken: string, sutApplicationId: string }`. -- Slack (`kind: "slack"`): `{ channelId: string, driverBotToken: string, sutBotToken: string, sutAppToken: string }` - `channelId` must match `^[A-Z][A-Z0-9]+$` (a Slack id like `Cxxxxxxxxxx`). See [Setting up the Slack workspace](#setting-up-the-slack-workspace) for app and scope provisioning. +- WhatsApp (`kind: "whatsapp"`): `{ driverPhoneE164: string, sutPhoneE164: string, driverAuthArchiveBase64: string, sutAuthArchiveBase64: string, groupJid?: string }` - phone numbers must be distinct E.164 strings. -Operational env vars and the Convex broker endpoint contract live in [Testing → Shared Telegram credentials via Convex](/help/testing#shared-telegram-credentials-via-convex-v1) (the section name predates Discord support; the broker semantics are identical for both kinds). +Slack lanes can also use the pool. Slack payload shape checks currently live in the Slack QA runner rather than the broker; use `{ channelId: string, driverBotToken: string, sutBotToken: string, sutAppToken: string }`, with a Slack channel id like `Cxxxxxxxxxx`. See [Setting up the Slack workspace](#setting-up-the-slack-workspace) for app and scope provisioning. + +Operational env vars and the Convex broker endpoint contract live in [Testing → Shared Telegram credentials via Convex](/help/testing#shared-telegram-credentials-via-convex-v1) (the section name predates the multi-channel pool; the lease semantics are shared across kinds). ## Repo-backed seeds diff --git a/docs/help/testing.md b/docs/help/testing.md index fa0280b8309..9c5df8fe205 100644 --- a/docs/help/testing.md +++ b/docs/help/testing.md @@ -322,8 +322,9 @@ Live transport lanes share one standard contract so new transports do not drift; ### Shared Telegram credentials via Convex (v1) When `--credential-source convex` (or `OPENCLAW_QA_CREDENTIAL_SOURCE=convex`) is enabled for -`openclaw qa telegram`, QA lab acquires an exclusive lease from a Convex-backed pool, heartbeats -that lease while the lane is running, and releases the lease on shutdown. +live transport QA, QA lab acquires an exclusive lease from a Convex-backed pool, heartbeats that +lease while the lane is running, and releases the lease on shutdown. The section name predates +Discord, Slack, and WhatsApp support; the lease contract is shared across kinds. Reference Convex project scaffold: @@ -397,6 +398,16 @@ Payload shape for Telegram kind: - `groupId` must be a numeric Telegram chat id string. - `admin/add` validates this shape for `kind: "telegram"` and rejects malformed payloads. +Broker-validated multi-channel payloads: + +- Discord: `{ guildId: string, channelId: string, driverBotToken: string, sutBotToken: string, sutApplicationId: string, voiceChannelId?: string }` +- WhatsApp: `{ driverPhoneE164: string, sutPhoneE164: string, driverAuthArchiveBase64: string, sutAuthArchiveBase64: string, groupJid?: string }` + +Slack lanes can also lease from the pool, but Slack payload validation currently +lives in the Slack QA runner rather than the broker. Use +`{ channelId: string, driverBotToken: string, sutBotToken: string, sutAppToken: string }` +for Slack rows. + ### Adding a channel to QA The architecture and scenario-helper names for new channel adapters live in [QA overview → Adding a channel](/concepts/qa-e2e-automation#adding-a-channel). The minimum bar: implement the transport runner on the shared `qa-lab` host seam, declare `qaRunners` in the plugin manifest, mount as `openclaw qa `, and author scenarios under `qa/scenarios/`. diff --git a/qa/convex-credential-broker/README.md b/qa/convex-credential-broker/README.md index f62d6dc3ae5..5d7c920b79e 100644 --- a/qa/convex-credential-broker/README.md +++ b/qa/convex-credential-broker/README.md @@ -1,6 +1,7 @@ # QA Convex Credential Broker (v1) Standalone Convex project for shared `qa-lab` live credentials with lease locking. +Keep private operator notes in `~/Projects/manager/docs/`, not in public docs. This broker exposes: @@ -152,6 +153,17 @@ For `kind: "discord"`, broker `admin/add` validates that payload includes: - non-empty `sutBotToken` - `sutApplicationId` as a Discord snowflake string +For `kind: "whatsapp"`, broker `admin/add` validates that payload includes: + +- `driverPhoneE164` as an E.164 phone number string +- `sutPhoneE164` as a distinct E.164 phone number string +- non-empty `driverAuthArchiveBase64` +- non-empty `sutAuthArchiveBase64` +- optional `groupJid` + +Other kinds are currently accepted as pass-through payloads. Add broker-side +validation before treating a new kind as a hardened shared pool. + Admin list (default redacted): ```bash diff --git a/scripts/docs-sync-publish.mjs b/scripts/docs-sync-publish.mjs index 63a3f69aa71..f88bc7a80b8 100644 --- a/scripts/docs-sync-publish.mjs +++ b/scripts/docs-sync-publish.mjs @@ -10,6 +10,7 @@ const HERE = path.dirname(fileURLToPath(import.meta.url)); const ROOT = path.resolve(HERE, ".."); const SOURCE_DOCS_DIR = path.join(ROOT, "docs"); const SOURCE_CONFIG_PATH = path.join(SOURCE_DOCS_DIR, "docs.json"); +const INTERNAL_DOCS_DIRS = ["internal"]; const DEFAULT_CLAWHUB_SOURCE_REPO = "openclaw/clawhub"; const CLAWHUB_DOCS_TARGET_DIR = "clawhub"; const CLAWHUB_REPO_ENV = "OPENCLAW_DOCS_SYNC_CLAWHUB_REPO"; @@ -458,6 +459,22 @@ function repairGeneratedLocaleDocs(targetDocsDir) { } } +function pruneInternalDocs(targetDocsDir) { + let pruned = 0; + for (const relativeDir of INTERNAL_DOCS_DIRS) { + const dirPath = path.join(targetDocsDir, relativeDir); + if (!fs.existsSync(dirPath)) { + continue; + } + fs.rmSync(dirPath, { recursive: true, force: true }); + pruned += 1; + } + + if (pruned > 0) { + console.log(`Pruned ${pruned} internal-only docs director${pruned === 1 ? "y" : "ies"}.`); + } +} + function shouldExcludeClawHubDocsPath(relativePath) { const normalized = normalizeSlashes(relativePath); return ( @@ -642,10 +659,12 @@ function syncDocsTree(targetRoot, options = {}) { "P .i18n/README.md", "--exclude", ".i18n/README.md", + ...INTERNAL_DOCS_DIRS.flatMap((dir) => ["--exclude", `${dir}/`]), ...localeFilters, `${SOURCE_DOCS_DIR}/`, `${targetDocsDir}/`, ]); + pruneInternalDocs(targetDocsDir); for (const locale of GENERATED_LOCALES) { const sourceTmPath = path.join(SOURCE_DOCS_DIR, ".i18n", locale.tmFile);