mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 20:35:39 -06:00
feat: portals — expose agent-run dev servers to the operator (#122536)
* feat(protocol): add portal methods and event Bump the reviewed protocol owner-module count from 55 to 56. * feat(gateway): add portal service and reverse proxy * feat(agents): add portal tool * fix(gateway): refine portal URL and proxy auth * refactor(gateway): keep portal helper types private * fix(gateway): declare portal transport service * test(gateway): satisfy portal proxy lint * test(gateway): narrow websocket payload types * refactor(protocol): compact portal schema exports * fix(gateway): export portal protocol types * feat(ui): add portals page * docs(gateway): add portals guide * fix(gateway): dial portal targets via localhost dual-stack Vite and other Node >=17 dev servers bind ::1 only for localhost, so a fixed 127.0.0.1 dial 502s on the default path. Use hostname localhost with family autoselection and rewrite Host to match. * fix(gateway): type portal dual-stack connection * fix: satisfy portal integration gates * fix(gateway): isolate portal cookie jars per target Cookies are hostname-scoped, not port-scoped, so the per-port origin split alone let Gateway plugin-auth cookies reach agent-run targets. Forward only cookies carrying this portal's own name prefix (stripped), rewrite target Set-Cookie names to the prefixed form incl. the WS 101 handshake, and drop Domain attributes. * fix(ui): detect unreachable portals behind proxied gateways Probe the portal origin from the browser (no-cors, 4s timeout) and show a recovery notice with the gateway-host URL instead of a dead iframe when only the gateway port is exposed (Serve/Funnel/reverse proxy). Docs: cookie isolation + reachability; zh-CN glossary entry. * test(ui): satisfy portal reachability lint * test(gateway): provide control UI request hosts * chore(protocol): regenerate after rebase * fix(gateway): namespace portal auth cookies by listener * fix(gateway): scope portal token URLs to write-capable clients The portal bearer token rides in the summary url/tokenQuery; portal.list is operator.read and portal.changed fans out to read subscribers, so a read-only client could harvest an openable URL. Make those fields optional, redact them from read-scope list responses, and drop them from every portal.changed broadcast; write/admin clients still receive them and the UI refetches the list on change. * docs(web): list the portals route * fix(gateway): type portal open credentials * docs(gateway): clarify portals PORT/PUBLIC_URL are agent-set Opening a portal creates only the proxy listener; the agent sets PORT and PUBLIC_URL in its own exec command, matching the portal tool contract. Removes the implication of an automatic env handoff. * chore(protocol): regenerate portal models * style(gateway): format portal method-order assertions Rebase union-merge left the portal.list assertion wrapped; oxfmt fits it on one line. * chore(plugin-sdk): refresh API baseline after rebase * chore(plugin-sdk): refresh API baseline after rebase * chore(protocol): refresh portal event order after rebase * chore(plugin-sdk): refresh API baseline after rebase * fix(gateway): pin portal referrer policy to no-referrer The portal URL carries its bearer token in the query, and upstream response headers are copied verbatim, so a target answering with Referrer-Policy: unsafe-url could leak that URL to every third-party origin it references. Force no-referrer after the copy and drop any inbound Referer that still carries the token before forwarding.
This commit is contained in:
committed by
GitHub
parent
4afccbdaa4
commit
cc2fc55f9b
@@ -113,8 +113,8 @@ const ownerModules = [
|
||||
...schemaModulesSource.matchAll(/^export \* from "\.\/schema\/([^"]+)\.js";$/gmu),
|
||||
].map(([, moduleName = ""]) => moduleName);
|
||||
check(
|
||||
ownerModules.length === 56 && new Set(ownerModules).size === ownerModules.length,
|
||||
"schema-modules.ts must contain one unique 56-module owner list",
|
||||
ownerModules.length === 57 && new Set(ownerModules).size === ownerModules.length,
|
||||
"schema-modules.ts must contain one unique 57-module owner list",
|
||||
);
|
||||
check(
|
||||
schemaModulesSource.split("\n").filter(Boolean).length === ownerModules.length,
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"openclaw.approval.resolved": "OpenClaw system-agent config approvals are a web/desktop operator surface; iOS has no operator-approval prompt.",
|
||||
"plugin.approval.requested": "Plugin approval prompts are not implemented on iOS.",
|
||||
"plugin.approval.resolved": "Plugin approval prompts are not implemented on iOS.",
|
||||
"portal.changed": "Control-UI-only surface; native apps have no portal viewer yet.",
|
||||
"presence": "Presence roster is a control-UI (web/desktop) surface; iOS does not render it.",
|
||||
"session.approval": "Native approval review uses exec.approval push/nudge delivery; the session-scoped approval stream is a Control UI chat surface.",
|
||||
"session.operation": "Chat UI derives run state from chat/agent events; no session.operation consumer yet.",
|
||||
@@ -44,6 +45,7 @@
|
||||
"openclaw.approval.resolved": "OpenClaw system-agent config approvals are a web/desktop operator surface; Android has no operator-approval prompt.",
|
||||
"plugin.approval.requested": "Plugin approval prompts are not implemented on Android.",
|
||||
"plugin.approval.resolved": "Plugin approval prompts are not implemented on Android.",
|
||||
"portal.changed": "Control-UI-only surface; native apps have no portal viewer yet.",
|
||||
"presence": "Presence roster is a control-UI (web/desktop) surface; Android does not render it.",
|
||||
"session.approval": "Native approval review uses exec.approval push/nudge delivery; the session-scoped approval stream is a Control UI chat surface.",
|
||||
"session.operation": "Chat UI derives run state from chat/agent events; no session.operation consumer yet.",
|
||||
|
||||
Reference in New Issue
Block a user