fix(plugin): 云令牌改由启动器解析,清掉废弃的 MCP 授权指引
CI / contract (push) Has been cancelled

症状:装好插件后调 list_projects 返回 auth_required,模型被技能里"先让用户完成 MCP 授权(codex mcp login deta_mcp)"带偏,用户卡在登录环节。

根因两条:① 启动器只透传环境变量 DETA_TOKEN,而 Finder/Dock 启动的桌面应用不继承终端环境,登录助手写的 ~/.deta 凭据根本进不去宿主进程;② 技能/README 仍留着远程形态的 OAuth 指引,本机形态里那条命令不存在。

修复:启动器按 DETA_TOKEN 环境变量 → ~/.deta/env → ~/.deta/credentials.json 顺序解析令牌并在 stderr 报告来源(不打印值);技能改为"让用户跑登录助手 → 开新会话",并明确区分 MCP 侧云令牌与面板侧 deta.city 登录;两份 README 的登录流程改为"登录一次、开新会话即可"(无需 source / launchctl / 改 .zshrc / 重启应用)。studio 0.3.1 → 0.3.2。

实测(隔离 HOME):四种取令牌路径与优先级、缺令牌时的引导文案均符合预期。
This commit is contained in:
ringo
2026-09-20 12:02:24 +08:00
parent 7d3f9e2933
commit e7d372f939
9 changed files with 81 additions and 23 deletions
+4 -4
View File
@@ -81,12 +81,12 @@ codex plugin add studio@deta
bash plugins/studio/bin/deta-mcp.sh
```
装完即用,**没有 OAuth 登录步骤**。需要云侧能力时注入令牌(助手见
`plugins/studio/README.md`「登录助手」):
装完即用,**没有 OAuth 登录步骤**。需要云侧能力(项目列表 / 项目同步 / 打开 Studio)时登录一次
(助手用法见 `plugins/studio/README.md`「登录助手」):
```bash
node plugins/studio/bin/deta-login.mjs
source ~/.deta/env # 让 DETA_TOKEN 进入宿主进程环境
node plugins/studio/bin/deta-login.mjs # 账号密码 / 邮箱验证码由你本人输入
# 然后开新会话即可,无需 source / launchctl / 重启应用
```
## 本地迭代
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "studio",
"version": "0.3.1",
"version": "0.3.2",
"description": "DETA Studio 插件:以 AML 为唯一事实源编辑建筑模型,编译为建筑模型并执行规则校验。",
"author": {
"name": "DETA",
+8 -3
View File
@@ -89,13 +89,18 @@ codex plugin add studio@deta
bash <插件目录>/bin/deta-mcp.sh --profile studio
```
装完即用,**没有 OAuth 登录步骤**。需要云侧能力(项目列表 / 同步)时再注入令牌:
装完即用,**没有 OAuth 登录步骤**。需要云侧能力(项目列表 / 项目同步 / 打开 Studio)时登录一次:
```bash
node bin/deta-login.mjs # 登录 deta.city,写 ~/.deta/credentials.json 与 ~/.deta/env
source ~/.deta/env # 让 DETA_TOKEN 进入宿主进程环境
node <插件目录>/bin/deta-login.mjs # 登录 deta.city:账号密码或邮箱验证码由你本人输入
# 写 ~/.deta/credentials.json 与 ~/.deta/env(均 0600)
```
登录后**开新会话**(MCP 服务重新拉起时读到凭据)。启动器自己按
`DETA_TOKEN` 环境变量 → `~/.deta/env` → `~/.deta/credentials.json` 的顺序解析令牌,
所以**不需要 source、不需要 launchctl 注入、不需要改 .zshrc、也不需要重启应用**——
Finder/Dock 启动的宿主不继承终端环境,令牌由启动器直接读取才可靠。
### 登录助手(云侧能力用)
```bash
+25 -1
View File
@@ -68,6 +68,30 @@ export ANT_HEADLESS_WORKSPACE="$WS"
# ── 工具面:产品档位缺省全量面;DETA_SURFACE 可覆盖(如 engine)──
export DETA_TOOL_SURFACE="${DETA_SURFACE:-full}"
echo "[deta] 档位=${PROFILE} 运行时=${ROOT} 工作区=${WS} 工具面=${DETA_TOOL_SURFACE} 云令牌=$([ -n "${DETA_TOKEN:-}" ] && echo 已注入 || echo 未注入)" >&2
# ── 云令牌:显式环境变量 → ~/.deta/env → ~/.deta/credentials.json ──
# 桌面应用(Finder/Dock 启动)不会继承终端的 export,也不是每个人都有 launchctl 注入,
# 因此登录助手写下的凭据必须由启动器自己解析——这是"登录一次即可用"的关键路径。
TOKEN_SOURCE=""
if [ -n "${DETA_TOKEN:-}" ]; then
TOKEN_SOURCE="环境变量"
fi
if [ -z "${DETA_TOKEN:-}" ] && [ -f "$HOME/.deta/env" ]; then
# shellcheck disable=SC1090
. "$HOME/.deta/env" >/dev/null 2>&1 || true
if [ -n "${DETA_TOKEN:-}" ]; then TOKEN_SOURCE="$HOME/.deta/env"; fi
fi
if [ -z "${DETA_TOKEN:-}" ] && [ -f "$HOME/.deta/credentials.json" ]; then
DETA_TOKEN="$(node -e "try{const c=JSON.parse(require('node:fs').readFileSync(process.env.HOME+'/.deta/credentials.json','utf8'));if(c&&typeof c.token==='string')process.stdout.write(c.token)}catch(e){}" 2>/dev/null || true)"
export DETA_TOKEN
if [ -n "${DETA_TOKEN:-}" ]; then TOKEN_SOURCE="$HOME/.deta/credentials.json"; fi
fi
if [ -n "${DETA_TOKEN:-}" ]; then
CLOUD_STATE="已注入(来源=${TOKEN_SOURCE})"
else
CLOUD_STATE="未注入——云类工具将返回 auth_required:运行 bin/deta-login.mjs 登录后开新会话"
fi
echo "[deta] 档位=${PROFILE} 运行时=${ROOT} 工作区=${WS} 工具面=${DETA_TOOL_SURFACE} 云令牌=${CLOUD_STATE}" >&2
exec node "$ENTRY" "$@"
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "deta-studio-plugin",
"version": "0.3.1",
"version": "0.3.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "deta-studio-plugin",
"version": "0.3.1",
"version": "0.3.2",
"license": "Proprietary"
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "deta-studio-plugin",
"version": "0.3.1",
"version": "0.3.2",
"description": "DETA Studio:AML 建筑模型读写与编译校验(Codex 插件 / dsh 组合包 双壳)",
"type": "module",
"license": "Proprietary",
+9 -7
View File
@@ -111,7 +111,10 @@ Studio 就是deta.city 上的建筑性能应用。在 Codex 右侧内置浏览
deta.city 登录并跳到 Studio。
- 典型节奏:`open_studio()` 拿 `url` → 面板打开 → `open_studio({ waitSeconds: 20 })`
确认 `executor_online: true` → 再调 `simulate` / `studio_command` 等执行器侧工具。
- 若返回 `auth_required` → 先让用户完成 MCP 授权(`codex mcp login deta_mcp`)再重试;
- 若返回 `auth_required` → 云令牌未注入(**本机形态没有 MCP 授权,不要去找 `codex mcp login`**):
让用户在终端运行插件自带登录助手 `node <插件目录>/bin/deta-login.mjs`(账号与密码/验证码由用户
本人输入;助手写 `~/.deta/credentials.json` 与 `~/.deta/env`,启动器会自动读取——不需 source、
不需 launchctl、不需重启应用),随后**开新会话**重试;
若 `url` 报「票据无效或已过期」→ 重新调一次,不要改成手工登录。
2. 按 browser 插件技能(`control-in-app-browser`)的 Bootstrap 初始化,再取内置浏览器句柄:
`const iab = await agent.browsers.get("iab");`,并先读一次 `await iab.documentation()`。
@@ -137,12 +140,11 @@ Studio 就是deta.city 上的建筑性能应用。在 Codex 右侧内置浏览
`open_studio` 时才退化为手工:在面板里打开 `https://deta.city/login` 完成一次登录
(登录态写入该浏览器的 `ant_token` cookie 与 `ant-model-auth` localStorage,之后长期有效),
登录完成后重新打开 `studioUrl` 即自动加载项目。
- **宿主把 MCP 授权页开在哪个浏览器由宿主决定**(实测 Codex Desktop 走系统默认浏览器),
且**那一页的登录态不会进入面板**——所以别把"MCP 已授权"当成"面板已登录",两者要分别确认:
MCP 授权只解决 Codex↔服务端;面板登录解决 Studio 在面板里的会话(`open_studio` 正是两者的桥)。
实测口径:授权页在系统浏览器完成换码后停留在宿主的回调页("Authentication complete. You may close
this window."),该页不归我们控制;授权页自身已改为**在隐藏框架内完成换码**并给出「打开 deta.city」
与「关闭本页」,不再顶层跳走。
- **本机形态没有 MCP 授权**(进程在本机由宿主拉起,无 OAuth、无 401、无令牌续期),要分清的是两件事:
- **MCP 侧的云令牌**:只影响项目列表 / 项目同步 / `open_studio` 这类云工具,`auth_required` 的处置见上;
- **面板侧的 deta.city 登录**:靠 `open_studio` 的一次性链接自动完成,登录态写在该浏览器的
`ant_token` cookie 与 `ant-model-auth` localStorage,之后长期有效。
两者互不替代——拿到云令牌不等于面板已登录,反之亦然。
- 没有 `projectId`:Studio 打开的是空项目。先在门户创建云项目拿到 ID,再 `push_project(…, projectId)` 绑定。
- 宿主没有内置浏览器(CLI 等形态):直接把 `studioUrl` 给用户,让他自己打开,不要假装已经打开。
+6 -3
View File
@@ -50,13 +50,16 @@ codex plugin add writer@deta
bash <插件目录>/bin/deta-mcp.sh --profile writer
```
需要云侧能力(项目列表 / 同步)时注入令牌:
需要云侧能力(项目列表 / 项目同步 / 打开 Studio)时登录一次:
```bash
node bin/deta-login.mjs # 登录 deta.city,写 ~/.deta/credentials.json 与 ~/.deta/env
source ~/.deta/env # 让 DETA_TOKEN 进入宿主进程环境
node <插件目录>/bin/deta-login.mjs # 账号密码 / 邮箱验证码由你本人输入
# 写 ~/.deta/credentials.json 与 ~/.deta/env(均 0600)
```
登录后**开新会话**即可——启动器自己按 `DETA_TOKEN` 环境变量 → `~/.deta/env` →
`~/.deta/credentials.json` 的顺序解析令牌,不需要 source、不需要 launchctl、不需要重启应用。
## 安装(dsh)
```bash
+25 -1
View File
@@ -68,6 +68,30 @@ export ANT_HEADLESS_WORKSPACE="$WS"
# ── 工具面:产品档位缺省全量面;DETA_SURFACE 可覆盖(如 engine)──
export DETA_TOOL_SURFACE="${DETA_SURFACE:-full}"
echo "[deta] 档位=${PROFILE} 运行时=${ROOT} 工作区=${WS} 工具面=${DETA_TOOL_SURFACE} 云令牌=$([ -n "${DETA_TOKEN:-}" ] && echo 已注入 || echo 未注入)" >&2
# ── 云令牌:显式环境变量 → ~/.deta/env → ~/.deta/credentials.json ──
# 桌面应用(Finder/Dock 启动)不会继承终端的 export,也不是每个人都有 launchctl 注入,
# 因此登录助手写下的凭据必须由启动器自己解析——这是"登录一次即可用"的关键路径。
TOKEN_SOURCE=""
if [ -n "${DETA_TOKEN:-}" ]; then
TOKEN_SOURCE="环境变量"
fi
if [ -z "${DETA_TOKEN:-}" ] && [ -f "$HOME/.deta/env" ]; then
# shellcheck disable=SC1090
. "$HOME/.deta/env" >/dev/null 2>&1 || true
if [ -n "${DETA_TOKEN:-}" ]; then TOKEN_SOURCE="$HOME/.deta/env"; fi
fi
if [ -z "${DETA_TOKEN:-}" ] && [ -f "$HOME/.deta/credentials.json" ]; then
DETA_TOKEN="$(node -e "try{const c=JSON.parse(require('node:fs').readFileSync(process.env.HOME+'/.deta/credentials.json','utf8'));if(c&&typeof c.token==='string')process.stdout.write(c.token)}catch(e){}" 2>/dev/null || true)"
export DETA_TOKEN
if [ -n "${DETA_TOKEN:-}" ]; then TOKEN_SOURCE="$HOME/.deta/credentials.json"; fi
fi
if [ -n "${DETA_TOKEN:-}" ]; then
CLOUD_STATE="已注入(来源=${TOKEN_SOURCE})"
else
CLOUD_STATE="未注入——云类工具将返回 auth_required:运行 bin/deta-login.mjs 登录后开新会话"
fi
echo "[deta] 档位=${PROFILE} 运行时=${ROOT} 工作区=${WS} 工具面=${DETA_TOOL_SURFACE} 云令牌=${CLOUD_STATE}" >&2
exec node "$ENTRY" "$@"