CI / contract (push) Has been cancelled
配合 ant-copilot 新增的 login 工具(回环 OAuth 授权):技能里 auth_required 的处置从"让用户去终端跑命令"改为"调 login 工具 + 浏览器点一下允许";三份 README 同步,终端助手降为无 login 工具形态(CLI 等)的兜底。
187 lines
11 KiB
Markdown
187 lines
11 KiB
Markdown
# DETA Studio 插件(Codex 插件 / dsh 组合包)
|
||
|
||
DETA 领域能力(AML 建筑模型读写、编译校验、围护结构统计、空间邻接查询)
|
||
以**本机 MCP 服务**形态挂进 Agent harness:同一个目录既是 Codex 插件根,
|
||
也是 dsh 组合包根,两端共享 `skills/` 与**同一个启动器**。
|
||
|
||
- 发行标识:**`studio@deta`**(插件 `studio` + 市场 `deta`)
|
||
- MCP 服务名:**`studio_mcp`**(工具前缀 `mcp__studio_mcp__…`)
|
||
- 产品档位:启动器 `--profile studio`(工作区缺省 `~/.deta/studio/workspace`)
|
||
|
||
## 连接形态与凭据(唯一管道:本机 stdio)
|
||
|
||
| 项 | 值 |
|
||
|----|-----|
|
||
| 形态 | **本机 stdio**:宿主拉起 `bin/deta-mcp.sh --profile studio`,由它解析领域服务运行时与工作目录 |
|
||
| MCP 服务名 | `studio_mcp`(工具前缀 `mcp__studio_mcp__…`);其他产品用各自服务名,可同时安装 |
|
||
| 宿主声明 | Codex 读 `.mcp.json`;dsh 读 `cordis.patch.yml`——两边都只声明"调用同一个脚本、带哪个档位" |
|
||
| 工作目录 | `DETA_WORKSPACE` → `ANT_HEADLESS_WORKSPACE` → 缺省 `~/.deta/studio/workspace`(启动器创建并在 stderr 回显) |
|
||
| 运行时 | `DETA_SERVER_ROOT` → `~/.deta/runtime` → 本机开发检出(回落到开发检出时启动器打印提示) |
|
||
| 云侧能力(可选) | `DETA_TOKEN`(`node bin/deta-login.mjs` 获取):项目/楼栋列表、`push_project` / `pull_project` |
|
||
| Studio 执行器中继(可选) | `ANT_WORKSPACE_BRIDGE_URL` + 项目 ID(三要素齐备才启用;供模拟/画布类工具) |
|
||
|
||
**本机形态不做 MCP 鉴权**(进程由宿主或用户在本机拉起,没有 OAuth、没有 401、没有令牌续期)。
|
||
未注入 `DETA_TOKEN` 时,云类工具返回 `auth_required`,其余工具(编辑、编译、校验、统计、
|
||
脚本、模拟)照常工作——这是与远程形态**同一份工具实现**的不同输入,不是两套代码。
|
||
|
||
## 工具面:编辑归宿主,MCP 只做引擎与同步
|
||
|
||
AML 是纯文本 DSL,读写、搜索、重构一律由**宿主原生的文件工具**完成
|
||
(Codex 的 read / patch / grep / shell;dsh 的同名能力),MCP 不是编辑入口:
|
||
|
||
| 分工 | 谁做 |
|
||
|------|------|
|
||
| 读取 / 修改 / 搜索 `aml.xml`、`bss.md`、`rules.yml`、`behaviors.yml`、`design-brief.md` | 宿主原生工具(本地项目目录里的文件) |
|
||
| 编译、规范校验、规则检查、围护结构统计、空间邻接、领域脚本 | MCP 引擎工具(编译 / 校验 / 统计 / 邻接 / 脚本等) |
|
||
| 改动送进引擎工作区、取回工作区 | MCP 同步工具 |
|
||
|
||
本机形态为**全量工具面**(含工作区文件类工具)。**当前可用的完整工具名以运行时
|
||
`list_capabilities` 为准**,本仓文档不列举工具清单。
|
||
|
||
## 本地项目目录约定
|
||
|
||
一个 DETA 项目就是一个普通目录;引擎工具读的是**引擎工作区**,因此改动要先同步:
|
||
|
||
```
|
||
my-project/
|
||
├── deta.json # 可选:项目绑定({ "projectId": "...", "name": "..." })
|
||
├── aml.xml # 必需:建筑模型唯一事实源
|
||
├── bss.md # 可选:样式/构造附属文档
|
||
├── rules.yml # 可选:校验规则
|
||
├── behaviors.yml # 可选:声明式行为规则
|
||
└── design-brief.md # 可选:设计任务书
|
||
```
|
||
|
||
- 想让引擎直接以项目目录为工作区:启动宿主前设 `DETA_WORKSPACE=<项目目录>`。
|
||
- `deta.json` 里的 `projectId` 是 deta.city 云项目 ID;带上它的同步调用会把同一个 ID
|
||
绑定到引擎工作区,之后生成/拉取会带同一个 Studio 深链 `https://deta.city/design/<projectId>`。
|
||
- 文件名沿用引擎侧约定(`aml` ↔ `aml.xml`、`brief` ↔ `design-brief.md` 等),
|
||
本地目录与引擎工作区结构一致,拉取结果可直接落盘回本地目录。
|
||
|
||
## 端到端工作流:本地编辑 → 同步 → 校验 → Studio 查看
|
||
|
||
```text
|
||
1. 本地编辑 read/patch/grep 直接改 aml.xml(MCP 不参与编辑)
|
||
2. 同步 把改动送进引擎工作区(带 projectId 时同时绑定云项目)
|
||
→ { ok, files, projectId, studioUrl: "https://deta.city/design/<ID>" }
|
||
3. 引擎校验 编译 AML 为建筑模型(工作区最新版本)→ 语法/几何/规则校验逐条修复
|
||
围护结构统计与空间邻接查询按需调用
|
||
4. Studio 查看 在宿主内置浏览器面板打开 studioUrl;已有相同 URL 的标签优先复用,
|
||
不为同一项目重复开新标签,也不要 goto 重载已有标签
|
||
5. 跑模拟 在面板里的 Studio 界面运行——模拟由 Studio 执行,本插件 MCP 不替代它
|
||
6. 反向核对 从引擎工作区拉回文件(工作区为空时返回空集)
|
||
```
|
||
|
||
路径安全:同步只接受工作区相对路径,绝对路径与 `..` 穿越会被拒绝,单次载荷上限 1 MiB。
|
||
|
||
## 安装(Codex)
|
||
|
||
```bash
|
||
# 1. 添加市场并安装插件
|
||
codex plugin marketplace add https://git.deta.city/deta/deta-agent-plugins.git
|
||
codex plugin add studio@deta
|
||
|
||
# 2. 准备领域服务运行时(二选一)
|
||
# a) 本机引擎包已安装 → 启动器自动找到(~/.deta/runtime)
|
||
# b) 从源码构建:在领域服务仓库执行 npm run build:mcp,并让 DETA_SERVER_ROOT 指向它
|
||
#
|
||
# 冒烟(应等待 stdin/stdout):
|
||
bash <插件目录>/bin/deta-mcp.sh --profile studio
|
||
```
|
||
|
||
装完即用,**没有 OAuth 登录步骤、也不需要终端**。需要云侧能力(项目列表 / 新建项目 / 项目同步 / 打开 Studio)时:
|
||
|
||
```
|
||
在对话里说"登录"或直接让助手新建项目/上传到云 —— 助手会调 login 工具给出一个授权地址,
|
||
你在浏览器打开(首次输一次账号密码或邮箱验证码)→ 点「登录并授权」→ 完成。
|
||
令牌自动写入 ~/.deta 并即时生效;凭据过期时先用 refresh token 静默续期,用户无感。
|
||
```
|
||
|
||
命令行形态(无 `login` 工具时)的兜底做法:
|
||
|
||
```bash
|
||
~/.deta/bin/deta login # 由启动器自动注册;账号密码或 --email-code <邮箱>
|
||
~/.deta/bin/deta doctor # 端到端自检:运行时、凭据、GSA、MCP 握手
|
||
```
|
||
|
||
启动器按 `DETA_TOKEN` 环境变量 → `~/.deta/env` → `~/.deta/credentials.json` 的顺序解析令牌,
|
||
所以浏览器授权后的下一次会话也自动带着登录态——不需要 source、不需要 launchctl、不需要重启应用。
|
||
|
||
### 登录助手(云侧能力用)
|
||
|
||
```bash
|
||
node bin/deta-login.mjs # 交互式:读标识与密码,写 ~/.deta/*
|
||
node bin/deta-login.mjs -u <用户名|已验证邮箱|已验证手机号> # 免交互输入标识
|
||
node bin/deta-login.mjs --email-code <邮箱> # 邮箱验证码登录(未设密码的账号用)
|
||
node bin/deta-login.mjs --base-url https://<host> # 自建/预发环境
|
||
```
|
||
|
||
**标识(identifier)填门户登录页上用的那一个**:用户名、已验证邮箱、已验证手机号,服务端都会解析到同一账号。
|
||
助手先匿名调用 `/login/check` 预检标识是否已注册,再走 `/login` 做密码校验(不走门户的「登录即注册」端点,
|
||
不会误建账号)。失败时按服务端提示给出下一步:
|
||
|
||
| 服务端提示 | 含义与处理 |
|
||
|------------|------------|
|
||
| `该标识在 <host> 未注册` | 标识不是门户上用的那一个;邮箱验证码注册的账号标识填邮箱 |
|
||
| `用户不存在/密码错误` | 服务端不区分「账号不存在」与「密码不匹配」;已确认标识存在,即密码不符,或改用 `--email-code` |
|
||
| `该邮箱/手机号尚未验证…` | 先用验证码方式登录一次门户完成验证 |
|
||
| `该登录方式未开放` | 用 `GET <base>/prod-api/login/methods` 查当前启用项 |
|
||
|
||
## 安装(dsh)
|
||
|
||
```bash
|
||
export DETA_PLUGIN_ROOT=<本包所在目录> # 配置层按它定位启动器
|
||
export DETA_WORKSPACE=<你的项目目录> # 可选;缺省 ~/.deta/workspace
|
||
dsh plugin --profile <name> add <本包>
|
||
```
|
||
|
||
`cordis.patch.yml` 声明本机 stdio,经 `bash -lc` 调用同一个 `bin/deta-mcp.sh`;
|
||
未设置 `DETA_PLUGIN_ROOT` 时启动器明确报错退出,不会静默失败。
|
||
|
||
## 文件
|
||
|
||
```
|
||
.codex-plugin/plugin.json Codex 插件清单
|
||
.mcp.json Codex MCP 声明(stdio → ./bin/deta-mcp.sh)
|
||
package.json dsh 组合包声明(dsh.bundle.patch)
|
||
cordis.patch.yml dsh 配置层(同一启动器,stdio)
|
||
bin/deta-login.mjs 登录助手(写 ~/.deta/credentials.json + ~/.deta/env,供云侧能力用)
|
||
bin/deta-mcp.sh 本机 stdio 启动器(两宿主共用;解析运行时与工作目录)
|
||
README.md 本文件(安装 / 连接 / 排障)
|
||
skills/aml-design/SKILL.md 宿主侧技能(工具使用规则;不含工具清单)
|
||
```
|
||
|
||
## 已知缺陷(实测,2026-09-10)
|
||
|
||
**上游:编码代理 × 模型服务 命名空间重复**
|
||
|
||
会话累积较多本插件工具调用后,代理会在请求里重复声明 MCP 命名空间,模型服务的
|
||
Responses 兼容层拒绝请求:
|
||
|
||
```
|
||
Duplicate namespace name 'deta'; Namespace names must be unique.
|
||
```
|
||
|
||
复现记录(同一插件、不同会话长度):
|
||
|
||
| 代理版本 | 会话规模 | 触发位置 |
|
||
|---------|---------|---------|
|
||
| 0.144.4 | 一步并发 2 次调用 | input[10]、input[18] |
|
||
| 0.144.4 | 串行 7–8 次调用 | input[37]、input[47] |
|
||
| 0.154.0 | 短会话 5 工具 | 未触发 |
|
||
| 0.154.0 | 长会话(≈15+ 工具) | input[260] |
|
||
|
||
已排除/无效的手段:`wire_api = "chat"`(该版本已移除,仅支持 responses)、
|
||
`features.non_prefixed_mcp_tool_names`(仅去掉命名空间前缀,重复依旧)、
|
||
`supports_parallel_tool_calls = false`(削弱并发触发面,长会话仍复现)。
|
||
|
||
**应对**:完成一个任务后另起会话;撞到该错误不要重试,重开会话即可(工作区文件保留)。
|
||
|
||
## 已修缺陷(实测)
|
||
|
||
| 现象 | 根因 | 修复 |
|
||
|------|------|------|
|
||
| 写类工具返回 `user cancelled MCP tool call` | MCP 工具默认需审批,非交互模式下无法弹窗 | `.mcp.json` 声明 `default_tools_approval_mode: "approve"`(实测 `auto`/`writes` 都会被取消,只有 `approve` 放行) |
|
||
| 本机 stdio 形态起不来:`ERR_UNSUPPORTED_DIR_IMPORT` | 启动器指向未打包的产物,依赖包的目录 import 在 Node ESM 下非法 | 领域服务提供打包入口;启动器优先取打包产物,回落未打包产物时打印警告 |
|
||
| 一个插件两种形态并存(远程 URL + 本机 stdio) | 历史遗留:远程端点声明与本地启动器同时存在,语义冲突 | 统一为**本机 stdio 单一管道**,远程端点声明从插件移除 |
|