fix(enchante): Agent deeplink missing top-level name field - #3
Closed
chenyida7-prog wants to merge 33 commits into
Closed
fix(enchante): Agent deeplink missing top-level name field#3chenyida7-prog wants to merge 33 commits into
chenyida7-prog wants to merge 33 commits into
Conversation
…SE_INPUT 7ec8a2f fixed the PreToolUse matcher (Bash -> Bash|Write|Edit) so Write/Edit calls actually trigger the hook. But the hook command itself never worked: it POSTed the literal string "$CLAUDE_TOOL_USE_INPUT" as the request body (single-quoted, so the shell never expanded it — and no such env var exists anyway; Claude Code delivers the PreToolUse payload on the hook command's stdin). The backend 422'd on every single call, and curl's exit code stayed 0 on that 422, so Claude Code silently treated every operation as allowed — regardless of matcher, before or after 7ec8a2f. Fix: _hooks_command_claude() now uses `-d @-` to read the POST body from stdin, where Claude Code actually writes it. Also fixes _merge_hook_entry(): it only compared the `matcher` string when deciding whether an existing install was "already current", so once the matcher already read Bash|Write|Edit, a stale `command` (like the broken one above) would survive every future reinstall untouched. It now overwrites the whole entry whenever _matcher_is_mine() recognizes it as ours (also extended to recognize the old broken command, so an existing install upgrades in place instead of getting a duplicate matcher). Verified end-to-end in a live Claude Code session against the real KB: direct Write, Edit, and bare `rm` targeting KB files are now denied with the intended guidance message; MCP tools (write__delete_document, etc.) still pass through normally. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QqKnwdnye3njPuxqxvLSi8
cherry-pick c37dc32 后修正 hooks_matcher docstring 残留的 $CLAUDE_TOOL_USE_INPUT 描述,与实际 -d @- 实现一致。
…退出码 0/2/其它) WorkBuddy 官方/实测查证:payload 走 stdin、读 hookSpecificOutput.permissionDecision、 退出码 0/2/其它、配置 ~/.workbuddy/settings.json(须完全重启)。后端不依赖 tool_use_id/call_id,现有 -d @- 命令直接复用,无需单独适配。更新 WorkBuddy.json matcher_note/protocol/notes 与 DEV.md。
需求:同一版本可导出多份 dmg(不同企业),每份的可适配平台与展示名不同。
方案 A(打包期合并):platforms.json 保持默认不动,新增 desktop/enterprises/<name>.json
覆盖配置,build-backend.sh --enterprise <name> 合并后注入产物。
- platforms.json: 每个平台加 enabled 字段(默认 true,向后兼容)
- client_config.py: PLATFORMS 过滤 enabled=false;新增 platforms_meta() 返回
{key: {display, enabled}}(动态读 _load_platforms_data,与 PLATFORMS 一致)
- main.py: 新增 GET /api/platforms-meta(独立于 /api/client-config,检测状态零改动)
- store.js: applyPlatformsMeta() 启动时拉取并覆盖 clientPlatforms 的 label
(失败静默保留硬编码兜底)
- api.js: getPlatformsMeta()
- build-backend.sh: --enterprise <name> 解析 + 临时合并(copytree+JSON merge,
不污染源 platforms.json,trap 清理)+ 未知平台校验 + --add-data 指向合并目录
- release.sh: 一键打包入口(默认全平台 / --enterprise <name>)
- desktop/enterprises/: Acme.json 示例 + README 配置说明
- tests: TestPlatformsMeta + TestEnterpriseMerge 共 8 个测试
验证:合并后 PLATFORMS/detect_all/platforms_meta 三者一致(禁用平台全消失);
116 个 client_config 测试全过;前端构建守门 50/50;脚本语法与错误路径验证通过
- .gitignore: 忽略 desktop/enterprises/*.json(!template.json 白名单) - 删除已提交的 Acme.json(虚构示例名,不应入库) - 新增 template.json 通用模板(YourCompany 占位) - README: 改用 template 示例 + 说明配置文件不进仓库
需求(Apple 企业配置): - default_disabled=true → 未列出的平台全部禁用(只启用显式列出的) 缺省 false → 未列出的沿用 platforms.json 默认(向后兼容) - order 字段控制前端展示顺序(数字小靠前,None 排后保持原序) 改动: - build-backend.sh: 合并逻辑支持 default_disabled + order 透传 - client_config.py: platforms_meta 按 order 稳定排序输出 + 返回 order 字段 - store.js: applyPlatformsMeta 按 order 重排 clientPlatforms(前端兜底) - template.json: 补 default_disabled 语义说明(保留用户缩进风格) - tests: +3(default_disabled 启用/缺省、order 排序) 验证:119 个 client_config 测试全过;Apple 配置合并后仅 Enchante+ClaudeCode, Enchante 第一(order=1)、ClaudeCode 第二且 display=Claude Code (Apple Internal); 前端守门 50/50
需求:MCP/Hooks/Agent 三页保持一致平台清单;不支持某 kind 的平台不隐藏, 而是整行置灰 + 「平台原生不支持」文字解释(不写 kind,因用户已在对应页)。 - client_config.py: platforms_meta 返回 kinds(后端权威能力集) - store.js: applyPlatformsMeta 覆盖 kinds(前端硬编码仅离线兜底); platformsForKind 返回全部启用平台 + supported 标记; platformUnsupportedKinds 返回原生不支持的 kind 标签 - index.html: 设置矩阵不支持行置灰(ai-platform-row--unsupported)+ 状态文字 「平台原生不支持」+ toggle/deeplink 禁用;引导结论行追加 「X / Y 平台原生不支持」灰字 - components.css: 置灰行 + native 文字样式 验证:119 个 client_config 测试全过;platforms_meta kinds 正确 (ClaudeDesktop=[mcp]、Enchante=[mcp,agent]);前端守门 50/50
- enterprises/README.md: 补 default_disabled(未列出平台全禁用)、order(顺序)、 kinds(能力集,三页置灰依据)字段说明与场景表 - template.json: _comment 同步补 kinds 说明(保留用户缩进风格) - DESKTOP_APP.md: P0 记录企业定制打包 + 三页一致置灰完成项; P1「后端启动提速」方向更正(正解=进度挂钩,延迟 import mcp 收益≈0 不单独做)
根因:guideExecute 用 Promise.allSettled 并行执行所有平台的 kind 写入,
但 configureClient 有全局单写锁 clientConfiguring(防连点),并行时后到的
job 因锁被占而静默 return(.catch(()=>{}) 吞错)→ 仅第一个 kind(MCP)成功,
Hooks/Agent 全未配置。
修复:改为 for...of + await 串行执行(逐个 kind 写入,等待完成再下一个);
单 kind 失败 catch 不中断后续。引导批量配置本质是顺序写入,串行最安全
(避免后端同文件并发写),耗时多一两百毫秒无感知。
第 34 行 '企业: $ENTERPRISE(...)':UTF-8 locale 下 bash 把全角左括号
的首字节 \xEF 并入变量名 → 变量名变成未定义的 'ENTERPRISE\xef...',
set -u 触发 unbound variable。
修复:改用 ${ENTERPRISE} 显式分隔。
第 46 行 '✓ 企业定制: $ENTERPRISE($ENTERPRISE_FILE)': UTF-8 locale 下 bash 把全角左括号首字节 \xEF 并入变量名, set -u 触发 unbound variable(与 release.sh 34 行同 bug,本次漏改)。 已用 Python 脚本全量扫描 scripts/*.sh,确认变量后跟全角字符的 隐患已清零。
本地环境 nginx 同时监听 8080/8081、开发版后端占用 8080,桌面 App 从 8080 起协商端口虽能跳开,但 8080-8081 区间与开发环境高冲突。改为从 2030 起协商 (2030-2079),与开发端彻底分家,消除启动超时隐患。
根因:clientPlatforms 是硬编码 6 个平台,applyPlatformsMeta 只覆盖 label/kinds/排序、不删除被禁用的平台 → 企业配置(如 Apple 只启用 2 个) 下,设置 modal 仍显示其他 4 个平台,且因后端不检测它们 → 显示 「检测失败 · 重新检测」残影。 修复:applyPlatformsMeta 先 filter(只保留 meta 中存在的平台)再覆盖, 前端列表/三页矩阵/引导页/结论行全部与后端 enable 一致。 企业固定 dmg 场景下方案足够(App 重启重新过滤,稳定)。
1. 设置 modal:deeplink 按钮 :disabled 补 !clientInstalled —— 未安装时 禁用(状态文字已是「请先安装 X」),与引导页结论行一致 2. 按钮高度:btn--deeplink 固定 height 22px + line-height 20px + inline-flex 居中,与同行 toggle(20px) 对齐,消除每行高度不齐
12px 字号在 20px 高内刚好放下(上下 4px 余量),字号保持 --text-xs 不变, 行高零误差;不再需要缩小字号。
- 去掉整行 opacity 0.55(会把 dot 也淡化,与正常行 dot 不一致) - 名称/状态文字用 --text-secondary (#6b7280),与「请先安装/未配置」同灰阶 - 引导行 native 文字 --text-tertiary → --text-secondary 统一
设置 modal 的 ai-platform-row__state--soft(请先安装/请在安装后使用) 原用最浅灰 #d1d5db,与「平台原生不支持/未配置」的 #6b7280 不一致。 统一到 --text-secondary,消除多种灰色。
问题1(loading 文本不显示):animation:...both 与额外 opacity:0 冲突,
baseline opacity:0 覆盖 to 态 → 文本永久不可见。删多余 opacity:0。
问题3(deeplink 点击后跳转主页):Electron 主窗口 setWindowOpenHandler
只放行 http/https,enchante:// 被 deny → 链接未传给系统唤起 Enchante。
修复:
- main.js: ipcMain.handle('open-external') 协议校验 + shell.openExternal
- preload.js: exposeInMainWorld('__mykOpenExternal__') 桥
- store.js: 桌面端(__MYK_APP_MODE__)走 IPC;网页端保持 a.click()(零影响)
测试:
- +3 静态断言(store 分支 / main IPC / preload 桥)
- 更新 test_enchante_grouping_and_deeplink 为三页一致置灰断言(旧隐藏断言与新行为冲突)
- desktop_server.py: main() 新增 --mcp 分支,构造 Namespace(root=args.root) 复用 cli.cmd_mcp
(自动 init/身份检查/GC/心跳/app.run(transport=stdio)),--mcp 与 --port 互斥(--mcp 优先)
- client_config.py: mcp_entry() 加 sys.frozen 分支(frozen → 二进制 + --mcp),非 frozen 路径零改动
- myknowledge-backend.spec: 修复 datas 过期临时路径 /var/folders/.../tmp.sDf5SYTPPa/AiClientConfig
→ backend/AiClientConfig(该临时目录来自某次 --enterprise 构建,导致 spec 构建直接失败)
- scripts/mcp-smoke.sh(新增): 独立 MCP stdio 冒烟脚本,无需打包即可跑(默认 python -m backend.cli mcp,
--bin 指向 frozen 二进制);select 非阻塞读+close(stdin)优雅退出+wait(timeout)兜底,不挂起
- build-backend.sh: 打包成功后自动调用 mcp-smoke.sh --bin <frozen 二进制>,失败即 exit 1 阻断
- 修复 shell locale 坑: /cd /Users/chrismoray/Desktop/Moray/MyOpenSource/MyKnowledge_PlatForm && git commit -m "桌面版 MCP stdio 支持:--mcp 复用 cmd_mcp + mcp_entry frozen 分支 + 独立冒烟脚本
- desktop_server.py: main() 新增 --mcp 分支,构造 Namespace(root=args.root) 复用 cli.cmd_mcp
(自动 init/身份检查/GC/心跳/app.run(transport=stdio)),--mcp 与 --port 互斥(--mcp 优先)
- client_config.py: mcp_entry() 加 sys.frozen 分支(frozen → 二进制 + --mcp),非 frozen 路径零改动
- myknowledge-backend.spec: 修复 datas 过期临时路径 /var/folders/.../tmp.sDf5SYTPPa/AiClientConfig
→ backend/AiClientConfig(该临时目录来自某次 --enterprise 构建,导致 spec 构建直接失败)
- scripts/mcp-smoke.sh(新增): 独立 MCP stdio 冒烟脚本,无需打包即可跑(默认 python -m backend.cli mcp,
--bin 指向 frozen 二进制);select 非阻塞读+close(stdin)优雅退出+wait(timeout)兜底,不挂起
- build-backend.sh: 打包成功后自动调用 mcp-smoke.sh --bin <frozen 二进制>,失败即 exit 1 阻断
- 修复 shell locale 坑: ${DESC}/${1} 花括号界定变量名(全角标点紧邻 $var 在 UTF-8 locale 下并入变量名)
- 测试: test_client_config.py +2(frozen mcp_entry),新增 test_desktop_server.py +4(dispatch)
- docs/AI-SETUP.md: 桌面版 MCP 入口说明 + MCP command/args 是数组 spawn 无需引号" && echo "COMMITTED" 花括号界定变量名(全角标点紧邻 在 UTF-8 locale 下并入变量名)
- 测试: test_client_config.py +2(frozen mcp_entry),新增 test_desktop_server.py +4(dispatch)
- docs/AI-SETUP.md: 桌面版 MCP 入口说明 + MCP command/args 是数组 spawn 无需引号
- _base64_quote(): safe 从含 /、= 的宽集合收紧为 '-._~'(只留 unreserved),
base64 的 + → %2B、/ → %2F、= → %3D 全部 percent-encode,保证无损 round-trip
(MCP 与 agent deeplink 共用,改动一致安全)
- _agent_template(platform=''): 按平台选模板——Enchante → MyKnowledge-agent-Enchante.md
(精简版,已落盘 563 字符,内容定稿不改);其余/默认 → MyKnowledge-agent.md (完整版)。
agent_content() 非 Enchante 平台走默认完整版,零变化
- enchante_agent_deeplink(): role 改用 _agent_template('Enchante');deeplink 长度
~4206 → 2114,且编码后无裸 /、+、=
- myknowledge-backend.spec 纳入 git:.gitignore 在 *.spec 后加 !myknowledge-backend.spec
例外;spec 是源,PyInstaller 不运行时改写
- 测试: +6 —— _base64_quote 转义 /、= round-trip、_agent_template 精简/完整选择、
agent deeplink 长度 <4206 且无裸 /。后端测试 729 全绿
- docs/DEV.md: 记录 base64 全转义 + 精简模板 + spec 入 git 约定
frozen onedir 冷启动(导入 fastapi/uvicorn/mcp + _auto_init git init)在 8s 内 可能未就绪,导致 8s 默认超时误报失败。select 只在无响应时计时,happy path 仍 立即返回,故提高默认超时不影响正常冒烟速度,仅放宽失败判定上界。
- .codegraph/(CodeGraph 本地索引 db/log/sock/pid)与 .pytest_cache/ 加入忽略 - git rm --cached 移除已误追踪的 .codegraph/.gitignore 与 .codegraph/daemon.pid(本地保留,供 CodeGraph 运行)
- backend/__version__.py → 0.7.7(权威来源),同步 package.json / README(zh) / electron-builder.yml / docs/DESKTOP_APP.md / dmg 背景
- fix(build-backend): mcp-smoke.sh 冒烟用 bash 调用而非 ${PYTHON}(之前误用 python 解释 bash 脚本致 SyntaxError,阻断 release 构建)
Enchante 走 deeplink 安装且无配置文件,后端无法检测其配置状态(clientStatus 恒 false),显示「未配置」不准确。改为中性的「生成专属链接」,与按钮文案呼应。非 deeplink 平台文案不变。
根因:绿条展开(0.6s)后旧代码立即触发 loading-done 切主界面,而文本需 animation-delay 0.55s + 0.3s 淡入(0.85s)才完全显示。浏览器直接打开无 __mykLoadingDone__ 不切换所以正常;桌面 app 里 preload 注入后提前切换,文本 opacity≈0 时主界面已切走,用户看不到文本。 修复:绿条展开后延迟 1.1s 再触发 loading-done(文本 0.85s 完整淡入 + 约 0.85s 停留)。已用真实 preload + contextIsolation 的 Electron 环境实测:loading-done 触发时文本 opacity=1,用户可见约 0.85s。
根因:main.js 的 loading 兜底在后端就绪后固定 3s 就强制切主界面。后端就绪快(1-3s)时, 3s 兜底(t≈4-5s)恰好撞上 loading 动画(约 3.3s 走满 + 绿条展开 0.6s + 文本淡入 ≈ 4.7s) 刚完成的时机,绕过 loading.html 的 __mykLoadingDone__ 延迟,导致「初始化完成」文本 还没完整显示就被切走。 修复:兜底改为后端就绪后 9s(晚于 loading-done ~5s 正常触发),仅作极端兜底防卡死, 正常由 loading.html 进度走满后触发 loading-done 切主界面,保证文本完整显示。 补充 loading.html 注释:进度 setInterval 间隔 150ms(约 3.3s 走满,与后端冷启动同步)。
…--mcp Enchante reported "unrecognized arguments: mcp" when spawning the packaged myknowledge-backend binary in stdio MCP mode — some MCP client configs (e.g. one copied from the pip-installed `myknowledge mcp` CLI docs) invoke it with a bare `mcp` positional instead of the `--mcp` flag the frozen binary's argparse actually defines. Reproduced against the installed v0.7.7 app and confirmed fixed against a freshly built binary (full MCP initialize handshake succeeds either way now).
pip install -e . (editable/dev install path) reads pyproject.toml's dependency list, not requirements.txt — same missing-typer failure mode as the previous commit if that's the install path the build environment uses.
fix(build): use mcp[cli] extra
Real-device testing on a Mac with Enchanté installed showed the Agent deeplink (enchante://agent/install) never surfaced an install prompt. Root cause: Enchanté requires the display name duplicated *inside* the base64 config JSON (top-level "name"), not only in the outer ?name= query param — without it, deserialization/validation fails silently on Enchanté's side. Adds "name" to the bundle in enchante_agent_deeplink() and asserts it in the existing test_agent_deeplink test. The MCP-only deeplink (enchante://mcp/install) and the base64 URL percent-encoding are unaffected — both already matched Enchanté's requirements.
CoderMoray
pushed a commit
that referenced
this pull request
Sep 2, 2026
Enchante 的 Agent deeplink(enchante://agent/install)在真机上验证不可靠 (即便 PR #3 补上了缺失的顶层 name 字段,deeplink 本身对 Enchanté 版本/环境的 依赖仍不稳定),改为引导用户直接看使用手册,避免继续给一个不可靠的安装入口。 改动范围仅 Enchante + agent 这一格: - 设置弹窗 Agent 分组、首次引导 2.2 结论页,Enchante 那一行都改成不可点的置灰 开关(视觉与 Hooks 分组里 Enchante 那行「平台原生不支持」的置灰渲染一致), 描述文字改为「参考使用手册」,旁边加一个「查看手册」链接。 - store.js: usesDeeplink() 收窄为仅 Enchante+MCP;新增 isEnchanteAgentManual() 判定 + openEnchanteAgentManual() 打开手册。 - desktop/main.js + preload.js: 新增独立的 open-manual IPC 通道,用 shell.openPath 打开手册文件。没有复用 open-external,因为那个通道专门 block 了 file: 前缀(避免渲染层任意打开本地文件/命令);手册路径由主进程硬编码, 渲染层不传参,同样是为了不给渲染层传任意本地路径的口子。 TODO(分发前必须替换,已在代码里标注):MANUAL_PATH 目前是本机开发路径占位, 正式版应指向打包进 app 资源目录的手册文件,并加入 electron-builder 的 extraResources / build-backend datas。 Enchante 的 MCP deeplink、其它平台的 Agent 配置均未改动。 测试:只跑了改动直接影响的静态结构测试(不含全局前端/浏览器测试套件): pytest tests/frontend/test_stage3.py::TestStage3StaticStructure # 16 passed pytest tests/frontend/test_stage3.py::TestStage3Build # 1 passed(对新 build 的 standalone 断言) 同时更新了这两类测试里跟 Agent deeplink 按钮相关的断言(按钮已移除、改为手册引导), 以及一个受影响的浏览器测试(TestStage3Browser,未在本地跑,留给 review/CI)。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
问题
在装了 Enchanté 的真机上测试 App 里"生成 Agent 链接",链接能生成,但打开后 Enchanté 没有弹出安装确认框(静默失败)。
根因
enchante_agent_deeplink()生成的configbase64 JSON 只有role/skillNames/mcpServers三个顶层字段,展示名只放在外层 URL 的?name=查询参数里。真机排查确认 Enchanté 解析config时要求 JSON 内部也要有顶层name字段,缺了这个字段会导致反序列化/校验静默失败,不会有任何报错提示。MCP-only 的 deeplink(
enchante://mcp/install)和 base64 的 URL 转义(+///=的 percent-encoding)没有这个问题,不受影响。修复
在
bundle里加了顶层"name",值与外层?name=一致,避免重复硬编码字符串。测试
按 mentor 要求只跑了精准测试,没有跑全局前端测试套件:
包含更新后的
test_agent_deeplink(新增断言bundle["name"] == "MyKnowledge 知识管理专家")和test_agent_deeplink_shorter_than_before(确认加了字段后链接长度仍在 4206 字符基线内,实测 2178)。未在真机上重新验证 Enchanté 侧是否真的弹出安装框(需要那台装了 Enchanté 的 Mac),麻烦 review 时确认一下。