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
- Trigger: the Console Slack editor searches or resolves saved IDs, or an authorized caller posts to the Namespace channel directory route.
- Assumptions: the caller can create an Agent or update the exact Agent or Configuration, and can operate the selected same-Namespace Secret.
- Source:
apps/controller/src/console/agents/slack-directory.mjs:createSlackDirectoryField,packages/occ/src/index.ts:OpenClawController.lookupChannelDirectory, andapps/controller/src/drivers/channel/slack.ts:SlackChannelDriver.lookupDirectory.
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
- A denied lookup requires checking the exact edit permission and Secret
operategrant. A token, scope, rate limit, or provider error returns a safe code without the token or upstream payload. - A
501lookup in production means the API has no directory proxy configured. Enable HelmslackProxy.enabledor set the approved external proxy IP and port inapi.channelDirectoryProxyUrl, then verify that the proxy permits CONNECT toslack.com:443. - Directory conformance tests cover provider pagination, safe errors, and native HTTP connection recovery after unfinished 429 and 503 response bodies. The OCC API integration test covers both authorization checks and response projection. Browser checks cover name display and exact-ID saving.
- The Agent plugin approver browser check holds the refocus access read and verifies that an open directory search remains available without a second lookup.
- Fixture and simulated provider tests do not prove a live Slack token, bot visibility, or channel message delivery.
