feat(hosts): WorkBuddy 接入(install-workbuddy:本机 stdio + 技能安装 + 握手自证)
CI / contract (push) Has been cancelled
CI / contract (push) Has been cancelled
WorkBuddy 自定义 MCP 支持 command/args/env(取证:其二进制 calculateConfigHash 先判 entry.command),因此走与 Codex 同款的本机 stdio full 工具面,不退远程引擎面。 落地:① 启动器多维护一个 MCP 稳定入口 ~/.deta/bin/deta-mcp-<档位>(指向当前插件副本,升级自动刷新;WB 信任哈希按命令行算,路径稳定=升级不重弹信任);② deta-cli 新增 install-workbuddy [--http]——备份并合并 ~/.workbuddy/mcp.json(同名旧条目与旧远程条目替换掉,防双工具面)、把插件内全部技能装进 ~/.workbuddy/skills/(单一事实源,带 icon.svg)、用宿主将执行的同一条命令做真实握手自证;③ aml-design 技能两处 Codex 特有措辞改宿主中立;④ 契约校验加 E7(装配源齐备 + README 指引);⑤ 探针 scripts/verify-workbuddy.sh(配置形态/稳定入口/技能/握手/WB 日志,5 项断言)。 实测:studio 0.3.16 已装副本重跑幂等通过(握手 39 工具);writer 0.1.7 在沙箱 HOME 验证(deta_writer + report-authoring 技能 + 握手 39 工具)。WB 侧需重启并点一次信任确认。
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "studio",
|
||||
"version": "0.3.14",
|
||||
"version": "0.3.16",
|
||||
"description": "DETA Studio 插件:以 AML 为唯一事实源编辑建筑模型,编译为建筑模型并执行规则校验。",
|
||||
"author": {
|
||||
"name": "DETA",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* 在用户自己的终端里交互读取(不回显)。
|
||||
*/
|
||||
import { spawn, spawnSync } from 'node:child_process';
|
||||
import { copyFileSync, existsSync, mkdirSync, readFileSync, statSync } from 'node:fs';
|
||||
import { chmodSync, copyFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { homedir } from 'node:os';
|
||||
import { dirname, join, resolve, basename } from 'node:path';
|
||||
@@ -171,7 +171,7 @@ async function cmdInstallRuntime(argv) {
|
||||
|
||||
// 装完立刻做一次真实 MCP 握手——「装上了」不等于「起得来」
|
||||
const launcher = join(HERE, 'deta-mcp.sh');
|
||||
const r = await smokeMcp(launcher, PROFILE);
|
||||
const r = await smokeMcp('bash', [launcher, '--profile', PROFILE]);
|
||||
if (r.ok) {
|
||||
console.log(`✓ MCP 握手通过(--profile ${PROFILE}):${r.tools} 个工具${r.tokenLine ? ` · ${r.tokenLine.trim()}` : ''}`);
|
||||
console.log('\n宿主里的下一步:退出并重启宿主应用(或开新会话)即可看到工具。');
|
||||
@@ -210,9 +210,9 @@ function resolveRuntime() {
|
||||
return '';
|
||||
}
|
||||
|
||||
function smokeMcp(launcher, profile) {
|
||||
function smokeMcp(cmd, args) {
|
||||
return new Promise((resolveP) => {
|
||||
const child = spawn('bash', [launcher, '--profile', profile], { stdio: ['pipe', 'pipe', 'pipe'] });
|
||||
const child = spawn(cmd, args, { stdio: ['pipe', 'pipe', 'pipe'] });
|
||||
let out = '';
|
||||
let err = '';
|
||||
let done = false;
|
||||
@@ -262,7 +262,7 @@ async function cmdDoctor() {
|
||||
if (runtime) {
|
||||
const launcher = join(HERE, 'deta-mcp.sh');
|
||||
const profile = PROFILE;
|
||||
const r = await smokeMcp(launcher, profile);
|
||||
const r = await smokeMcp('bash', [launcher, '--profile', profile]);
|
||||
say(r.ok, `MCP 握手(--profile ${profile})`, r.ok ? `${r.tools} 个工具 · ${r.server?.name ?? '?'}${r.tokenLine ? ` · ${r.tokenLine.trim()}` : ''}` : r.detail);
|
||||
}
|
||||
|
||||
@@ -270,12 +270,126 @@ async function cmdDoctor() {
|
||||
process.exit(failed ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 接入 WorkBuddy:把本档位的 MCP 以 stdio 写进 ~/.workbuddy/mcp.json,并把领域技能装进
|
||||
* ~/.workbuddy/skills/。WorkBuddy 的自定义 MCP 支持 command/args/env 形态
|
||||
* (取证:其配置哈希 calculateConfigHash 先判 entry.command),本机 stdio = 与 Codex 同款
|
||||
* full 工具面。--http 回落为远程引擎面(适合没装本机运行时的机器)。
|
||||
*/
|
||||
async function cmdInstallWorkbuddy(argv) {
|
||||
const useHttp = argv.includes('--http');
|
||||
const wbDir = join(HOME, '.workbuddy');
|
||||
if (!existsSync(wbDir)) {
|
||||
console.error('✗ 未检测到 WorkBuddy(没有 ~/.workbuddy 目录)。');
|
||||
console.error(' 请先安装并启动一次 WorkBuddy,再重跑本命令。');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 1) 稳定入口(与启动器维护的那份内容一致;启动器未跑过时这里先写出来)
|
||||
const binDir = join(DETA_DIR, 'bin');
|
||||
const shim = join(binDir, `deta-mcp-${PROFILE}`);
|
||||
const launcher = join(HERE, 'deta-mcp.sh');
|
||||
const shimBody = '#!/bin/bash\n# DETA MCP 稳定入口(由插件自动维护,勿手工编辑)\n'
|
||||
+ 'export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}"\n'
|
||||
+ `exec bash "${launcher}" --profile ${PROFILE} "$@"\n`;
|
||||
mkdirSync(binDir, { recursive: true });
|
||||
writeFileSync(shim, shimBody);
|
||||
chmodSync(shim, 0o755);
|
||||
|
||||
// 2) 合并 ~/.workbuddy/mcp.json(备份 → 合并 → 写完再校验)
|
||||
const mcpPath = join(wbDir, 'mcp.json');
|
||||
const serverName = `deta_${PROFILE}`;
|
||||
const entry = useHttp
|
||||
? { type: 'streamable-http', url: 'https://deta.city/mcp-api/mcp', disabled: false }
|
||||
: { type: 'stdio', command: shim, args: [], disabled: false };
|
||||
let cfg = { mcpServers: {} };
|
||||
if (existsSync(mcpPath)) {
|
||||
const raw = readFileSync(mcpPath, 'utf8');
|
||||
try {
|
||||
cfg = JSON.parse(raw);
|
||||
} catch (e) {
|
||||
console.error(`✗ ${mcpPath} 不是合法 JSON:${e.message}`);
|
||||
console.error(' 未做任何改动;请先手工修复或备份后删除该文件。');
|
||||
process.exit(1);
|
||||
}
|
||||
copyFileSync(mcpPath, `${mcpPath}.bak-${Date.now()}`);
|
||||
}
|
||||
if (!cfg.mcpServers || typeof cfg.mcpServers !== 'object') cfg.mcpServers = {};
|
||||
// 旧的远程条目与新的本机条目指向同一套工具 → 并存会出双工具面,替换之
|
||||
const replaced = [];
|
||||
if (!useHttp && cfg.mcpServers.deta_mcp) {
|
||||
delete cfg.mcpServers.deta_mcp;
|
||||
replaced.push('deta_mcp(远程引擎面)');
|
||||
}
|
||||
if (cfg.mcpServers[serverName]) replaced.push(`${serverName}(旧配置)`);
|
||||
cfg.mcpServers[serverName] = entry;
|
||||
writeFileSync(mcpPath, JSON.stringify(cfg, null, 2) + '\n');
|
||||
console.log(`✓ MCP 已写入 ${mcpPath}:${serverName}(${useHttp ? '远程 streamable-http' : `本机 stdio → ${shim}`})`);
|
||||
if (replaced.length) console.log(` · 已替换旧条目:${replaced.join('、')}(防同服务双工具面)`);
|
||||
|
||||
// 3) 技能:把插件内的全部技能原样装进 WB 技能目录(单一事实源,不另写一份;
|
||||
// 各档位的技能不同——studio 是 aml-design,writer 是 report-authoring)
|
||||
const skillsSrc = join(HERE, '..', 'skills');
|
||||
const skillsDst = join(wbDir, 'skills');
|
||||
const logo = join(HERE, '..', 'assets', 'logo.svg');
|
||||
let installedSkills = 0;
|
||||
if (existsSync(skillsSrc)) {
|
||||
for (const name of readdirSync(skillsSrc)) {
|
||||
const src = join(skillsSrc, name);
|
||||
if (!existsSync(join(src, 'SKILL.md'))) continue;
|
||||
const dst = join(skillsDst, name);
|
||||
cpSync(src, dst, { recursive: true });
|
||||
if (existsSync(logo)) copyFileSync(logo, join(dst, 'icon.svg'));
|
||||
installedSkills += 1;
|
||||
console.log(`✓ 技能已安装到 ${dst}`);
|
||||
}
|
||||
}
|
||||
if (!installedSkills) {
|
||||
console.log(`! 插件包内未找到技能(${skillsSrc} 为空,打包缺陷);已跳过技能安装`);
|
||||
}
|
||||
|
||||
// 4) 自证:用 WorkBuddy 将要执行的同一条命令做一次真实握手
|
||||
if (!useHttp) {
|
||||
const r = await smokeMcp('bash', [shim]);
|
||||
if (r.ok) {
|
||||
console.log(`✓ 握手自证通过:${r.tools} 个工具(${r.server?.name ?? '?'})`);
|
||||
} else {
|
||||
console.error(`✗ 握手自证失败:${r.detail}`);
|
||||
console.error(' 排障:~/.deta/bin/deta doctor');
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const res = await fetch('https://deta.city/mcp-api/mcp', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}',
|
||||
});
|
||||
const challenge = res.headers.get('www-authenticate') ?? '';
|
||||
if (res.status === 401 && challenge.includes('resource_metadata')) {
|
||||
console.log('✓ 远程端点在线且 OAuth 通道在位(401 + resource_metadata)');
|
||||
} else {
|
||||
console.log(`! 远程端点响应异常(HTTP ${res.status})——请检查网络后重试`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`! 远程端点不可达:${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\nWorkBuddy 里的下一步:');
|
||||
console.log(' 1) 退出并重启 WorkBuddy(或在设置里刷新 MCP 列表)');
|
||||
console.log(` 2) 首次会弹「是否信任 MCP 服务器 ${serverName}」→ 点允许(路径稳定,插件升级后不再询问)`);
|
||||
console.log(' 3) 开新对话,说「列出 deta 的能力」核对工具面');
|
||||
console.log(' 4) 首次使用云能力时调 login 工具走浏览器授权(与 Codex 同一套,不需要终端)');
|
||||
}
|
||||
|
||||
const [cmd = 'help', ...rest] = process.argv.slice(2);
|
||||
switch (cmd) {
|
||||
case 'login': cmdLogin(rest); break;
|
||||
case 'status': cmdStatus(); break;
|
||||
case 'doctor': await cmdDoctor(); break;
|
||||
case 'install-runtime': await cmdInstallRuntime(rest); break;
|
||||
case 'install-workbuddy': await cmdInstallWorkbuddy(rest); break;
|
||||
case 'help':
|
||||
case '--help':
|
||||
case '-h':
|
||||
@@ -283,6 +397,7 @@ switch (cmd) {
|
||||
|
||||
deta login [--identifier <账号>] [--email-code <邮箱>] 登录 deta.city(凭据写入 ~/.deta/)
|
||||
deta install-runtime [--force] [--no-resvg] 安装/更新领域服务运行时 + 可选预览图依赖
|
||||
deta install-workbuddy [--http] 接入 WorkBuddy(写 mcp.json + 装技能 + 握手自证)
|
||||
deta status 查看凭据来源 / 运行时 / 工作区
|
||||
deta doctor 端到端自检(运行时、凭据、GSA、MCP 握手)
|
||||
|
||||
@@ -290,6 +405,6 @@ switch (cmd) {
|
||||
登录后开新会话即可让宿主读到凭据(无需 source / launchctl / 重启应用)。`);
|
||||
break;
|
||||
default:
|
||||
console.error(`未知命令:${cmd}(可用:login / install-runtime / status / doctor)`);
|
||||
console.error(`未知命令:${cmd}(可用:login / install-runtime / install-workbuddy / status / doctor)`);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
@@ -105,6 +105,19 @@ if [ ! -f "$SHIM" ] || [ "$(cat "$SHIM" 2>/dev/null)" != "$SHIM_BODY" ]; then
|
||||
printf '%s' "$SHIM_BODY" > "$SHIM" 2>/dev/null && chmod 0755 "$SHIM" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# ── MCP 稳定入口:~/.deta/bin/deta-mcp-<档位>(供不支持插件市场的宿主以 stdio 接入,如 WorkBuddy)──
|
||||
# 指向当前插件副本;升级后下一次启动自动刷新。宿主对 MCP 的信任按命令行算哈希,
|
||||
# 路径稳定 ⇒ 升级插件不再重弹信任确认。PATH 前缀兜住 GUI 拉起时找不到 homebrew node 的情况。
|
||||
MCP_SHIM="$BIN_DIR/deta-mcp-${PROFILE}"
|
||||
MCP_SHIM_BODY="#!/bin/bash
|
||||
# DETA MCP 稳定入口(由插件启动器自动维护,勿手工编辑)
|
||||
export PATH=\"/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:\${PATH:-}\"
|
||||
exec bash \"$PLUGIN_ROOT/bin/deta-mcp.sh\" --profile ${PROFILE} \"\$@\"
|
||||
"
|
||||
if [ ! -f "$MCP_SHIM" ] || [ "$(cat "$MCP_SHIM" 2>/dev/null)" != "$MCP_SHIM_BODY" ]; then
|
||||
printf '%s' "$MCP_SHIM_BODY" > "$MCP_SHIM" 2>/dev/null && chmod 0755 "$MCP_SHIM" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# ── 云令牌:显式环境变量 → ~/.deta/env → ~/.deta/credentials.json ──
|
||||
# 桌面应用(Finder/Dock 启动)不会继承终端的 export,也不是每个人都有 launchctl 注入,
|
||||
# 因此登录助手写下的凭据必须由启动器自己解析——这是"登录一次即可用"的关键路径。
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "deta-studio-plugin",
|
||||
"version": "0.3.14",
|
||||
"version": "0.3.16",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "deta-studio-plugin",
|
||||
"version": "0.3.14",
|
||||
"version": "0.3.16",
|
||||
"license": "Proprietary"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "deta-studio-plugin",
|
||||
"version": "0.3.14",
|
||||
"version": "0.3.16",
|
||||
"description": "DETA Studio:AML 建筑模型读写与编译校验(Codex 插件 / dsh 组合包 双壳)",
|
||||
"type": "module",
|
||||
"license": "Proprietary",
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
"source": "@ringoarch/ant-copilot",
|
||||
"version": "0.2.2",
|
||||
"gitSha": "984a816",
|
||||
"builtAt": "2026-09-20T08:38:38.048Z",
|
||||
"builtAt": "2026-09-20T09:11:33.439Z",
|
||||
"entry": "dist/deta-mcp.mjs",
|
||||
"sha256": "db05fa93049977fe69846b296e51ee6b9245552e8d96928589911c0051e8ee59",
|
||||
"bytes": 4153494,
|
||||
"note": "由 scripts/sync-runtime.mjs 生成;勿手工编辑。安装到 ~/.deta/runtime 由启动器/install-runtime 完成。",
|
||||
"plugin": "studio",
|
||||
"pluginVersion": "0.3.14"
|
||||
"pluginVersion": "0.3.16"
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ build_model({ aml?: "<AML 全文>", files?: { "aml.xml": "…" }, projectId?: "2
|
||||
|
||||
## 在右侧面板打开 Studio,并在其中跑模拟
|
||||
|
||||
Studio 就是deta.city 上的建筑性能应用。在 Codex 右侧内置浏览器面板打开它之后,
|
||||
Studio 就是deta.city 上的建筑性能应用。在宿主内置浏览器面板打开它之后,
|
||||
看模型、跑模拟、看结果图层都在面板里完成;本插件的 MCP 只负责把本地改动推上去,不执行模拟。
|
||||
|
||||
打开步骤(用宿主内置浏览器 = browser 插件的 `iab`):
|
||||
@@ -200,7 +200,7 @@ Studio 就是deta.city 上的建筑性能应用。在 Codex 右侧内置浏览
|
||||
确认 `executor_online: true` → 再调 `simulate` / `studio_command` 等执行器侧工具。
|
||||
- 若返回 `auth_required` → 未登录:**直接调 `login` 工具**(建议 `waitSeconds: 90`),把返回的 `url`
|
||||
交给用户在内置浏览器打开——首次输一次账号密码或邮箱验证码,之后只需点「登录并授权」按钮。
|
||||
**不需要终端,也不要去找 `codex mcp login`**;用户点完后本工具会自动换取令牌并写入当前会话
|
||||
**不需要终端,也不要去找宿主自己的 MCP 登录命令**;用户点完后本工具会自动换取令牌并写入当前会话
|
||||
(凭据过期时 `login` 会先用 refresh token 静默续期,用户完全无感)。
|
||||
仅当当前形态没有 `login` 工具(CLI 等)时,才让用户在终端运行 `~/.deta/bin/deta login`;
|
||||
排查用 `~/.deta/bin/deta doctor`(自检运行时 / 凭据 / GSA / MCP 握手)。
|
||||
|
||||
Reference in New Issue
Block a user