Skip to content

feat(agent-device): 新增办公设施实验抽象#272

Open
Xy718 wants to merge 6 commits into
mainfrom
xy718/agent-device-impl
Open

feat(agent-device): 新增办公设施实验抽象#272
Xy718 wants to merge 6 commits into
mainfrom
xy718/agent-device-impl

Conversation

@Xy718
Copy link
Copy Markdown
Contributor

@Xy718 Xy718 commented May 30, 2026

变更内容

  • 新增 Agent Device / 办公设施实验抽象,覆盖预设、设备实例、授权、租约、调用和审计链路。
  • 接入首个 Browser Pilot 预设,并通过 Agent Device Gene 提供受控操作入口。
  • 在 Portal 办公室 2D / 3D 中展示设备节点,支持放置、移动、重命名、删除和详情抽屉。
  • 补齐 Agent 间委托授权、父授权撤销级联、成员移除后的设备绑定清理,以及 CE 审计对 user / agent 操作的记录。
  • 按“必须拓扑可达,remote 仅预留扩展口”的边界接入现有 Corridor 拓扑。

验证

  • DEBUG=true DATABASE_URL=postgresql+asyncpg://nodeskclaw:nodeskclaw@localhost:5432/nodeskclaw_test uv run pytest tests/test_agent_device_service.py tests/test_ce_audit_handler.py tests/test_workspace_agent_node_card_sync.py
  • uv run ruff check alembic/versions/fccc870bdf4d_add_agent_device_models.py app/api/agent_devices.py app/api/corridors.py app/api/router.py app/api/workspaces.py app/core/config.py app/data/gene_scripts/deskclaw_agent_device.py app/main.py app/models/__init__.py app/models/agent_device.py app/models/workspace_member.py app/schemas/agent_device.py app/services/agent_device_gene_sync_service.py app/services/agent_device_provider.py app/services/agent_device_service.py app/services/audit_handler.py app/services/corridor_router.py app/services/runtime/registries/node_type_registry.py app/services/workspace_service.py tests/test_agent_device_service.py tests/test_ce_audit_handler.py tests/test_workspace_agent_node_card_sync.py
  • npm run build(nodeskclaw-portal)
  • GitHub Actions:后端 Ruff / Pytest、Portal Build / Test 均通过

备注

  • 本期只启用 Browser Pilot 作为第一批预设,设备抽象和治理链路按可扩展形态落地。
  • remote 入口本 PR 仅保留为未来 Corridor / Topology 扩展位,不作为本期独立可达路径。

@Xy718 Xy718 marked this pull request as ready for review May 30, 2026 19:59
@chenchenchenchencj
Copy link
Copy Markdown
Collaborator

我先审了一轮 Agent Device 核心链路,当前建议合并前至少处理下面几项:

  1. Provider action 白名单没有在 Controller 层强制校验

    • nodeskclaw-backend/app/services/agent_device_service.py:1185
    • nodeskclaw-backend/app/services/agent_device_provider.py:78
    • 当前 invoke_device 只校验 agent 有 invoke scope 和 active lease,然后把任意 action 原样转发给 Browser Pilot provider。preset 里虽然声明了 capability_schema.actions,但服务端没有使用它做强制校验。
    • 建议:转发前校验 action in preset.capability_schema["actions"],非法 action 返回明确业务错误;后续再补 payload schema 校验。
  2. 自动安装的 Agent Device Gene 可能误删用户后来手动保留/安装的 gene

    • nodeskclaw-backend/app/services/agent_device_service.py:1365
    • nodeskclaw-backend/app/services/agent_device_service.py:1471
    • was_preexisting 由历史 binding 推导。如果第一次是自动安装,之后用户手动安装或希望保留这个 gene,撤回最后一个 device binding 时仍可能走 uninstall_gene
    • 建议:撤回时重新检查当前 InstanceGene 的来源/状态,或增加明确 install source,不要只靠 binding 历史布尔值决定是否卸载。
  3. 租约并发 acquire 可能把唯一约束冲突暴露成 500

    • nodeskclaw-backend/app/services/agent_device_service.py:1021
    • 现在是 check-then-insert,迁移里有 active lease 唯一索引。两个 agent 并发 acquire 时,一个请求可能在 flush/commit 触发 IntegrityError
    • 建议:加事务锁,或捕获 IntegrityError 并转换成 409 lease_conflict
  4. 人工创建带 parent_grant_id 的 grant 时没有继承范围校验

    • nodeskclaw-backend/app/services/agent_device_service.py:601
    • Agent 委托路径会校验 child scopes 是 parent scopes 子集,但 user 创建 grant 且传 parent_grant_id 时只校验 parent 存在/有效,没有校验 scopes、expires_at、can_delegate 是否受 parent 限制。
    • 建议:要么禁止 user 创建 grant 时传 parent_grant_id,要么复用同样的继承校验。
  5. 前端一键授权默认给 Agent 全权限且允许继续委托

    • nodeskclaw-portal/src/components/workspace/DeviceDetailDrawer.vue:109
    • 当前 UI 直接授予 discover/lease/invoke/delegatecan_delegate: true。这可能超出用户对“授权使用设备”的直觉。
    • 建议:默认不授予 delegate,或 UI 明确拆分权限选择。

建议补充测试:非法 action 被拒绝、自动 gene 不误删手动安装、并发 acquire 返回业务冲突、parent grant 子授权不能超过父授权范围。

@chenchenchenchencj
Copy link
Copy Markdown
Collaborator

另外看到一个 PR 范围问题,建议在合并前处理:

这个 PR 标题和主体是 feat(agent-device): 新增办公设施实验抽象,但 diff 里还包含了不少看起来不属于 Agent Device 的行为变更,例如:

  • nodeskclaw-backend/app/api/workspaces.py 里的 /messages/clear runtime context 清理逻辑;
  • get_performance / get_workspace_token_usage 的 LLM 用量聚合口径变更;
  • app/core/config.pyapp/main.py 里的 GeneHub/DeskHub registry 默认配置调整;
  • app/main.py 里新增 resume_deleting_instances 启动恢复逻辑;
  • LLM_ATTRIBUTION_SECRET 配置项。

这些改动可能本身是合理的,但它们会让 Agent Device PR 的评审、回滚和线上问题定位变复杂。尤其 Registry 默认值和 /messages/clear 都是已有核心行为,不应该被一个办公设施协议 PR 顺带改变。

建议:

  1. 把非 Agent Device 的变更拆到独立 PR;或
  2. 如果这些是 Agent Device 必需依赖,在 PR 描述里明确说明依赖关系和行为变化,并补对应测试。

否则后续如果 Agent Device 需要回滚,会把这些无关行为一起回滚掉,风险会比较高。

@chenchenchenchencj
Copy link
Copy Markdown
Collaborator

再补一个和 Agent Device 直接相关的模板链路问题:

当前办公设施节点没有进入办公室模板保存/部署流程。

相关位置:

  • nodeskclaw-backend/app/services/workspace_template_collect.py:105 起,保存模板时只收集 humancorridorblackboard,忽略了 topology 里的 node_type == "device"
  • nodeskclaw-backend/app/services/workspace_template_deploy_service.py:217 起,部署模板时 reserved coords 也只处理 corridorhuman,不会为 device 预留位置。
  • nodeskclaw-backend/app/services/workspace_service.py:185 / apply_internal_deploy_topology 也只恢复 corridor/human,不会重新创建 AgentDeviceInstance 和对应 NodeCard

影响:用户在办公室里放了 Browser Pilot / Agent Device 后,保存成模板再部署,会丢失设备节点、设备配置、拓扑连接和后续 gene 同步触发。对“办公设施实验抽象”来说,这会让模板复用场景不完整。

建议:

  1. 模板 payload 增加 device_specs 或在 topology_snapshot.nodes 中完整保留 device 节点的 preset_idprovider_iddisplay_nameconfigmetadata、坐标;
  2. 部署模板时先创建 AgentDeviceInstance + NodeCard,再恢复 edges;
  3. prepare_template_deploy_layout 把 device 坐标加入 reserved coords,避免 Agent 被放到设备位置;
  4. 部署完成后触发一次 sync_workspace_device_genes

如果本 PR 暂时不打算支持模板,也建议在 PR 描述里明确列为不支持范围,否则用户会以为设备和员工/过道一样可被模板保存。

@chenchenchenchencj
Copy link
Copy Markdown
Collaborator

再补一个 Agent Device 生命周期问题:移除办公室员工时没有清理该员工相关的 device grant / lease。

