资讯动态

Ekko Studio App Relay 应用连接中继:从 LAN 直连到云端中转的完整授权与转发机制

发布时间:2026/9/23 16:27:14 来源:尧图企业网站定制
AI 应用人工智能AI Agent本地部署前端后端工作流自动化【免费下载链接】ekko-studioEkko Studio is a local-first AI workspace for multi-agent chat, coding, and visual workflows, available on desktop and the web.项目地址https://gitcode.com/gh_mirrors/he/ekko-studio点击查看免费下载导读Ekko Studiohermes-studio 仓库的 App Relay 是一个本地优先的多端连接方案它允许移动端 App 在不把 Studio HTTP 服务直接暴露到公网的前提下安全地访问本机 Studio 实例——既支持局域网LAN直连也支持通过云端中继跨越公网。本文以仓库文档 docs/app-relay.md 为骨架结合packages/server下的控制器、服务与存储层源码系统讲解 App Relay 的两种接入路径、一次性授权码机制、云端预连接preconnection与认领claim协议、云数据模型与隔离、在线状态与删除/重启语义、转发协议边界以及云端可观测性设计。读完本文你将掌握 Ekko Studio App 连接在 LAN 与云端两种模式下的完整握手流程、QR 码编码结构、安全边界与运维要点。一、App Relay 的定位与两种中继路由App Relay 的核心目标非常明确让移动端 App 能够访问 Ekko Studio 实例但无需将 Studio 的 HTTP 服务器暴露到互联网。它独立于 MCU微控制器的/global-agent连接——也就是说App 与 Studio 之间的连接通道与设备侧的全局 Agent 通道是完全解耦的两套机制。从源码上看这一独立性体现在两个层面控制器层packages/server/src/modules/studio/controllers/app-relay.ts中的connectAppRelayController专门启动 App relay 客户端测试 tests/server/app-relay-controller.test.ts 明确断言只启动独立的 App relay 并返回配对码且断开 App relay 不会触碰 MCU relay 注册表。连接层packages/server/src/modules/studio/services/app-relay/connection.ts以固定连接 IDAPP_RELAY_CONNECTION_ID app-relay管理唯一的宿主客户端与 MCU 的连接池完全分离。Studio 支持两条官方中继路由AppRelayRoute定义在 route.ts路由取值中继 URLofficial默认officialconfig.appRelay.url即官方https://api.ekkostudio.xyzcloudflarecloudflarehttps://cn.ekkostudio.xyz路由的持久化与读取通过应用配置完成// packages/server/src/modules/studio/services/app-relay/route.ts export type AppRelayRoute official | cloudflare export const DEFAULT_APP_RELAY_ROUTE: AppRelayRoute official export const CLOUDFLARE_APP_RELAY_URL https://cn.ekkostudio.xyz export async function getAppRelayRoute(): PromiseAppRelayRoute { return normalizeAppRelayRoute((await readAppConfig()).appRelayRoute) } export async function setAppRelayRoute(route: AppRelayRoute): Promisevoid { const current await readAppConfig() if (current.appRelayRoute route) return await writeAppConfig({ appRelayRoute: route }) }对应的配置字段为appRelayRoute?: official | cloudflare见 app-config.ts。几个重要的行为细节选中的路由被持久化在本地并作为信息性元数据informational metadata写入云端 QR 码中。App 端持有独立的路由设置扫描云端 QR 码永远不会改变 App 自身的路由配置——QR 中的r字段只是参考信息。当创建云端授权码时如果请求体携带了route且合法会立即通过setAppRelayRoute持久化该路由见 app-connections.ts。二、LAN 授权一次性授权码换设备绑定令牌2.1 创建 LAN QR 码已认证的 Studio 用户通过POST /api/app-connections/authorization-codes/lan创建一次性 LAN QR 码路由定义见 routes/app-connections.ts。QR 码内包含本地后端 URLbackend_url机器 IDmachine_id高熵一次性授权码authorization_code过期时间expires_at控制器实现app-connections.ts还做了几个细节处理QR 类型常量APP_CONNECTION_QR_TYPE hermes-studio.app-connection、版本APP_CONNECTION_QR_VERSION 1通过getLanBackendUrlForRequest(remoteAddress, requestOrigin, config.port)计算局域网后端地址并结合x-forwarded-proto头判断协议HTTPS/HTTP机器 ID 取自getDeviceId()最终响应同时返回结构化字段与qr_payloadJSON 字符串供客户端直接渲染二维码。2.2 授权码的安全属性授权码存储层的约束app-connections-store.tsTTL 五分钟export const APP_AUTHORIZATION_CODE_TTL_SECONDS 5 * 60只存哈希hashAppCredential使用 SHA-256 对授权码摘要后才落库明文不落盘记录创建用户created_by_user_id字段记录发起者单次消费consumeAppAuthorizationCode在消费时写入used_at与used_by_device_code重复使用会抛app_authorization_code_used过期即失效expires_at now时抛app_authorization_code_expired。2.3 App 交换令牌POST /api/auth/app-loginApp 携带以下信息调用POST /api/auth/app-login一次性授权码或用户名密码稳定的安装设备码device_code设备名称、品牌、型号device_name/device_brand/device_model控制器appLoginauth.ts的处理逻辑校验字段device_code与device_name必填且必须提供授权码或用户名密码二选一若走授权码consumeAppAuthorizationCode消费后findUserById找回授权用户若走口令authenticatePasswordUser校验活跃用户判定连接类型lan/cloudLAN 模式下cloud_user_id恒为 0云端模式必须提供正整数cloud_user_id签发绑定设备的app_access令牌并upsertAppConnection落库。令牌的细节middleware/auth.tsJWT 的audaudience固定为hermes-studioconst JWT_AUDIENCE hermes-studio载荷包含type: app_access、app_device_code、app_connection_type默认有效期 30 天DEFAULT_EXPIRES_SECONDS 60 * 60 * 24 * 30可通过环境变量HERMES_WEB_UI_AUTH_JWT_EXPIRES_IN调整支持s/m/h/d单位上限 365 天校验时通过inspectAppUserToken反向检查连接状态revoked/expired/active并支持HERMES_WEB_UI_AUTH_JWT_EXPIRES_IN等环境变量。此外手动 LAN 登录也受支持用户可以用活跃的 Studio 用户名密码直接换取 App 令牌而不必先生成授权码。三、云端预连接与认领QR 码背后的三段式握手3.1 创建云端 QRPOST /api/app-connections/authorization-codes/cloud已认证用户在云端模式下创建 QR 时Studio 先以Ed25519 机器身份签名登录云端的/app-relay命名空间再向云端请求一个预连接preconnection。控制器app-connections.ts的完整流程校验可选的route未指定则读取当前持久化路由ensureAppRelayHostClient(route)建立/复用宿主机 Socket.IO 客户端waitForConnected(8000)超时则返回 502app_relay_unavailable非刷新请求优先返回缓存中的预连接getCachedPreconnection否则本地createAppAuthorizationCode(userId)生成一次性授权码再通过client.requestPreconnection(authorizationCode, refresh, 8000, userId)向云端请求预连接错误映射刷新限流 429preconnection_refresh_rate_limited、刷新次数耗尽 409preconnection_refresh_limit_reached、预连接过期 410preconnection_expired、其余 502。3.2 QR 码的紧凑编码结构文档给出的云端 QR 内容是一个 7 字段的紧凑 JSON{ t: hsac, v: 1, c: cloud, m: hwui_..., p: uuid, k: high-entropy secret, e: 0, r: official }各键含义与生成位置对照如下源码 app-connections.ts键含义值来源t类型 type固定hsachermes-studio app connectionv版本 version固定1c连接类型 connection typecloudm机器 ID machine ID宿主机 Ed25519 身份派生的device_id形如hwui_...p预连接 ID preconnection ID云端分配的 UUIDk匹配码 matching code高熵密钥用于双向匹配e过期时间 expiryUnix 秒级时间戳rStudio 中继路由 routeofficial或cloudflare紧凑编码的意义在不降低匹配码熵值的前提下显著压缩 QR 密度——二维码容纳的数据越多像素越密集、越难扫描保持高熵匹配码不变仅用短键名削减非关键字段的体积是兼顾安全与可用性的设计。3.3 刷新冷却与生命周期限制匹配码 5 分钟过期刷新冷却 10 秒且每个预连接最多刷新 3 次每次成功刷新都会使上一枚匹配码作废getCachedPreconnection会同时检查expiresAt与hardExpiresAt见 client.ts未被匹配的宿主连接有绝对 15 分钟生命周期超时由云端主动断开UI 永不自动刷新已过期的 LAN/云 QR过期后 QR 保持显示并覆盖已过期遮罩直到用户手动请求刷新。3.4 认领协议claim已登录的 App 将 QR 字段连同其稳定的设备身份发送到POST /api/app/connections/claim注意这是 App 面向云端的接口与 Studio 侧的/api/app-connections/**不同。云端随后精确定位到对应 Studio 宿主 socket请求该 Studio 交换服务端一次性授权码即第 2.2 节中本地生成的授权码只有当两侧都成功后云端才创建或重新激活正式连接。其中服务端一次性授权码的交换由AppRelayClient.authorizeCloudConnection完成client.ts宿主收到云端的connection.authorize事件后校验preconnectId与matchingCode是否匹配本地挂起的预连接匹配则代 App 调用本地/api/auth/app-login并把返回的studioToken、studioUserId、profiles、机器信息回传给云端完成授权。四、云端数据模型与隔离4.1 两张核心表云端将物理手机安装与正式的手机↔Studio关系分开存储app_device物理手机安装实例app_device_connection正式的手机到 Studio 的关系记录。正式连接以(appDeviceId, machineId)为唯一键因此一台 Studio 可连接多台手机一台手机可连接多个 Studio一个设备码device code全局绑定一个App 账户。4.2 限额钩子entitlement hooks账户限额通过独立的 entitlement hooks 检查。当前这些钩子返回无限值未来如需引入订阅或套餐限额无需改动连接表结构或认领协议——这是典型的扩展点后置设计。值得注意的约束物理设备限额必须统计app_device而不是连接行。因为连接行是多对多关系按行计数会严重高估设备数量。4.3 三重凭据与身份派生云端 App socket 需要三个相互独立的凭据才能建立App 账户访问令牌access token正式连接的connectionId与 App 设备码随机生成的、按连接唯一的凭据——云端只存其哈希。同时云端从正式连接行派生machineId而不是信任 App 握手中自报的machineId防止身份冒用。本地侧的隔离仍然保留Studio 本地用户令牌保持独立继续在转发请求时执行正常的 Studio 用户/Profile 权限校验packages/server/src/modules/studio/services/app-relay/server.ts的authorized()每次转发都会重新调用inspectAppUserToken检查revoked状态。五、在线状态、删除与重启语义5.1 在线状态查询列表轮询GET /api/app-connections控制器listAppConnectionsController同时聚合两类在线状态——LAN 侧读取本地 relay 的在线表云端侧读取云端连接池getAppRelayClient(APP_RELAY_CONNECTION_ID)?.isCloudDeviceOnline(deviceCode, cloudUserId)。Studio 页面在可见期间轮询该接口。单连接状态App 使用GET /api/app/connections/:connectionId/status。云端先验证该连接属于已认证账户然后从Studio 宿主 socket 池读取machineOnline从正式 App 连接 socket 池读取appOnlineSocket.IO 心跳丢失即从对应池中移除该 socket响应不可缓存并额外返回该机器最新一次签名注册元数据中的Hermes Agent 与 Hermes Web UI 版本。注意这是存在性查询presence lookup不是对 Studio 机器发起的新入站请求。5.2 删除语义删除 Studio 连接时本地先创建撤销墓碑revocation tombstoneLAN App直接通知并断开云端删除client.revokeCloudConnection(deviceCode, cloudUserId)撤销正式云端连接并断开其 socket成功后调用markCloudAppConnectionRevocationSynced标记同步完成见 app-connections.ts离线 App下次连接时被拒绝并收到app_connection_deletedApp 端弹出确认对话框后移除该设备记录。5.3 重启与身份保持只要本地存在任何活跃的云端 App 记录Studio启动时即连接云端Socket.IO 对瞬时断线无限重连reconnectionAttempts: Infinity见 client.ts云端恢复正式连接快照connection.snapshotStudio 将其与本地撤销墓碑对账reconcileConnectionSnapshot离线期间的云端删除最终会被传播见 client.ts开发模式开发 Web UI 宿主使用独立的持久化 Ed25519 身份并注册为非抢占式non-preemptive。因此运行npm run dev会创建一个独立的 Web 端点而不会替换打包版桌面 socket 或覆盖其机器元数据生产构建保留传统机器身份legacy machine identity已有桌面连接保持兼容生产宿主保留现有的抢占takeover行为——shouldReplaceExistingAppRelayHost()在NODE_ENV production时返回true见 connection.ts重启的桌面端可以接管陈旧连接。六、转发协议边界哪些请求能穿过中继6.1 白名单策略无论 LAN 还是云端App 面向 RPC 的事件形状完全相同区别只在于传输路径LANApp → Studio 本地 relay → 本地 HTTP 或 Socket.IO云App → 云端 relay → 已签名的 Studio 宿主 socket → 本地 HTTP 或 Socket.IO。转发边界由宿主机侧的AppRelayClient与本地 relay 的LocalAppRelayServer共同执行client.ts 与 server.tsHTTP RPC只接受 Studio 的/api/**与/health路径normalizeRelayPath方法白名单GET / POST / PUT / PATCH / DELETE / HEAD请求头白名单accept、accept-language、authorization、content-type、if-match、if-none-match、range、x-hermes-profile、x-request-id、x-app-access-token、x-session-share-token、x-group-agent-request-secret、x-expected-sha256等请求/响应体上限 20 MiB控制面MAX_CONTROL_REQUEST_BODY_BYTES/MAX_CONTROL_RESPONSE_BODY_BYTES 20 * 1024 * 1024云端媒体文件默认 30 MiB可被云端relay.limits.updated动态调整Socket RPC仅接受/chat-run与/group-chat命名空间本地 relay 另支持/terminal、/workflow、/group-chat-agent-relay且每个命名空间都有独立的事件白名单例如/chat-run仅放行run、resume、abort、approval.respond、clarify.respond、calendar.respond等受控事件桥接绑定每个 bridge 严格绑定一个正式 App 连接 一个宿主 socket每次转发前重新校验正式连接授权authorized()每次处理请求都会重新检查令牌状态server.ts。此外文本响应按 Content-Type 前缀判定application/json、text/等二进制大文件走分块下载会话app.http.download.chunkrun.completed等非流式场景会把累积的message.delta/reasoning.delta汇总后一次返回——这些细节保证了移动端在弱网环境下的可用性。七、云端可观测性与审计云端将结构化的 JSON 运维日志写入 stdout并严格执行脱敏一律脱敏认证材料、密码、匹配码、授权码、连接凭据关键生命周期事件入审计表app_connection_audit_log记录关键的 App 连接生命周期事件设备码只以 SHA-256 哈希形式存储与本地授权码的hashAppCredential策略一致。审计表的运维参数参数默认值说明保留期30 天通过环境变量APP_AUDIT_RETENTION_DAYS调整最大行数1,000,000 行通过环境变量APP_AUDIT_MAX_ROWS调整清理策略是三合一触发启动时、每 6 小时、每新增 1,000 条审计记录后各执行一次且每次按有界批次bounded batches删除避免长事务与锁竞争。超级管理员可通过/admin/appConnectionAudit/getList查询审计历史。八、关键接口速查与源码索引接口作用源码位置POST /api/app-connections/authorization-codes/lan创建 LAN 一次性授权码QRcontrollers/app-connections.tsPOST /api/app-connections/authorization-codes/cloud创建云端预连接QRcontrollers/app-connections.tsPOST /api/auth/app-login授权码/口令换设备绑定app_access令牌controllers/auth.tsGET /api/app-connections聚合 LAN/云在线状态列表controllers/app-connections.tsDELETE /api/app-connections/:id删除连接LAN 直断 / 云端撤销controllers/app-connections.ts宿主 Socket.IO 客户端预连接、授权、转发、快照对账services/app-relay/client.ts本地 LAN relay局域网内 App 连接与转发services/app-relay/server.ts授权码/连接存储TTL、哈希、墓碑、单次消费repositories/app-connections-store.ts路由选择official / cloudflare 持久化services/app-relay/route.ts机器身份Ed25519 签名与身份派生services/app-relay/connection.ts相关测试覆盖可参见 tests/server/app-relay-controller.test.ts、tests/server/app-relay-client.test.ts、tests/server/app-relay-server.test.ts、tests/server/app-connections-store.test.ts 与 tests/server/app-connections-auth.test.ts。九、安全设计小结综合全文App Relay 的安全模型可以归纳为几个相互独立的防线不暴露端口公网访问永远终止于云端中继Studio 的 HTTP 服务只监听本机双因素匹配一次性授权码SHA-256 哈希存储、5 分钟 TTL、单次消费 高熵匹配码5 分钟 TTL、刷新作废前码三重云端凭据账户令牌 连接 ID/设备码 按连接随机的哈希化凭据任一缺失都无法建立 socket身份不信任客户端machineId由云端从正式连接行派生宿主与 App 均不能自报身份转发面最小化路径、方法、头、命名空间、事件五重白名单 20 MiB 体上限 每次转发的重新授权删除即失效本地墓碑 云端撤销 快照对账保证离线删除最终一致日志零明文认证材料与各类密钥全部脱敏设备码仅存哈希。这套机制既保证了移动端随时随地访问本地 Studio 的便利性又把安全边界收敛到了即使云端被攻破也无法直接操纵 Studio 用户数据的层级——本地用户令牌与 Profile 权限始终在每次转发时独立校验。赞分享AI 应用人工智能AI Agent本地部署前端后端工作流自动化【免费下载链接】ekko-studioEkko Studio is a local-first AI workspace for multi-agent chat, coding, and visual workflows, available on desktop and the web.项目地址https://gitcode.com/gh_mirrors/he/ekko-studio点击查看免费下载相关推荐Ekko Studio Group Agent 连接隔离与自动恢复机制解析Ekko Studio Group Agent 连接隔离与自动恢复机制解析 本篇技术指南深入剖析 Ekko Studio 中 Group Agent群聊智能体AI 应用人工智能AI Agent本地部署前端后端工作流自动化Paseo Hub 与 Daemon 关系从显式连接、权限授权到断连撤销的完整指南Paseo Hub 与 Daemon 关系从显式连接、权限授权到断连撤销的完整指南 Paseo Hub 是位于 Daemon 之上的编排层本指南围绕 docAutomatisch 连接 Reddit从创建应用授权到 OAuth 连接的配置与源码级解析Automatisch 连接 Reddit从创建应用授权到 OAuth 连接的配置与源码级解析 本指南面向在 Automatisch 中启用 Reddit 集工作流自动化后端前端低代码任务调度创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价