Skip to content

fix(ios-simulator): release disabled Host and settle WDA fallback - #2494

Open
horizon554 wants to merge 5 commits into
makecindy:mainfrom
horizon554:fix/ios-simulator-host-idle-teardown
Open

fix(ios-simulator): release disabled Host and settle WDA fallback#2494
horizon554 wants to merge 5 commits into
makecindy:mainfrom
horizon554:fix/ios-simulator-host-idle-teardown

Conversation

@horizon554

@horizon554 horizon554 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

这次改了什么

摘要

修复 iOS Simulator capability 关闭后 Host 仍长期占用 ownership / shell guard,以及 Native Sidecar 不可用时界面和输入没有稳定降级的问题。

关闭最后一个 capability provider、卸载最后一个 provider,或在「设置 → 内置工具」按用户/项目范围关闭 iOS Simulator 时,Host 会按 scope 清理 Cindy-owned 资源;清理成功后释放 singleton、writer lease 与 shell guard,之后 Agent 可以再次启动外部 Simulator.app / simctl,重新启用能力时 Host 仍可懒初始化。

Codex 的 open / xcrun PATH shim 现在通过当前 Desktop profile 的鉴权 loopback 通道实时复用项目开关与 Host 状态。能力关闭且清理完成后,同一个 Codex 任务无需重建任务或重启 Desktop 即可使用外部 Simulator;重新启用后,同一个 shim 会恢复拒绝外部 Simulator 操作。

Native Sidecar 在二进制、完整性或沙箱 preflight 阶段失败时,现在会把 admission 收敛为 failed,而不是一直遗留 AWAITING_PROBE。WDA 保持运行并成为稳定兼容路径;右侧栏只在 Native 确实处于 probe 时显示「检测中」,并且 WDA 模式的点击/滑动不再先发送一个必然失败的 Native touch。

变更类型

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

范围

  • 关联 Issue / 需求:iOS Simulator Host 关闭后不能恢复外部模拟器使用;测试实例长期显示「检测原生能力」。
  • 本 PR 包含:全局/项目范围 capability deactivation、失败可重试、Host 释放、Codex shell shim 实时状态同步、Native route status 收敛、WDA 输入降级。
  • 明确不包含:关闭 viewer/tab 时清理设备;测试实例选择或多 Desktop 实例管理;跨 profile Skill 链接清理;插件 manifest、批准状态或安装布局变更。
  • 用户可见变化:能力关闭且清理成功后无需重启 Desktop 或重建 Codex 任务,即可让 Agent 使用外部模拟器;重新启用后恢复内嵌能力保护;Native 不可用时状态不再无限显示「检测中」,点击/滑动继续通过 WDA 工作。
  • 是否存在 breaking change:无。

UI 变化

  • 引用的设计规范:docs/design-rules/DESIGN.md §11 Voice & Content(进行中状态只用于真实进行中的操作)与 §10 Light / Dark 双模式交付门槛。本次只修正既有状态文案的显示条件,不新增样式、颜色、布局或文案 key;沿用现有主题 token,因此 Light / Dark 实现均未改变。
  • 截图/录屏:未附。变化只在 Native capability 缺失/失败时,将错误的持续「检测中」改为既有「不可用/兼容」状态。

怎么验证的

自动验证

Desktop 定向:
pnpm exec vitest run src/main/mcp-integrations/__tests__/ios-simulator.test.ts src/renderer/features/right-sidebar/plugins/ios-simulator/__tests__/IOSSimulatorTabBody.test.tsx
结果:2 files,156 tests passed

Runtime 定向:
pnpm exec vitest run src/native-sidecar/process-manager.test.ts src/wda/process-manager.test.ts
结果:2 files,52 tests passed

Codex shell shim / Host policy 定向:
pnpm exec vitest run src/main/maker-host/__tests__/agentShellPolicyServer.test.ts src/main/maker-host/__tests__/agentShellGuards.test.ts src/main/cindy-brain/__tests__/iosSimulatorPluginGate.test.ts src/main/maker-host/__tests__/iosSimulatorShellHook.test.ts src/main/maker-host/__tests__/shellCommandPolicy.test.ts src/main/mcp-integrations/__tests__/ios-simulator.test.ts --project standard
结果:6 files,531 tests passed

pnpm --filter desktop run --if-present typecheck
结果:通过

pnpm --filter @cindy/ios-simulator-runtime run --if-present typecheck
结果:该 package 无 typecheck script,按仓库约定跳过

pnpm --filter @cindy/ios-simulator-runtime build
结果:通过(tsc --noEmit)

pnpm test:unit
结果:全仓 required unit tier 通过

git diff --check
结果:通过

pnpm check:dco
结果:5 commits 全部通过

手工验证

  • 已用隔离 Desktop 测试实例复核卡住表象;确认其中一次实例错配属于同时运行两个隔离 Desktop 的操作问题,不扩进产品代码。
  • 未做 UI Light / Dark 实机目检;本次没有样式或 token 改动。

未执行的验证

  • 未执行真实 Native Sidecar H.264 / HID smoke:当前测试实例未构建 Native Sidecar,不能把 WDA 降级验证冒充 Native 实机验证。
  • 未执行 Windows 实机验证:iOS Simulator runtime 只在 macOS 生效;全仓单元测试覆盖跨平台静态回归,最终以 CI 为准。

风险

风险分类

  • 无已知风险
  • SQLite / migration
  • system prompt
  • 协议兼容
  • 权限 / 安全 / 用户数据
  • 存量插件兼容(批准状态 / 指纹 / manifest 校验 / 安装布局 / 包格式)
  • 原生层 / fingerprint / OTA
  • 跨平台差异
  • 其他:

影响与回滚

  • 影响范围:仅 Desktop iOS Simulator Host 生命周期、Codex 在 macOS 上的 open / xcrun PATH shim、Native Sidecar admission、WDA fallback 与右侧栏 route status / input routing。
  • 回滚 / 降级方式:回滚本 PR 的五个 commit;Native Sidecar 不可用时仍可通过 WDA MJPEG + 离散输入降级。
  • 清理失败时会保留 ownership、Host singleton、writer lease 与 shell guard,允许后续重试;不会提前暴露仍由 Cindy 使用的设备。shim 只有收到当前 profile 鉴权 loopback 通道的精确 allow 才会转发;通道缺失、超时、鉴权失败、异常响应或 Desktop 正在退出时继续拒绝。
  • 项目范围关闭只清理该项目 binding;多个 enabled provider 时关闭一个不会提前清理;项目显式启用优先于用户默认关闭。
  • 存量插件影响:无。未修改 manifest、receipt、批准状态 schema、指纹、安装布局、包格式或凭证/偏好;已安装、已批准、已启用插件无需重新安装、确认或配置。
  • 插件作者契约无需同步:未修改 slot、manifest 校验、发现链、沙箱权限或 FORGE_GUIDE。
  • 不涉及 Mobile runtime fingerprint 或 OTA。