相关位置:

  • nodeskclaw-backend/app/services/workspace_service.py:596remove_agent 只删除员工节点、过道连接和 channel plugin;
  • nodeskclaw-backend/app/api/workspaces.py:299 之后只调用了 withdraw_workspace_agent_device_gene_bindingssync_workspace_device_genes
  • 当前没有看到针对 AgentDeviceGrant.subject_id == instance_idAgentDeviceLease.holder_agent_id == instance_id 的 revoke/reclaim。

影响:如果一个员工持有 Browser Pilot 设备 active lease,管理员把这个员工从办公室移除后,该 lease 仍然是 active。由于 acquire_lease 会检查当前 active lease,其他员工后续可能一直拿不到这个设备,直到 TTL 过期;如果 TTL 较长,会表现为设备被一个已移除员工“占住”。授权记录也会继续残留,后续重新加入同 id 实例时权限状态可能不符合预期。

建议:员工移除时同步做两件事:

  1. 回收该员工作为 holder 的所有 active leases;
  2. 撤销该员工作为 subject 的 active grants,并级联撤销它委托出去的 descendant grants,同时回收这些 grants 下的 active leases。

这里最好补一个测试:给员工授权并 acquire lease 后移除员工,断言 lease 被 reclaimed、grant 被 revoked/soft_deleted,其他员工可以重新 acquire 该设备。

@chenchenchenchencj
Copy link
Copy Markdown
Collaborator

再补一个授权语义问题:同一设备对同一 subject 可以重复创建多条 active grant,撤销其中一条后 subject 仍然可能保留访问权。

相关位置:

  • nodeskclaw-backend/app/services/agent_device_service.py:542create_grant 每次都直接插入新的 AgentDeviceGrant
  • nodeskclaw-backend/alembic/versions/fccc870bdf4d_add_agent_device_models.py 里没有针对 (workspace_id, device_id, subject_type, subject_id) 的 active grant 唯一约束;
  • nodeskclaw-portal/src/components/workspace/DeviceDetailDrawer.vue:109 的“授权选中员工”按钮可以重复点击,没有前端去重。

影响:管理员可能多次点击授权,产生多条 active grant。之后 UI 上撤销其中一条 grant,find_valid_grant 仍会命中另一条 active grant,员工实际上仍然能 discover/lease/invoke 设备。用户会以为“撤销成功但权限没收回”,这在设备治理场景里比较危险。

建议:

  1. 后端创建 grant 前检查同一 subject 是否已有 active grant;已有时做 upsert/合并 scopes,而不是新增重复记录;或
  2. 增加 partial unique index,约束 active grant 的 (workspace_id, device_id, subject_type, subject_id, parent_grant_id)
  3. 前端在已有 grant 时禁用“授权选中员工”或展示“更新授权”。

建议补测试:重复授权同一员工后撤销,确认不会残留另一个 active grant 导致设备仍可见。

@chenchenchenchencj
Copy link
Copy Markdown
Collaborator

再补一个授权过期和租约生命周期的边界问题:active lease 没有跟随 grant 过期一起失效。

相关位置:

  • nodeskclaw-backend/app/services/agent_device_service.py:852expire_active_leases 只按 AgentDeviceLease.expires_at 过期租约;
  • nodeskclaw-backend/app/services/agent_device_service.py:1025 获取租约时直接用请求的 ttl_seconds 计算 lease.expires_at,没有和 grant.expires_at 取更早时间;
  • nodeskclaw-backend/app/services/agent_device_service.py:1069 续租时同样没有把续租上限限制到当前有效 grant 的过期时间。

影响:如果管理员创建了一个 5 分钟后过期的 grant,但 Agent 获取了 1 小时 lease,那么 5 分钟后该 Agent 可能因为 require_agent_device_access 失败而不能继续 invoke/renew,但这条 lease 仍保持 active,其他员工会一直拿不到设备,直到 lease 自己过期或人工 reclaim。设备治理语义上,授权到期后不应该继续占用排他设备。

建议:

  1. acquire / renew 时把 lease.expires_at 限制为 min(now + ttl, grant.expires_at)
  2. expire_active_leasesactive_lease 同时回收/过期 backing grant 已失效(过期、撤销、软删)的 active lease;
  3. 补测试:短 grant + 长 lease,到 grant 过期后其他员工可以重新 acquire。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants