Platform console request flow
Overview
Entry Points
- Browser entry:
apps/controller/src/console/console.mjscomposes the session, request client, view lifetime, navigation, and shell. - HTTP:
apps/controller/src/index.ts:createFastifyApp. - Startup:
apps/controller/src/composition/production.ts:composeProductionanddevelopment-postgres.ts:composePostgresDevelopment. - Requires a bootstrapped Installation, provisioned account, selected IAM Driver, same-origin controller, and the API permissions for each read or mutation.
Flow
graph TD
THEME["API startup loads optional theme bundle"] -->|valid snapshot| ASSET["Serve branded HTML, CSS and images"]
THEME -->|invalid| FAIL["Refuse API startup"]
ASSET --> A
subgraph Browser["Browser"]
A["Open console or change page"] --> B["Restore scoped preview or show first-load state"]
B --> B1["Recheck session and Namespace access"]
B1 -->|no session| C["Login"]
C -->|OIDC/Google| C1["External sign-in"]
B1 -->|authenticated recovery key| NR["Resolve authorized Agent key"]
NR -->|allowed| D
NR -->|unavailable| D
C1 -->|callback redirect| B1
B1 -->|authenticated| D["Read readable Namespaces and validate selection"]
D -->|Debug mode enabled| DBG["Read accessible Agents and runtime image metadata"]
DBG --> F
D --> E["Request current page resource"]
E --> NC["Create Namespace"]
E --> ND["Confirm deletion"]
E --> SEC["Manage Namespace Secret metadata and values"]
E --> E1["Edit starter JSON and select associations"]
E1 --> S1["Select Secret or open creation modal"]
S1 -->|select| S3["Stage binding until Apply"]
S3 -->|apply| E1
E --> E2["Open new version draft or admitted version by URL"]
E2 --> E3["Save supported channel draft edit"]
E2 --> E4["Confirm Agent deletion"]
E2 --> E5["Confirm Agent stop"]
end
subgraph Controller["Controller API"]
ND --> NDELETE["Authorize DELETE; check contents"]
E --> F["Authenticate and authorize exact scope"]
F -->|Agents or Namespaces| G["OCC reads and filters by IAM"]
F -->|Backends and Installation admin| H["Project loaded Backend IDs and types"]
NC --> NPOST["POST authorizes, persists and queues provisioning"]
S1 -->|create| S2["POST stores Namespace Secret immediately"]
S2 --> S3
SEC --> SAPI["Authorize Secret list, read, create, replace or delete"]
SAPI -->|metadata or confirmed mutation| I
SAPI -->|unconfirmed write| SU["Erase input and block resubmission"]
SU -->|manual inspection| SEC
E1 -->|draft runtime| M1["POST creates Configuration with staged bindings"]
E1 -->|supported Dedicated and valid repository selection| M3["POST queues provisioning with inline Configuration"]
M3 --> M4["Worker creates resources, grants and first deployment"]
M1 -->|returned Configuration ID| M["POST creates Agent draft only"]
M --> M2["Console grants Agent use of selected Secrets"]
E2 --> N["GET draft Configuration or Agent revision"]
E3 --> O["PATCH Configuration, then grant selected Secret access"]
E4 --> P["DELETE exact Agent"]
E5 --> P2["POST exact Agent stop"]
end
subgraph Result["Browser result"]
NDELETE -->|nonempty| NB["Show blockers and cleanup links"]
NDELETE -->|accepted| NP["Poll Namespace until removed"]
NDELETE -->|uncertain| NRD["Require status refresh before retry"]
G --> I["Accept only current navigation response"]
H --> I
NPOST -->|201| NSUCCESS["Creation accepted"]
NPOST -->|unconfirmed| NUNKNOWN["Inspect before retry"]
NPOST -->|refused| K
NUNKNOWN --> D
NSUCCESS --> D
M2 --> I
M4 --> I
N --> I
O --> I
P -->|accepted or uncertain| Q["Show status and refresh exact Agent"]
P2 -->|accepted or uncertain| Q
P2 -->|denied| K
P -->|denied| K
Q -->|Agent not found| R["Return to Agents list"]
I --> J["Render list, draft, Agent revision, or channel state"]
F -->|denied or unavailable| K["Clear rows and show recovery"]
J -->|Logout| L["Hide private state and confirm sign-out"]
endExecution Trace
1. Compose discovery metadata and serve public assets
apps/controller/src/api-startup.ts:startApi loads the theme bundle once. Invalid bundles stop startup;
ConfigMap links stay inside the bundle. renderConsoleThemeShell escapes branding;
console/shell.mts:brandContents renders names as text. /console/theme/ serves
images and generated CSS with no-store; missing assets return 404.
Startup passes Backend {id,type} to createFastifyApp.
Backend-managed delivery owns activation.
apps/controller/src/console-assets.ts:readConsoleAsset serves allowlisted HTML and assets with MIME types and same-origin CSP. Unknown paths return HTML 404s; API errors stay JSON. encodedAsset caches brotli/gzip output
according to Accept-Encoding and Vary; higher-weight identity wins.
HTML, including sign-in, stays uncompressed.
scripts/build-console-metadata.mjs stamps OCC_BUILD_REVISION into HTML.
internal/occdev/openshell_k3d.go:importDevelopmentController resolves Git HEAD
and runs the engine directly for cancellation and timing. Missing metadata
stops builds; the SHA excludes uncommitted edits.
shell.mts:renderShell displays it in Debug mode.
console.mts:readDebugPreference enables diagnostics only for local-storage
oce.console.debug=true. Storage failures default to off. settingsView saves,
clears retained views, and rebuilds the shell; failures show an error.
navigation.mts removes the old query flag.
runtime-images.mts:renderRuntimeImages limits reads to three concurrent requests
for readable Agents in the selected Namespace. packages/occ/src/index.ts:OpenClawController.getAgentRuntimeImages
authorizes Agent read, resolves its selected version, and calls its Compute Driver.
Disabling Debug mode stops new reads. See the
Compute contract for provenance.
2. Resolve the session before private reads
agents/collection.mts:renderAgentList focuses the Agent link on plain row clicks.
Interactive targets, modified clicks, and selected text retain their behavior.
loadPage starts with GET /api/auth/session. Opening an Agent shows Loading
agent details… and Back to Agents. Back cancels once. Refocus waits. Loading
delays are 400 ms for other Agent routes and 250 ms for other visits or Refresh.
A visible loading page stays for 300 ms or longer.
Return and refocus hold the view inert. Still checking… appears after one second. Up to 16 views retain drafts and disclosures by route, Namespace, and session. Changed reads, newly readable Namespaces, and Refresh rebuild. A cancelled return preserves the view. A cancelled Refresh discards it. Discovery, passwords, mutations, and pending or failed reads prevent reuse. Read-only POSTs permit reuse.
console.mts:revalidateMountedAgent checks session, Namespace, Agent, and detail
reads. Focus and visibility share the check. Navigation cancels it. Detail errors
hide protected content. A 5xx permits one retry unless a refusal, 429, or delay
takes precedence. Narrowed access rebuilds. Both reused and replacement views
require confirmed Agent permissions for grant-dependent controls.
Persistent detail errors show a warning with the confirmed Agent name. Retry starts foreground admission after the latest deadline. Expiry opens sign-in. Refusals remove private content.
api-client.mts:createApiClient preserves abort and socket failures through
response bodies without replay.
Back to Agents waits for admission and the collection. The newest readable
versions supply the rows. agents/collection.mts:currentRevision reads off-page
current versions. deniedReadsFor retains 403s. Reused deployment cells show
Reading deployment… after 250 ms for at least 150 ms. Focus returns to the
cell. Pending or failed reads resume. Search cancels reads for rows it hides
(collection.mts:retireHidden). A row shown again reads once.
A changed user or session key clears private state. A missing session opens login. See sign-in and return for recovery.
After authentication, GET /namespaces keeps the URL selection or picks the
first ready and readable Namespace. Unreadable IDs stay unavailable.
shell.mts:namespaceSelector hides choices during admission. Widening past 760 px
closes the drawer without focusing its toggle. An open drawer blocks scrolling.
createShell preserves navigation presses across renders. A Namespace switch
returns Agent detail and creation to Agents.
3. Authorize the selected page resource
apps/controller/src/index.ts:perform, requireInstallationAdmin
packages/occ/src/index.ts:OpenClawController.listNamespaces, listAgents
Agents and Secrets use the selected Namespace's route. OCC authorizes Namespace reads and
filters Agents by exact read permission; Namespace listing filters its
Installation-wide collection. No readable selection means no Agent request.
GET /backends ignores selection and requires Installation administer before
returning safe startup summaries. Empty configuration returns an empty list;
missing wiring or failed dependencies return errors.
apps/controller/src/console/secrets.mts:renderSecrets lists metadata and reads
the exact Secret's consumers and advisory callerPermissions before showing
actions, as the Secrets guide describes. Agent
names come from the readable Agent list. Known references disable Delete; the
server rechecks them. console.mjs:takeLinkedSecret keeps a secret link's ID
until the page shows it. Create and replacement wait for Namespace readiness.
Mutations use the tracked request client and Secret API, never a Driver.
secrets.mjs:editValue masks new input, preserves whitespace (CRLF becomes LF),
and clears it after the request, dismissal, or view retention
(console.mjs:clearPasswordInputs). Pending requests block navigation and
dismissal. A denied write disables submission; an unconfirmed write cannot be
replayed from that dialog. secrets.mjs:confirmDelete offers an explicit exact
status read after an uncertain delete; a denial never confirms removal.
Replacement hands delivery to explicit Agent deployment or CredentialSource
update under the Secret delivery contract.
apps/controller/src/console/namespaces.mts:renderNamespaces tracks Name-rule-valid
POST /namespaces; OpenClawController.createNamespace authorizes and queues
provisioning; perform audits. Open Agents requires relisting. Closing refocuses
the opener; 403 disables it per account across navigation until Refresh (fromRefresh).
Pending writes prevent dismissal; 400s mark names; conflicts retain input.
Inspect unconfirmed responses.
namespace-deletion.mts blocks navigation/dismissal during writes. OCC rechecks
IAM/contents (20 blocker IDs/kind). 202: three-second polling; 404: remove row,
repair selection; 403: lost access. readStatus requires exact ID and scalar
NamespaceSchema status. Failed/invalid
reads close confirmation. Uncertainty shows Status unknown;
Refresh status restores confirmed status
and deletion actions.
Agents, Secrets and Create Agent read Namespace permissions. False agents.create or an Agent-refusing
Namespace status explains why, rechecking on return; failed reads keep creation open.
Refused GET /installation saves only drafts. agents/collection.mts polls provisioning Namespaces;
omission disables Create (NAMESPACE_MISSING), clears rows and feedback, closes
confirmations, and ends polling. Its note replaces empty states. Late outcomes are
generic, without replay; 401 expires the session. Focus: Search. Failed reads retry.
See Agent setup and provisioning.
4–6. Edit the Agent and access runtime files
Agent editing traces version rendering, channels and credentials; workspace files and stop and deletion trace those operations; Agent sharing traces policy writes.
channels/slack.mjs:supportSlack rejects shapes the editor cannot preserve. The
editing flow
owns DM policies and channel-only reply defaults. Admission snapshots native values;
Kubernetes prepareRevision carries them into openclaw.json without adding defaults.
apps/controller/src/console/agents/detail.mts:renderAgentDetail registers tabs
with console.mjs:loadPage. Within one Agent, Namespace and version, tabs replace
content; shell, native-admin and version controls remain mounted. Configuration
and revision reads are shared; direct Workspace URLs start neither. Refresh,
version changes and successful channel/authentication edits reload fully.
console.mjs:deniedReadsFor remembers audited Configuration/revision 403 paths
in owner-scoped tab sessionStorage. Later views show Configuration unavailable
without rereading; Retry forgets and rereads. Logout clears denials.
agents/detail.mts:renderTab reveals clipped tabs horizontally without changing
focus or vertical scroll. Its ResizeObserver repeats on resize, disconnects for
inactive/detached views, and re-arms on resume.
Tabs retain DOM/drafts except during pending/failed reads, passwords or mutations;
unmounted responses are ignored.
Draft captures
exclude passwords. Secret saves update the shared snapshot.
agents/detail.mts:boundSecretsDenied reuses Deploy's authorized names.
Configuration excludes Harness-only Secrets; Plugins/Credentials include them.
Epochs fence publication; rechecks clear names and refresh authorized reads.
Deploy keeps its last Secret operate answer until the refreshed one lands.
Late names preserve state/focus; metadata failures keep IDs; unknown grants stay generic.
Save handling owns receipts and draft retirement.
Open vN Logs opens the failed version from any version or draft. Logs reserves space; Stop and Delete stay put. Direct navigation focuses and scrolls to the output; selecting the tab leaves visible output in place. Recovery links scroll their version tab into view and focus its tab control, smoothly unless the page rebuilds or motion is reduced.
Runtime logs explains access. A false readLogs in the Agent
permission answer skips the audited log read. Status 403 hides Pod details, not
separately authorized logs. Denials last until logout; newly granted operate permits another
status read. Refused reads or downloads clear the rows; late successes cannot restore them or
download. Refusing allowed log text rereads status at once, superseding stale reads. Back
rebuilds refused views, else shows the checked page. Refresh keeps rows and cursor until
replaced; rate-limit notices update at the retry time without rereading. New streams or
rejected cursors clear rows.
7. Commit only the current response, or clear the view
apps/controller/src/console/console.mjs:loadPage, logout
After full-page navigation settles, loadPage focuses the destination heading if
focus fell to the body, using preventScroll to preserve return position. An Agent
opened from the list focuses its heading when the title renders. Tabs (one Tab
stop; arrows move) and focused controls keep focus. Navigation, Namespace changes, and logout invalidate
reads; generations reject late responses. Refocus coalesces events.
revalidateMountedAgent rechecks session, Namespace, Agent, version, and
permission answer;
unchanged access preserves controls, input, and saves.
Refocus sends a fresh GET. A repeated 403 on a never-readable draft Configuration
keeps the partial Agent; a version proved unreadable is not read again.
Lost access scrubs; Agent, session, and scope failures clear their protected views.
Other pages revalidate before reuse, including a shown 403 or 404; forms defer the check.
Returning to the tab restores focus after the unchanged view is admitted, if the control
remains and the reader hasn’t moved on. Draft save baselines and Namespace scopes stay separate.
Other admission failures clear affected content;
a current protected 401 clears all private state. pagehide clears
private DOM, previews, and drafts even for BFCache; persisted pageshow performs
a fresh load. Failure views show local reasons and req_<UUID> request IDs. The
only backend text they show is a refusal the API explains (a 400 naming the field or
rule, a typed 409 naming the state), set as text. Backend authorization denial clears every retained preview,
including other Namespace selections, because the permission is Installation-wide.
The detail action flow traces
acceptance, uncertainty, and replay guards. agents/deletion.mts:refreshStatus owns polling: 404 returns to Agents;
Postacceptance 403 stops polling without claiming completion.
See asynchronous cleanup.
Logout first hides private state, then calls the sign-out endpoint. Confirmed success or session inspection proving absence replaces history with login. An unconfirmed logout stays blocked with Retry. The authentication flow owns server revocation; this client never infers it from a network error.
Deploy the new version
Deploy new version on the Create new version draft sends bodyless
POST /namespaces/:namespaceId/agents/:agentId/deploy after the checks and
uncertain-response handling that Agent editing traces.
Operator-managed credentials ({ "method": "runtime" }) skip managed setup; OCC
does not validate host credentials. The server saves the version; its read-only
details open while Deployment activity follows it. Workspace reads check
gateway startup and file availability.
Debugging and Verification
- Match the displayed request ID to controller logs. A Namespace-only user cannot discover Backends; check Installation authority before treating that denial as a configuration problem.
- Browser suites use real Fastify, Better Auth, Native IAM, and in-memory storage. They verify navigation, isolation, creation, draft/history/channel editing, and authentication, not PostgreSQL persistence, Backend health, runtime dispatch, worker leases, or Compute effects.
node --test tests/browser/console-secrets.test.tsexercises Secret mutation, Agent references, denials, and lost replies with passive test Secret storage. Theconsole-secretsQA scenario selects real shipped storage; it requires the documented runtime and credential setup.- API tests cover safe discovery, permission boundaries, empty versus missing wiring, static MIME/allowlisting, and unchanged API JSON errors. See Testing for commands and the image smoke boundary.