提交前检查

  • 已 review 完整 diff
  • 每个 commit 都带 DCO 签名(git commit -s,见 DCO
  • UI 改动已在「UI 变化」注明引用的设计规范章节(不涉及 UI 则跳过)
  • 未提交凭证、令牌或授权文件
  • 已补充必要文档(本次不改变公开接口或作者契约,无额外文档改动)
  • 已确认测试结果或说明未执行原因

Signed-off-by: JiangJiahao <jiangjiahao@xd.com>
Signed-off-by: JiangJiahao <jiangjiahao@xd.com>
Signed-off-by: JiangJiahao <jiangjiahao@xd.com>
@horizon554 horizon554 changed the title fix(ios-simulator): release host after capability is disabled fix(ios-simulator): release disabled Host and settle WDA fallback Aug 12, 2026
@horizon554
horizon554 marked this pull request as ready for review August 12, 2026 07:57
@horizon554
horizon554 requested a review from a team as a code owner August 12, 2026 07:57
@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown

Greptile Summary

此 PR 增加 iOS Simulator capability 停用后的 Host/ownership 释放、动态 shell guard,以及 Native Sidecar 失败后的 WDA 稳定降级。

  • 按用户或项目范围清理 Simulator binding,并在全量清理后释放 Host 资源
  • 通过本地 policy channel 让 Codex 的 open/simctl guard 跟随实时状态
  • 收敛 Native admission 与右侧栏 route status,并让 WDA 输入直接走兼容路径
  • 当前项目级清理仍会暂时关闭其他已启用项目对共享 Host 的访问

Confidence Score: 4/5

当前不宜合并,因为项目级 iOS Simulator 停用仍会在清理期间中断其他已启用项目对共享 Host 的访问。

项目范围 deactivation 会无条件设置进程级 closing 标志,而初始化和插件状态查询在返回现有 Host 前都会拒绝请求;因此另一个仍保留 binding 且明确启用的项目会在无关清理期间收到 shutdown 错误。

Files Needing Attention: apps/desktop/src/main/mcp-integrations/ios-simulator.ts

Important Files Changed

Filename Overview
apps/desktop/src/main/mcp-integrations/ios-simulator.ts 新增 scoped/global deactivation 和 Host 释放流程,但 scoped 清理仍使用进程级 closing 标志,导致跨项目访问中断。
apps/desktop/src/main/cindy-brain/index.ts 在 capability provider 停用或卸载后触发全局或项目级 Simulator 清理,并保留失败重试所需资源。
apps/desktop/src/main/maker-host/agent-shell-policy-server.ts 新增带 token 的 loopback policy server,使 shell shim 能按实时项目与 Host 状态决定放行。
apps/desktop/src/main/maker-host/agent-shell-guards.ts 将静态 Simulator 命令拦截改为 fail-closed 的动态 policy 查询。
packages/ios-simulator-runtime/src/native-sidecar/process-manager.ts 将 Native Sidecar preflight 失败收敛为稳定失败状态,以便 WDA 接管兼容路径。
apps/desktop/src/renderer/features/right-sidebar/plugins/ios-simulator/IOSSimulatorTabBody.tsx 仅在 Native 实际 probe 时展示检测状态,并根据稳定 route status 选择输入适配器。

Sequence Diagram

sequenceDiagram
  participant A as 项目 A 设置
  participant D as deactivateIOSSimulatorHost
  participant H as 共享 Host
  participant B as 项目 B
  A->>D: "projectWorkingDirs=[A]"
  D->>D: "defaultIOSSimulatorRuntimeClosing=true"
  D->>H: 清理 A 的 binding
  B->>D: 查询状态或初始化 Host
  D-->>B: Host is shutting down
  Note over H,B: B 的 binding 仍保留,但访问被进程级标志阻断
  D->>D: "清理完成后 closing=false"
Loading
Prompt To Fix All With AI
### Issue 1
apps/desktop/src/main/mcp-integrations/ios-simulator.ts:6754
**项目清理关闭全局 Host**

当项目 A 执行异步 capability 清理时,这里无条件设置进程级 `defaultIOSSimulatorRuntimeClosing`;项目 B 即使仍显式启用该能力且 binding 被保留,其 Host 初始化和插件状态查询也会收到 “The iOS Simulator host is shutting down.”,导致工具调用、状态查询和 Viewer 输入被无关项目的清理中断。

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (3): Last reviewed commit: "fix(ios-simulator): make Codex shell gua..." | Re-trigger Greptile

Comment thread apps/desktop/src/main/mcp-integrations/ios-simulator.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 79f744651d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// Close the actor queues before ownership reconciliation. This is the
// point at which the shell guard's protection becomes stricter, so no
// new boot/mutation can race the final sweep.
await Promise.all([actor.cancelAllLifecycleStarts(), actor.cancelAllMutations()]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Block new actor starts before draining the Host

When a create_instance or attach_device call has already passed assertHostActive() but is still awaiting resolveWorktreeRoot, it has not registered an actor lifecycle start, so this snapshot drain misses it. The operation can then register immediately after the final hasPendingOperations() check; deactivation may conclude that the Host is releasable, dispose it without ownership cleanup, and release the registry lease and shell guard while the new binding/device remains. Add an admission latch that is checked when actor work is registered, or revalidate and drain atomically before releasing the Host.

Useful? React with 👍 / 👎.

getPluginRegistry,
onProjectPolicyChanged: async ({ workingDir, id, effectiveEnabled }) => {
if (id === 'ios-simulator' && !effectiveEnabled) {
await deactivateIOSSimulatorForBuiltinToolProject(workingDir);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Canonicalize project paths before deactivating bindings

When a local session uses a symlinked working directory, project settings are keyed by that symlink path, but simulator bindings store realpath(workDir). Passing the settings path here therefore cannot match instance.worktreeRoot in deactivate(), because normalizeWorkingDirForProjectSettings does not resolve imported/user-managed symlinks; disabling iOS Simulator for that project returns successfully while its binding, Host lease, and shell guard remain active. Resolve both sides to the same canonical identity, or select bindings from the affected sessions rather than comparing these differently normalized paths.

AGENTS.md reference: AGENTS.md:L115-L116

Useful? React with 👍 / 👎.

@MagicLizi

Copy link
Copy Markdown
Contributor

@horizon554 👋 这个 PR 还有 3 条 review conversation 没 resolve(apps/desktop/src/main/mcp-integrations/ios-simulator.ts / apps/desktop/src/main/maker-ipc/register.ts),auto-review 因此暂时跳过、没法继续审查 / 合并。

如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。

Signed-off-by: JiangJiahao <jiangjiahao@xd.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3d3bb9e2f3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

const pendingCreateEvidenceArmed = pendingCreateEvidence?.isArmed() === true;
if (!scoped && (actor.listAll().length > 0 || pendingCreateEvidenceArmed)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reconcile pending-create evidence during default disable

When the user-default Settings handler disables iOS Simulator, it always passes shouldReleaseProject, which makes this deactivation scoped even when there are no explicitly enabled projects. If an interrupted create has left pendingCreateEvidence armed without a persisted binding, this branch skips reconcilePersistedOwnership(), selects no session to clean, and returns hostCanBeReleased: false without throwing; because the capability is now disabled, no later tool call triggers reconciliation, so the Host lease and shell guard can remain indefinitely and external Simulator/simctl use stays blocked. The default-disable path needs to reconcile this evidence or otherwise arrange a retry before reporting successful cleanup.

Useful? React with 👍 / 👎.

@MagicLizi

Copy link
Copy Markdown
Contributor

维护者确认

本 PR 涉及 iOS Simulator 功能改动(Host 释放与 WDA fallback 修复),触发产品确认门。

确认方式:请维护者直接在 PR 上 Approve;如需修改请 Request Changes,作者改完后重新 Approve 即放行。

讨论 issue:#2530

@MagicLizi MagicLizi added awaiting-discussion 等待维护者讨论(review-pr) touches:core 改动碰到架构核心路径(review-pr 自动维护,仅展示) touches:plugin-base 改动碰到插件基座(review-pr 自动维护,仅展示) touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示) labels Aug 12, 2026
Signed-off-by: JiangJiahao <jiangjiahao@xd.com>
return Promise.resolve({ hostReleased: true, cleanedInstanceCount: 0 });
}

defaultIOSSimulatorRuntimeClosing = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 项目清理关闭全局 Host

当项目 A 执行异步 capability 清理时,这里无条件设置进程级 defaultIOSSimulatorRuntimeClosing;项目 B 即使仍显式启用该能力且 binding 被保留,其 Host 初始化和插件状态查询也会收到 “The iOS Simulator host is shutting down.”,导致工具调用、状态查询和 Viewer 输入被无关项目的清理中断。

Context Used: 使用和PR描述相同的语言进行评论 (source)

Knowledge Base Used: Embedded iOS Simulator Integration

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/main/mcp-integrations/ios-simulator.ts
Line: 6754

Comment:
**项目清理关闭全局 Host**

当项目 A 执行异步 capability 清理时,这里无条件设置进程级 `defaultIOSSimulatorRuntimeClosing`;项目 B 即使仍显式启用该能力且 binding 被保留,其 Host 初始化和插件状态查询也会收到 “The iOS Simulator host is shutting down.”,导致工具调用、状态查询和 Viewer 输入被无关项目的清理中断。

**Context Used:** 使用和PR描述相同的语言进行评论 ([source](https://app.greptile.com/review/custom-context?memory=instruction-0))

**Knowledge Base Used:** [Embedded iOS Simulator Integration](https://app.greptile.com/xindong/-/custom-context/knowledge-base/makecindy/cindy/-/docs/ios-simulator.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f870303558

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +13296 to +13302
if (id === 'ios-simulator' && enabled === false) {
const registry = getPluginRegistry();
await deactivateIOSSimulatorForBuiltinToolDefault({
// A project-level explicit enable outranks the user default. Keep its
// active bindings while retiring projects that now resolve disabled.
shouldReleaseProject: (workingDir) => !registry.isEnabled(id, workingDir),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prevent stale disable cleanup after re-enabling

When a second PLUGINS_SET_ENABLED invocation re-enables ios-simulator while this awaited deactivation is still running, the new preference is persisted concurrently but does not cancel or supersede the older cleanup. If the disable pass has already selected a matching session, it continues shutting down or deleting that session's simulator and may dispose the Host after the enable call succeeds, leaving the currently enabled capability without its active simulator state. Serialize these writes with teardown or revalidate a preference generation before cleaning each selected session and releasing the Host.

Useful? React with 👍 / 👎.

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:plugin-base 改动碰到插件基座(review-pr 自动维护,仅展示) touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants