| created | 2026-09-19 |
|---|---|
| updated | 2026-10-27 |
| last_updated_session | authoring-run/f3434faa-8624-4f34-83a2-521047f80329 |
The console checks exact-Agent administer access and returns a stable native admin URL for the selected gateway version. The Agent host uses the OCE session cookie; OCC rechecks access before proxying HTTP and WebSocket traffic.
- Trigger: Console opens an Agent native admin URL, or
scripts/first-agent.mjs --control-uicreates a compatible local Compose/OpenShell Agent. - Source:
scripts/first-agent.mjs:nativeConfiguration - Source:
apps/controller/src/console/agents/native-admin.mjs:renderNativeAdminAccess - Source:
apps/controller/src/http/native-admin.ts:resolveNativeAdminAvailability - Assumptions: The API has a valid controller session,
agentNativeAdmin.enabledis true,agentNativeAdmin.domainandagentNativeAdmin.sharedCookieDomainare configured, Better Auth emits the shared session cookie at that parent domain, and private Agent gateway routing can return aComputeDriver.getGatewayEndpointvalue.
graph TD
L["Local first-Agent selects Control UI"] --> L2["Create Agent, derive exact origin,<br/>and save Configuration generation 2"]
L2 --> A
A["Console opens Agent detail tab"] --> P{"administer answer false?"}
P -->|yes| PH["Hide card, no request"]
P -->|"no or unknown"| B["GET exact Agent native-admin status"]
B --> C["Resolve OCC session and exact Agent administrator principal"]
C --> D{"Exact Agent exists?"}
D -->|no| E["Return protected-route error"]
D -->|yes| F{"Installation enabled?"}
F -->|no| G["Return disabled"]
F -->|yes| H["Call resolveNativeAdminAvailability"]
H --> I{"Selected version available?"}
I -->|no| J{"Authorized stopped Agent without a selected version?"}
J -->|yes| X["Return stopped without an origin"]
J -->|no| Y["Return unavailable"]
I -->|yes| K{"Agent desired running?"}
K -->|no| L["Return stopped with derived origin"]
K -->|yes| R2{"Newer version replacing it on an exclusive Compute Driver?"}
R2 -->|yes| Y
R2 -->|no| M{"Native config supported?"}
M -->|no| N["Return unsupported with derived origin"]
M -->|yes| CO{"Compute ID and implementation match selected version?"}
CO -->|no| CF["Return 503 dependency error"]
CO -->|yes| EP{"Private gateway endpoint supported?"}
EP -->|no| N
EP -->|yes| O["Return available Agent URL"]
O --> P["Browser opens derived Agent host with shared OCE session cookie"]
P -->|HTTP| Q["OCC resolves host to exact Agent using platform state"]
P -->|WebSocket| V["OCC guards client socket during admission"]
V --> Q
Q --> R{"Shared session and exact Agent administer still valid?"}
R -->|no| S["Reject request; audit exact-Agent authorization denial"]
R -->|yes| T["Resolve current version, native config and Compute owner"]
T -->|owner mismatch| RF["Refuse before key lookup or gateway connection"]
T -->|matching owner| U["OCC strips browser credentials and proxies HTTP to private gateway"]
T -->|WebSocket client reset| VB["Stop before gateway connection"]
T -->|WebSocket client connected with matching owner and exact Origin| VC["Proxy 101 upgrade with version lease"]
U -->|HTML preview with sandbox routing enabled| W["Browser loads public shell from separate preview origin"]
W --> Z["Envoy sandbox listener routes to Agent sandbox port"]
Z --> AA["Native UI delivers HTML into runtime-isolated iframe"]
scripts/first-agent.mjs:loadLocalInstallation
scripts/first-agent.mjs:nativeConfiguration
scripts/first-agent.mjs:ensureManagedConfiguration
With launcher-owned Compose/OpenShell browser state, the helper saves disabled
Configuration generation 1, creates the Agent, then derives its origin. Before
deployment, generation 2 enables controlUi, pins origins, and grants
occ-workspace-files trusted-proxy operator.admin with device auto-approval.
An existing helper-owned name cannot change this selection.
apps/controller/src/console/agents/native-admin.mjs:renderNativeAdminAccess
Agent detail reads ${path}/native-admin only when the Agent permission answer
allows administer or is unknown. Disabled or denied access stays hidden;
other refusals explain why. Only available enables the header Open Agent UI link, opening directly
with noopener noreferrer. An audited 403 in the session owner’s tab
sessionStorage suppresses reads until logout, sign-in, or a new tab. Changes to
activeRevisionId or desiredRuntimeState refresh access, coalescing concurrent
requests into one follow-up. The target description names the version selected for
service; browsing a historical version does not change the destination. Stopped and
unsupported states offer Open draft. An unavailable deployment offers View deployment
when a readable version exists. Other unavailable states keep Refresh access.
The warning describes Gateway managed mode and OCE overwriting changes made while it is off; durable configuration belongs in OCE.
apps/controller/src/http/native-admin.ts:nativeAdminStatusOperation
createNativeAdminAccess owns host interception, upgrades and socket shutdown.
A getter supplies the initialized controller after bootstrap. Shared route
admission remains in apps/controller/src/index.ts.
GET /namespaces/:namespaceId/agents/:agentId/native-admin requires exact-Agent administer through admit and resolveIdentity. read and operate do not grant access. Even disabled is returned only after authorization and existence checks.
apps/controller/src/http/native-admin.ts:getNativeAdminStatus
apps/controller/src/http/native-admin.ts:resolveNativeAdminAvailability
packages/occ/src/index.ts:getAdministerableActiveAgentRevision
After the authorization and existence checks above, enabled access requires a public origin/domain and getAdministerableActiveAgentRevision.
ResourceConflictError for an authorized stopped Agent without a selected version becomes only status: "stopped", including new Agents and completed stops. NoActiveAgentRevisionError for a running Agent awaiting activation returns unavailable. Other dependencies, including IAM/State outages, remain 503 before gateway routing. The console offers deployment checks and refresh for the selected version, independently of the viewed snapshot.
With a selected version, OCC derives its native target. Non-running Agents return stopped with host/origin; a newer exclusive replacement returns unavailable. Unsupported trusted-proxy auth, admin scopes/device auto-approval, controlUi.enabled, exact allowedOrigins, or host-header/device-auth settings return unsupported with the target.
Then the selected Compute Driver's id and implementation must match revision.compute. Selection changes do not migrate versions. A mismatch raises status 503 DEPENDENCY_UNAVAILABLE before endpoint/key/transport. Matching ownership proceeds to endpoint validation; a missing or unclean private wss: endpoint returns unsupported. Only available carries gatewayBase, which nativeAdminAvailabilityData omits from browser responses.
apps/controller/src/gateway/native-admin.ts:nativeAdminTarget
apps/controller/src/gateway/native-admin-host.mjs:deriveNativeAdminHost
deriveNativeAdminHost hashes Installation, Namespace, and Agent IDs into an opaque hostname under the configured domain. nativeAdminTarget substitutes it in publicOrigin and returns /.
Native-host admission resolves the hash from Installation, Namespace, and Agent state. Unknown hosts, wrong suffixes, deleted Agents, and non-unique matches fail closed.
nativeAdminGatewayHttpBase requires a wss: endpoint without userinfo, query, or hash, then converts it to https: preserving authority and the Agent base path. Workspace-file WSS behavior stays unchanged.
apps/controller/src/auth/index.ts:createControllerAuth
When enabled, native admin passes the configured shared cookie domain to Better Auth. The controller validates that the console and Agent hosts fit that parent on DNS-label boundaries, rejecting public suffixes and malformed domains. It never infers a parent domain. When disabled, the console uses the host-only openclaw_occ cookie. A shared-domain cookie cannot use the __Host- prefix; one canonical name and scope prevents ambiguous duplicates.
apps/controller/src/http/native-admin.ts:interceptNativeAdminHttp
The onRequest hook intercepts the native admin domain before routing, so an
Agent origin cannot expose console or controller routes. OCC authenticates the
shared session, resolves the IAM identity and exact Agent, requires administer,
then checks the selected version and native configuration. An attributable IAM
denial records the human and Agent in the denial audit.
Admission owns one audit attempt per such denial, also when it arrives after the
five-second admission wait, a client disconnect or the shutdown wait. A timely HTTP
denial waits for its append before returning 403; a failed append or admission
timeout returns 503.
Without a valid session, a user-activated, top-level HTML GET to /,
/chat/main, or /chat/main/<32 hex digits> gets a 302 to the configured
console. Its fragment carries only the opaque host key, which the console resolves
after sign-in and exact-Agent authorization. OCC drops queries and session suffixes.
API requests, writes, WebSockets and permission denials never redirect. Reload an
open native tab after expiry.
A signed-in administer denial on that navigation stays HTTP 403. The page does
not name the Agent and links Go to your Agents to /console/agents on the
configured public origin. A JSON refusal names the administer grant but not
the Agent, the same for an unknown host; WebSocket refusals stay unchanged.
Status and proxy checks reuse one Better Auth lookup for identity, expiry and
attribution. Each WebSocket lease checks the current session again. After handling
stopped, replacing or unsupported Agents, admission rechecks both Compute owner
fields. A mismatch returns HTTP 403 or closes an upgrading socket without an
HTTP response, before gateway-key lookup or connection. A failed lease closes
both sockets.
apps/controller/src/gateway/native-admin-proxy.ts:proxyNativeAdminHttp
The HTTP proxy validates and normalizes the request URL, which must not exceed 4,096 characters, rejects missing or nonmatching Origin on non-GET/HEAD requests, strips browser cookies, service keys, forwarding headers, native identity, native scopes, and upstream Set-Cookie, rejects service-worker script requests, rewrites same-upstream Location values to the Agent origin, appends worker-src 'none' to proxied Content Security Policy, and forwards to the private https: gateway base. A 502 from the gateway's user-photo route (/api/users/<id>/avatar) becomes an empty 404: it means OpenClaw could not fetch a Gravatar fallback, which a dedicated Gateway without internet egress never can, and the UI shows initials for both. The native gateway never receives the OCE session cookie.
apps/controller/src/http/native-admin.ts:handleNativeAdminUpgrade
The API process intercepts upgrade before Fastify routing. It accepts only derived Agent hosts and tracks active sockets so preClose destroys them during shutdown. Before awaiting shared-session and exact-Agent admission, it handles client socket errors; a TCP reset during admission therefore does not raise an uncaught socket error. A refused socket closes without waiting for its denial audit, which still runs after a reset. An allowed admission checks for client destruction and shutdown before and after resolving the private transport context, so late success cannot open a gateway connection. A connected client proceeds with the selected version.
apps/controller/src/gateway/native-admin-proxy.ts:proxyNativeAdminWebSocket
The WebSocket proxy requires a non-null exact Agent Origin, forwards a sanitized upgrade request to the private https: gateway base, and only connects the browser after the upstream returns 101. onConnect appends openclaw.agents.native_admin.websocket.connect; onClose appends openclaw.agents.native_admin.websocket.close. The websocket.connect audit record includes connectionId; the matching websocket.close audit record reuses that connectionId and includes closeReason, whose value distinguishes lifecycle, revocation, dependency, client, upstream, and shutdown paths. A timer rechecks the shared-session admission path every 25 seconds, with each check subject to a five-second timeout. Failed, denied, timed-out, or version-changed lease checks close both sockets. Denial closure does not wait for the append; a late attributable lease denial still gets its single audit attempt. An authorized reconnect uses the selected version. Native chat does not renew the OCE session.
Admission and close audits share one five-second shutdown drain. Work still pending
afterwards logs native_admin.pending_work_unresolved with its count and keeps its
audit attempt while the process lives. The drain neither cancels producers nor
guarantees durable delivery; process exit can prevent or interrupt an audit attempt.
apps/controller/src/drivers/compute/kubernetes/index.ts:gatewaySandboxConfiguration
apps/controller/src/drivers/compute/kubernetes/index.ts:reconcileGatewayRoute
With operator sandbox routing configured, preparation and activation render the Agent's stable preview origin and adjacent sandbox port in its native document. The worker reconciles its GET/HEAD route, public-shell SecurityPolicy and Agent-scoped backend ingress policy under the serving version. A separate Helm listener admits only configured ingress peers, and the backend targets the sandbox port instead of the admin port. The preview domain is outside the shared session cookie scope. Native UI still reads private file content through its authenticated connection, then delivers it to the runtime's isolated iframe. The sandbox listener serves public shell and registered renderer assets; runtime CSP and nested frame isolation remain responsible for executing generated content. See the routing contract.
For opted-in Kubernetes gateways, KubernetesComputeDriver.privateStateInitContainer
copies the admitted configuration from /etc/openclaw-managed/openclaw.json to
/runtime-state/home/.openclaw/openclaw.json on the init container's writable
volume. The gateway mounts that same home subdirectory at /home/node, so native
edits remain Pod-local and the next Pod starts from the admitted configuration.
The init container cannot write through the gateway's later mount path.
apps/controller/src/drivers/compute/kubernetes/runtime-entrypoints.ts:configureNativeWorkerProfile
reuses only the exact generated worker profile after a container restart;
conflicting settings fail. KubernetesComputeDriver.gatewayPrivateStateVolumeMounts
retains client/ and its migration receipt with the Gateway database. Native
admin Configuration remains Pod-local.
AGENT_NATIVE_ADMIN_INVALIDat startup points to native admin enablement, the Agent domain, or a missing shared cookie domain. A bad shared cookie domain or public origin reportsAUTH_BASE_URL_INVALID, short auth secret materialAUTH_SECRET_INVALID, and a missing gateway API key pathGATEWAY_API_KEY_UNAVAILABLE.disabledmeans the Installation has not enabled the feature.stoppedmeans the exact Agent is not desired running. Its response has no origin or version after stop reconciliation clears the selected version, or before the first deployment.unavailablemeans a desired-running Agent has no selected version, or a newer version on a Compute Driver withrequiresStoppedPredecessorshas stopped the active workload. If replacement fails, the old version remains selected without a workload.unsupportedmeans the selected Compute Driver, gateway endpoint, or native trusted-proxy/control UI configuration cannot support the selected version.- Wrong or unknown Agent hosts fail before gateway proxying. Check the derived host calculation, Agent lifecycle state, and
agentNativeAdmin.domain. - Browser requests contain no native-admin exchange, callback, verifier, or Agent-specific session-cookie traffic.
- IAM denial audits should appear for attributable denied status checks, proxy admission, and WebSocket lease renewal, with the human principal and exact Agent target preserved.
- A TCP reset during WebSocket admission leaves the API running and preserves a later authorization-denial audit without opening an upstream connection.
- WebSocket connect and close audits share
connectionId; close audits add acloseReasoncategory from step 7. - Service-worker registration failure is expected: the HTTP proxy rejects
Service-Worker: scriptrequests and addsworker-src 'none'to proxied responses. - Browser tests cover panel visibility, warnings, availability, and URL opening. Integration proof covers shared-cookie admission, denied service keys and unknown hosts, assets, WebSocket leases and reconnects, and a reversible native edit.
- Agent native admin UI
- Platform console
- Gateway routing with Envoy
- Deploy native admin UI access
- Workspace files flow
[keep this for the user to add notes. do not change between edits]
-
2026-10-27 09:30: Document direct Open Agent UI navigation and availability recovery. Accompanying uncommitted changes. (authoring-run/f3434faa-8624-4f34-83a2-521047f80329 - 24a35dff22e1efb2a9b7058f5c90d6d9e2f9caf7)
-
2026-10-26 22:00: Close refused sockets and denied leases before their denial audit settles; track admission through its audit attempt and bound the shutdown drain. (wf-land-1707 - #1707)
-
2026-10-26 01:00: A JSON
administerdenial names the grant, not the Agent. -
2026-10-25 12:00: Skip the status read when the permission answer denies
administer. -
2026-10-24 23:25: Documented the authenticated browser 403 Agents link.
-
2026-10-20 14:54: Extract persistence repair; trim repeated prose. (agent:roboclaw:dashboard:830a6ba7-3357-4ae0-9037-95d98205608c - 1ce18eeea10c83c856c8c08f6b0815e4b39308e0)
Agent native admin documentation history preserves the older dated entries.