OpenClaw EnterpriseDOCSGitHub

Agent Channel Directory Lookup Flow

Overview

An operator searches Slack users or channels while creating or editing an Agent. The Console uses the selected bot Secret to show names and workspace identity, then saves only the selected IDs in channel Configuration. OpenClaw Control Plane (OCC) authorizes and reads the Secret; the selected ChannelDriver owns provider calls. This flow ends when the Console displays candidates or an actionable error.

Entry Points

Flow

graph TD
  A["Operator selects bot Secret and searches"] --> B["OCC checks edit target and exact Secret operate"]
  B -->|denied or missing| X["Return safe lookup error"]
  B -->|authorized| P{"ChannelDriver selected?"}
  P -->|no| U["Return 501; Console offers exact-ID entry"]
  P -->|yes| C["SecretDriver reads current value"]
  C --> D["OCC rechecks target, Secret grant, and backend identity"]
  D -->|changed| X
  D -->|current| M{"Managed Helm proxy?"}
  M -->|yes| H["Tunnel through proxy Service DNS"]
  M -->|no| I["Tunnel through external proxy IP"]
  H --> E["Slack Driver reads workspace and directory pages"]
  I --> E
  E -->|provider error| X
  E --> F["Console shows names and exact IDs"]
  F --> G["Configuration saves selected IDs"]

Execution Trace

1. Authorize the selected Secret

packages/occ/src/index.ts:OpenClawController.channelDirectorySecret

The request names a same-Namespace Secret and optionally an Agent or Configuration being edited. OCC checks the matching create or exact update permission and Secret operate, then reads the resource and Secret from platform state. A missing or foreign target is rejected before provider I/O.

2. Read and use the current value

packages/occ/src/index.ts:OpenClawController.lookupChannelDirectory

Production composition selects the bundled Slack ChannelDriver only when the API has an approved OCC_CHANNEL_DIRECTORY_PROXY_URL. Without it, an authorized lookup returns 501 before the Secret value is read. Development selects the Driver directly. In production the Driver tunnels requests to slack.com:443 through the configured proxy. With the managed proxy enabled, Helm points the API at the openclaw-enterprise-slack-proxy.<namespace>.svc Service, sets that exact host in OCC_CHANNEL_DIRECTORY_MANAGED_PROXY_HOST, and limits API egress to the proxy Pod selector. With an external literal IPv4 proxy, Helm limits API egress to that IP and port. The managed proxy accepts CONNECT only for Slack hostnames on port 443, while its NetworkPolicy allows upstream egress to public IPv4 addresses on TCP 443, excluding private and reserved ranges.

In apps/controller/src/slack-proxy.mjs:createSlackProxyServer, the managed proxy starts a 10-second wait when a write to the client is pending, even if the write buffer is below its high-water mark. The proxy clears the wait when all pending writes complete. A later pending write starts a new wait. A quiet tunnel has no write deadline. An upstream end, an upstream close, or a client close also clears the wait. If the wait expires with a write still pending, the proxy resets the client connection so a partial reply cannot appear complete. After a clean upstream end, a separate wait gives the client time to read the remaining bytes and close its side.

The selected SecretDriver invokes withValue and verifies backend ownership. OCC rechecks grants and Secret backend identity after the read. It passes the token only in process to the ChannelDriver. The bundled Slack implementation requires a bot identity from auth.test and pages through users.list or conversations.list. Exact-ID searches and saved IDs use users.info or conversations.info. The response contains at most 100 candidates and pagination state, never the token. An incomplete page cannot establish that a name is absent or unique.

apps/controller/src/drivers/channel/slack.ts:SlackChannelDriver.call cancels an unused response body on a non-success HTTP status before returning the safe rate-limit or unavailable error. This releases occupied transport capacity even when the error body has not finished. The same request owner serves credential validation; no automatic retry is added. Production composition sets expectProxy: with no proxy URL, a credential check whose fetch fails to connect, resolve, or answer within the time budget raises SlackRouteNotConfiguredError (409) instead of the unavailable error (finding 2521). Its message names the Helm proxy settings and says to try again when the API has another approved route, since an open route can also time out (finding 2531). Any other fetch rejection, such as a refused redirect, a TLS failure, or an invalid header value, stays unavailable (finding 2532). Successful responses have an 8,000,000-byte streaming cap before decoded JSON is checked against the existing limit of 2,000,000 UTF-16 code units. Overflow cancels the body read and returns invalid_response.

3. Display names and save IDs

apps/controller/src/console/channels/slack.mjs:appendFields, apps/controller/src/console/agents/slack-directory.mjs:createSlackDirectoryField

The Console presents bot-token selection before channel access and explains that name lookup needs that token. It debounces typing and shows each candidate's name, exact ID, and workspace. The directory result panel overlays the form, following the Secret picker pattern in apps/controller/src/console/console.css. Closing results on focus loss cancels pending searches without moving the clicked control; name-status hints retain their layout space while results are open. It buffers the provider's complete returned batch and divides it into display pages. Previous and Next reuse those pages before Next follows the provider cursor. An empty provider page with a continuation still offers Next. New input, dismissal, or a changed Secret cancels the queued search and its browser request; generation checks also discard obsolete responses. Browser cancellation does not guarantee cancellation of provider work already started by the API.

Selecting a result or confirming pasted IDs adds removable chips to the field; search text remains separate from committed IDs. Plugin approver fields accept raw Slack user IDs when lookup is unavailable; directory-selected approvers remain workspace-qualified. Arrow keys move the active option; Enter confirms it. Escape closes the list and retains input focus and text. With the list closed, plain Down starts a fresh authorized search and activates its first current result. Clicking the focused input also starts a fresh search, without an active option. Neither action commits a selection. Dismissal, new input, or a changed Secret cancels pending activation with the search. It resolves saved IDs again when the editor opens or the selected Secret changes. A view sends the first of these name lookups alone (slack-directory.mjs:nameLookupInTurn, request-turns.mjs); the others wait until one succeeds, then go at once. A 403 answers the lookups waiting behind it for the same Secret, unsent, so each field shows that refusal and the API audits one denial; once no lookup is pending, a later one asks again. A lookup still waiting when its page is left is not sent. A denied or failed lookup leaves manual exact-ID entry available; no directory result changes the saved Configuration until the operator saves the channel edit.

When Agent detail performs a browser-refocus access check, it keeps the mounted view. The picker keeps its open query and results while controls are temporarily inert; moving focus to another Console control closes the list. Read-only directory lookups do not invalidate tab retention, so a completed search can keep its picker when switching Agent tabs and returning. Those results are from the last authorized lookup. A new search rechecks the exact edit target and Secret operate grant, and denied Agent access removes the view.

Debugging and Verification

Search documentation