Skip to content

feat(plugin): install and run plugins with namespace identity - #5166

Open
kelv1nq1an wants to merge 43 commits into
makecindy:mainfrom
kelv1nq1an:feat/plugin-namespace-client-foundation
Open

kelv1nq1an wants to merge 43 commits into
makecindy:mainfrom
kelv1nq1an:feat/plugin-namespace-client-foundation

Conversation

@kelv1nq1an

@kelv1nq1an kelv1nq1an commented Sep 28, 2026 •

Copy link
Copy Markdown

这次改了什么

摘要

客户端现在识别服务端下发的 namespace,并以安装实例的物理身份隔离插件运行、凭证、用户数据和窗口。存量 XD 插件在原安装目录完成身份盖章,不会仅因升级搬入 _ns;插件包也不能自行声明 namespace。

未收到 namespace 的新安装会作为 root 安装,并在收据中明确写入 namespace: null。存量收据,或被旧客户端重写过的收据,可能缺少该字段;此时先保留“身份待确定”,不直接推断为 root。企业市场安装和 Forge 自测按经过核实的组织身份取得 Broker 资格,不靠插件名前缀授权。

本 PR 从 #4910 重开;配套服务端 S1 已合入(xindong/cindy-server#760)。

变更类型

  • feat 新功能
  • fix 缺陷修复
  • refactor / perf 重构或性能优化
  • docs / test / chore 文档、测试或工程维护
  • 其他:

范围

  • 关联需求:企业插件 namespace 第一阶段,覆盖客户端身份与安装链路。
  • 本 PR 包含:交付字段解析、按实例寻址、存量收据原位迁移、降级后身份恢复、同名实例隔离、媒体及用户数据搬迁、企业特权判定和按实例确认安装。
  • 不包含:服务端 S2 生产开关、主动搬迁尚未发生同名冲突的存量 XD 安装,以及 GitHub 发包流程。
  • 用户可见变化:没有新增界面;支持同名 root 与企业插件分别安装和使用。
  • 兼容性:缺少 namespace 的交付仍安装到旧目录;旧批准收据不要求用户重新安装或重新确认权限。证据不足的身份不会被猜成 root。

UI 变化

  • 不涉及界面或文案调整;本次改动集中在 Desktop Main 的安装、身份和授权链路。

怎么验证的

自动验证

当前分支做过以下本地验证,不是全量 CI 或实机验收:

  • Desktop 迁移、GhostManager、市场服务及降级恢复相关的六个测试文件:567 项通过。
  • 媒体账本、身份特权、OIDC、凭证存储等六个测试文件:193 项通过。两组有重叠,不累加为总数。
  • pnpm --filter desktop typecheck:通过。

手工验证:历史记录

以下操作发生在隔离国内开发版 Cindy-dev2-plugin-namespace-client-2641db,没有使用 --shared,也没有修改正式版数据目录。这些结果不是最新 HEAD f6ec31502 的实机验收。

在当时的现网市场中,XD 默认插件可以安装且仍位于原目录;插件包不携带 namespace;卸载后可不重启直接重装。当时还观察到接口不下发 namespace 时收据会省略字段。当前代码已改为新装 root 显式写入 null,不能再用该次观察证明当前行为。

在同一隔离客户端接入本地 mock 市场后,观察到存量 XD 插件在原目录盖章;root 与企业 twin 可同名共存,显式指定 namespace 的命令指向对应实例,未指定时会提示歧义;卸掉其中一份不影响另一份。假 xd- 插件未取得企业特权,开启开发环境的前缀闸后安装被拒。飞书 OAuth 曾在重新同意后连接成功;这不能证明升级过程无需重新授权。

尚待最新构建验收

  • 从正式版已有插件升级:验证市场离线、回网迁移及切回另一数据 owner;全过程不重装、不重新授权。
  • 存量企业插件原位盖章后,再安装同名 root:检查 KV、OAuth、密钥、Library、技能、媒体、窗口、面板及持久引用的归属,新 root 不继承旧实例数据。
  • 注入搬迁目标冲突、执行失败和中断,再重启:确认旧插件可恢复、数据未丢失且不会出现两份活动实例。已有部分自动化覆盖,仍缺实机重启验证。
  • 组织 A → B → 个人账号及在途调用切换:检查身份和凭证隔离,以及 public/personal 插件原有可见性。
  • 对同名实例分别执行单独更新、update-all、defaultInstall 和下架移除:确认另一实例的版本、批准状态与数据不变。
  • 验证能力正反例:真实 XD 身份可用,其他组织的同名或同前缀插件不可借权;Mivo 历史密钥读、写、删除一致;root 与新 Forge 不继承旧别名;无前缀 Forge 完成实际 Broker/OIDC 调用。
  • 用最新构建对接真实 S1;以正式安装包验证 xd- 前缀限制。隔离环境或 mock 的 S2 测试须与真实服务联调分开记录。
  • 全量 Desktop 测试尚未运行。

风险

风险分类

  • 无已知风险
  • SQLite / migration(涉及媒体引用搬迁;没有 schema 变更)
  • system prompt
  • 协议兼容
  • 权限 / 安全 / 用户数据
  • 存量插件兼容
  • 原生层 / fingerprint / OTA
  • 跨平台差异
  • 其他:

影响与回滚

  • 影响 Desktop 插件安装、运行、市场账本、媒体与插件用户数据;正常升级不会主动搬动存量 XD 插件目录。
  • 首次普查会捕获旧收据,核实身份后原位盖章。若降级客户端在启停或更新时擦除了 namespace,新客户端仅在批准包、原始清单、当前组织及唯一市场记录相互吻合时自动恢复企业身份;证据不足则保持待确定。
  • 旧客户端可能读取但不保留新收据字段,因此不能把“回滚客户端”视为无风险操作。降级前应备份,并在再次升级后核对插件身份和用户数据。

提交前检查

  • 已 review 改动
  • 提交带 DCO 签名
  • UI 变化栏已注明不涉及
  • 未提交凭证或令牌
  • 已区分完成的验证与尚未执行的验收

@kelv1nq1an
kelv1nq1an requested a review from a team as a code owner September 28, 2026 03:45
@greptile-apps

greptile-apps Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Critical risk] Overhauls plugin identity and storage to use namespaces.

PR 看起来可以合并;手机群数量超过 200 时的列表限制是非阻断问题。

Summary

本 PR 为桌面插件加入 namespace 身份与原位迁移,并在近期改动中补强搬迁恢复和技能快照处理;同期还加入手机群聊、调整远程桌面控制流程等功能。

  • 手机群列表目前最多下发 200 个群,超出部分无法从手机端发现。
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[手机群列表] -->|请求前 200 项| B[远程资源 Provider]
  B -->|读取全部群并截取| C[桌面群聊服务]
  C -->|返回群数据| B
  B -->|最多 200 项| A
Loading

Reviews (4) · Last reviewed commit: "fix(plugin): preserve physical identity ..."

Comment thread apps/desktop/src/main/cindy-brain/GhostManager.ts
Comment thread apps/desktop/src/main/cindy-brain/ghostNamespaceMigration.ts Outdated
Comment thread apps/desktop/src/main/cindy-brain/ghostFirstPartyPrivilege.ts Outdated
Comment thread apps/desktop/src/renderer/cindy-brain/ghostPanelBody.tsx
Comment thread apps/desktop/src/renderer/cindy-brain/ghostCommand.ts Outdated
@MagicLizi

Copy link
Copy Markdown
Contributor

本 PR 命中维护者确认门(product / pluginBase / arch 三类触发),自动流程在获得维护者确认前不会合并。

本次改动:客户端识别服务端 namespace,安装、运行、凭证与窗口按物理实例身份工作;已有 XD 插件原位盖章迁移、不搬目录;企业市场装入与 Forge 自测按组织身份给 Broker;安装确认按 namespace/实例 id 查找。

为何需确认:

  1. product:feat + 生产 UI 路径(GhostPanelBubbleLayer.tsx / GhostSettingsWebview.tsx 等);
  2. pluginBase:改到插件基座(GhostManager.ts 等),影响全部已装插件,请维护者明确确认存量插件兼容(升级后已装/已批准/已启用插件照旧可用,无需重装或重新授权);
  3. arch:核心路径 6184 行、总 diff 7246 行的大改动。

确认方式:请维护者直接在本 PR 上 Approve;需要作者修改则 Request Changes 写明要求,改完后重新 Approve。

讨论 issue:#5168

@MagicLizi MagicLizi added awaiting-discussion 等待维护者讨论(review-pr) touches:core 改动碰到架构核心路径(review-pr 自动维护,仅展示) touches:large-diff 改动量较大(review-pr 自动维护,仅展示) touches:plugin-base 改动碰到插件基座(review-pr 自动维护,仅展示) touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示) labels Sep 28, 2026
@kelv1nq1an

Copy link
Copy Markdown
Author

@greptile-apps review

Comment thread apps/desktop/src/main/cindy-brain/ghostNamespaceMigration.ts Outdated
@kelv1nq1an

Copy link
Copy Markdown
Author

@greptile-apps review

Comment thread apps/desktop/src/main/cindy-brain/GhostManager.ts Outdated
Comment thread apps/desktop/src/main/cindy-brain/index.ts Outdated
Comment thread apps/desktop/src/main/cindy-brain/GhostManager.ts Outdated
@kelv1nq1an
kelv1nq1an force-pushed the feat/plugin-namespace-client-foundation branch from 662b89b to 2b67e75 Compare September 28, 2026 09:49
@kelv1nq1an

Copy link
Copy Markdown
Author

@greptile-apps review

@fmfsaisai

Copy link
Copy Markdown
Collaborator

基于最新 HEAD 2b67e7555a606547f8a41f4afd15f876d023368e 再次核对 namespace 规划、实际代码与手工验证记录。结论:上轮问题已有修复,但仍有隔离和升级兼容问题,建议修复并补齐验证后再合并。

已确认改善:原位安装在 receipt 盖章后能获得 Forge Broker;自由命名 Forge 的 OIDC 不再依赖旧前缀;新装 XD Mivo 能找到历史密钥;旧企业插件与同名 root 可以完成安装。下面是仍需处理的部分。

1. 搬迁遗漏媒体归属,导致新 root 继承旧企业插件的媒体访问权。

relocateGhostUserData 只处理 KV、凭证、文件和 Library,没有迁移媒体账本中的 refId / originId。复现路径:旧 acme-helper 盖章为 acme → 安装 root 同名插件 → 企业实例搬到 _ns/acme/acme-helper。使用真实 GhostManager、文件系统与 SQLite 检查后,ghostCanRead(hash, 'acme-helper') 为 true,而新企业物理身份对应的读取为 false。画廊也按相同旧键查询,因此不是只有知道 hash 才会遇到的问题。

需要让媒体归属随安装实例保持一致,并纳入搬迁事务及恢复;也可以通过稳定的实例映射避免搬动数据身份。新 root 不能继承这些引用。

代码:index.ts、媒体权限判断。

2. 目标数据冲突时,新安装失败会连带破坏旧插件可用性。

在源和目标存储位置分别预置不同 KV 数据后,安装同名 root:代码先搬走旧插件目录,再发现数据目标冲突。新安装返回 io,旧企业插件变为 approval.state=invalid;重新创建 Manager 模拟启动恢复后仍为 invalid,冲突会持续阻止恢复。

应在搬动前完成目标数据冲突检查,或在失败时完整恢复旧安装。保留 journal、防止覆盖数据是必要的,但不能把原本可用的旧插件永久留在隔离状态。

代码:GhostManager.ts。

3. 尚未完成 namespace 迁移的旧 Forge 仍会失去 Broker。

本轮已修复“receipt 已盖章”的情况,但对升级时已捕获为 pending 的合法旧 Forge 安装,缺 namespace 仍被解释成 null,随后因与当前 orgSlug 不同而拒绝。以真实旧 receipt 的 agent-forge 来源复现:盖章前拒绝,盖章后通过。

需要保留已核实旧安装在迁移期间的原有资格,同时确保升级后新安装不能靠缺字段取得旧资格。不能依赖用户重新安装或重新确认权限。

代码:ghostFirstPartyFacts.ts、Forge 门槛。

4. 特殊权限仍未按已定设计限定可信来源。

  • XD 端口回收和头像下载改成了 isOfficialGhostId(facts.ghostId)。即使 namespace 和当前组织均为 acme,合法企业市场的 xd-tool 也得到这些能力。规划要求的是经过验证属于当前 XD 组织的安装身份,不是任意企业的官方前缀名称。
  • Mivo 历史别名只根据字符串中的 namespace 为 null/xd 来判断,没有区分可信市场、root 和新 Forge。应仅保留合法历史安装及可信市场 xd / xd-mivo 的资格,并保证旧键读、写、删除一致;不能直接取消合法旧安装的别名,也不能只凭名称给新实例授权。

当前名称限制挡住了部分入口,但这些边界在 S2 放开命名之前必须完成,因此目前还不能宣称已支持 S2 开启后的全部行为。

代码:XD 能力判断、Mivo 别名判断。

手工验证覆盖还不足。

现有记录覆盖了默认安装、缺字段保持旧协议、原位盖章、两份新插件同名共存、指令歧义和卸载隔离。这些是有效证据,但“两份新安装共存”不等于“旧企业实例迁移后与新 root 共存”;“重新同意后连接成功”也不能证明升级无需重新授权。

建议补齐以下验收,并注明实测构建 commit、环境、操作和结果:

场景 必须确认的结果
当前正式版已有安装升级 不重装、不重新授权;市场暂不可达或迁移未完成时保留旧资格,联网后完成迁移;覆盖升级后首次切回的另一数据 owner
旧企业原位安装后,再装同名 root KV、OAuth、密钥、Library、技能和媒体归属正确;窗口、面板及持久引用仍指向原实例;新 root 不继承旧数据
搬迁冲突、失败、中断、重启 旧插件可恢复,不丢数据,不形成两份活动实例;故障点宜用自动化注入,实机补测重启恢复
组织 A → B → 个人及在途操作 不使用前一组织的身份或凭证;public/personal 原有可见性保持;覆盖 OAuth/插件调用在途切换
同名实例更新及移除 单独更新、update-all、defaultInstall、下架移除只影响目标实例,另一份版本、批准状态与数据不变
能力正反例 真 XD 能力可用,其他企业同名不可用;Mivo 历史密钥读写删除一致,root/新 Forge 不继承;无前缀 Forge 实际 Broker/OIDC 调用成功
最新构建与真实 S1 联调 mock 市场可用于客户端回归,但不能替代真实 S1 服务联调;开发版环境变量模拟名称闸也不能完全替代 packaged 构建验证

“线上尚未开启 S2”不妨碍使用隔离环境/mock 验证自由命名、跨组织、update-all 等客户端行为,但测试结果应明确区分模拟验证和真实服务联调。最后两次搬迁修复尤其需要基于最新构建重新验收。

本轮在临时副本复跑相关现有单测 131 个,均通过;补充定向反例确认了上述问题。媒体复现使用真实文件系统和 SQLite,并执行 PR 中的数据搬迁函数;不涉及真实用户数据。以上属于代码与针对性测试结果,本轮未重新操作真实客户端,不把这些测试当作 E2E 已通过。

@kelv1nq1an
kelv1nq1an force-pushed the feat/plugin-namespace-client-foundation branch from 2b67e75 to f6ec315 Compare September 29, 2026 08:51
@MagicLizi MagicLizi added the touches:rules 改动碰到规则 / 规范文档(review-pr 自动维护,仅展示) label Sep 29, 2026
Signed-off-by: fattycat <fattycat.u@gmail.com>
Stamp namespace onto existing installs in place so XD plugins stay at
their current directories and storage keys. Stop, uninstall, OAuth,
media, and webview use the physical identity, not a derived _ns path.
Author packages still cannot declare namespace.

Signed-off-by: fattycat <fattycat.u@gmail.com>
… one

Legacy installs were writing namespace: null into receipts. Keep the
field omitted unless the caller explicitly passes namespace.

Signed-off-by: fattycat <fattycat.u@gmail.com>
Namespaced Node workers were matching device/plugin authorization
against manifest.id, so _ns instances could not open the auth card.

Signed-off-by: fattycat <fattycat.u@gmail.com>
Install and update wrote pending markers under _ns/<namespace>/ but
cleared them with the bare ghostId, leaving leftover journals after a
successful namespaced install. Require organization namespace when
currentOrganization.orgSlug is present.

Signed-off-by: fattycat <fattycat.u@gmail.com>
$command and Host capability chips now inject the storage-part instance
id. ghost_call was re-resolving with the catalog ghostId and dropping
namespace, so a public twin made namespaced calls GHOST_AMBIGUOUS.
Pin the instance after the first unique resolve for setup, grants,
revalidation, and dispatch. Ledger, runtime, composer, and cards look
up the same physical id instead of manifest.id.

Signed-off-by: fattycat <fattycat.u@gmail.com>
GHOST_AMBIGUOUS was missing from setup target validation, census
candidates were typed as readonly, and identity helpers narrowed
valid ghost ids to never. Align the types with the physical instance
id lookups.

Signed-off-by: fattycat <fattycat.u@gmail.com>
Linux shard 1/2 failed on a 5s mobile Maestro dry-run timeout. This
branch does not change apps/mobile; empty commit retriggers checks.

Signed-off-by: fattycat <fattycat.u@gmail.com>
Update-all matched installed ghosts by manifest.id, so a root/org twin
could donate the wrong approval token. Resolve by market namespace or
physical root, and key batch versions/ignore-round by pluginId.

Signed-off-by: fattycat <fattycat.u@gmail.com>
Library delete still validated isValidGhostId after IPC accepted instance
ids, so org installs could not trash their library. Canonicalize to the
storage part for overview, relocate, and delete.

Signed-off-by: fattycat <fattycat.u@gmail.com>
Main now requires a consent context on market install. The namespaced
drift case still called the old two-argument signature after rebase.

Signed-off-by: fattycat <fattycat.u@gmail.com>
Signed-off-by: fattycat <fattycat.u@gmail.com>
Signed-off-by: fattycat <fattycat.u@gmail.com>
A tombstoned root row fell back to its same-name organization twin, while Connection only checked manifest identity. Require the trusted receipt namespace to match the market route so root copies cannot borrow organization OIDC access.
Physical namespace relocation previously moved plugin data but left per-project disables keyed by the old root ID, so the relocated plugin became usable and a new root plugin inherited its disable. Atomically remap those preferences at the end of relocation, preserve unreadable files for retry, and cover the repeated migration path.
The earlier workdir disable fix covered one physical-ID state store, but recommendations, recent use and live detached windows still used the old root ID. Move owner-scoped recommendation history on relocation, key the suggestion flow by physical instance, and rebuild the detached window before a new root plugin can claim it. Cover duplicate identities and interrupted relocation.
…ommendations

Signed-off-by: fattycat <fattycat.u@gmail.com>
Legacy purge notices omitted namespace after organization installs gained namespaced ledger keys, so cleanup silently skipped them. Resolve only a unique matching organization record and refuse ambiguous notices. Keep namespace through mobile call grouping and projection, and resolve remote identity cards by the selected instance instead of a bare id. Regression tests cover the missed compatibility path and same-id root/org calls.

Signed-off-by: fattycat <fattycat.u@gmail.com>
Namespace collisions recurred because renderer card matching and setup lifecycle snapshots still used bare ghost ids after namespaced installation keys were introduced. Use physical instance ids for card ownership and setup notifications, with red-green regressions for same-id root and enterprise plugins and in-place stamps.
…ce migration

Signed-off-by: fattycat <fattycat.u@gmail.com>
Signed-off-by: fattycat <fattycat.u@gmail.com>
Signed-off-by: fattycat <fattycat.u@gmail.com>
Signed-off-by: fattycat <fattycat.u@gmail.com>
Prevent legacy WebView sessions from surviving namespace commits, and use instance identity for model lookup, uninstall cleanup, offline market recovery, and home suggestions. Cover matching and mismatched identities with regression tests.

Signed-off-by: fattycat <fattycat.u@gmail.com>
…nd cards

Signed-off-by: fattycat <fattycat.u@gmail.com>
Signed-off-by: fattycat <fattycat.u@gmail.com>
Preserve S1 and S2-disabled publishing compatibility while supporting namespace-aware installs and verified organization identities.

Pin requests and sessions to owner and approval identity; archive source-owned state and serialize recovery with mutations. Regression guards prevent stale guest requests, delayed Library creation, and superseded recovery jobs from reaching replacement installs.

Validation: 60 Vitest files / 1977 tests passed; Desktop typecheck and development-doc contract checks passed. Existing lint diagnostics are unchanged.
Signed-off-by: fattycat <fattycat.u@gmail.com>
Consolidate physical-instance lookup and relocatable state for S1 and both S2 rollout modes, preserving delivery namespace tri-state and trusted legacy provenance.

Previous fixes still reselected plugins by bare name or retained physical-key-only authorization state after relocation. Bind revalidation, adapters and confirmation caches to the installed target, serialize recovery against owner/journal/receipt snapshots, and migrate durable staging metadata alongside user-data references.

Add regression coverage for same-name coexistence, stale authorization and subscription callbacks, superseded recovery transactions, and replayable staging migration. Verified red-green regressions and the complete candidate in an independent worktree: 4871 desktop tests, 218 protocol/tools tests, typechecks and documentation checks.

Signed-off-by: fattycat <fattycat.u@gmail.com>
Signed-off-by: fattycat <fattycat.u@gmail.com>
@kelv1nq1an
kelv1nq1an force-pushed the feat/plugin-namespace-client-foundation branch from f6ec315 to 45362ca Compare September 30, 2026 14:33
@MagicLizi MagicLizi added the touches:security 改动碰到安全边界(review-pr 自动维护,仅展示) label Sep 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-discussion 等待维护者讨论(review-pr) touches:core 改动碰到架构核心路径(review-pr 自动维护,仅展示) touches:large-diff 改动量较大(review-pr 自动维护,仅展示) touches:plugin-base 改动碰到插件基座(review-pr 自动维护,仅展示) touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示) touches:rules 改动碰到规则 / 规范文档(review-pr 自动维护,仅展示) touches:security 改动碰到安全边界(review-pr 自动维护,仅展示)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants