feat(windows): feasibility spike for a supervised C#/.NET maka-cu Windows helper
- Dominant language
- TypeScript
- Stars
- 5.4k
- Forks
- 502
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 715
Description
## Relationship
Child of #3785 (Windows Computer Use epic), Phase 5 of the Windows roadmap in #2142. The bounded scope follows the maintainer clarifications in #3785, rather than its original broad implementation checklist.
## Context
The maintainer preference for the bounded v1 preview is a **C#/.NET helper supervised directly by the existing TypeScript host**, subject to a small feasibility spike confirming packaging and lifecycle behavior on Windows. This issue is that spike: it does not define the final public protocol, deliver the production semantic v1 slice, or change macOS `maka.cu`.
Run against the **published artifact in the intended packaged-app layout**, initially a self-contained single-file exe, not only `dotnet run`. Single-file publishing is a hypothesis to test, not a reason to reject an otherwise viable .NET helper automatically.
## Spike scope — six checks
1. **Long-lived startup/handshake.** Start and retain the helper; complete a bounded line-delimited JSON-RPC 2.0 handshake over stdio, with private protocol id `maka.cu.windows/0` or an agreed private id, version, capabilities, and limits. Baseline host deadlines: handshake **10s**, request **20s**, then cancellation grace **2s** where applicable. Record any divergence and its reason.
2. **One MTA UIA observation.** Use a dedicated non-UI MTA worker (`CoInitializeEx(MTA)`) and one explicitly selected, already-running top-level HWND. Bound tree nodes, depth, text/response size, and elapsed work. Bind the observation to `pid + processStartTime + hwnd + windowGeneration` and a retained UIA root. A traversal time budget alone is not proof that a blocked provider call can be interrupted; check 6 supplies the recovery boundary.
3. **One supported semantic action.** Implement `set_value` through `ValuePattern`; a click through `Invoke`/`Toggle`/`SelectionItem` is optional only if trivial. Resolve an opaque token only within its originating snapshot, atomically spend the snapshot before mutation dispatch, revalidate the target before and after dispatch, and use readback for typed `verified`/`refused`/`unknown` outcomes. Unknown outcomes never trigger automatic mutation replay.
4. **Target-window WGC capture.** Use `IGraphicsCaptureItemInterop::CreateForWindow(HWND)` for the same target generation. Report capture and action paths independently. Confirm that occluding the target does not substitute the covering window's pixels; return `capture_unavailable` on capture failure, with no silent screen-rectangle fallback. Tree and frame must identify the same target, without claiming temporal atomicity for dynamic content.
5. **Cancellation with execution settlement.** Exercise cancellation before dispatch, after dispatch, and while a provider call is blocked. Keep stdio/control handling responsive independently of the UIA worker. A cancellation ACK alone is not completion: the original request must settle with its actual outcome where known, or `unknown` if dispatch may have occurred. Before-dispatch cancellation must prove no mutation occurred. Release the execution lane only after the old operation has ended or the old helper has been confirmed terminated; do not report cancellation while abandoned work continues in the background. If the request cannot settle within the **2s** grace, the host terminates the helper as in check 6. Killing the helper does not undo an action already delivered to the target.
6. **Recovery after a hung provider.** Use a deterministic fixture that blocks an actual UIA provider call; sleeping only inside the helper is insufficient evidence. The supervisor terminates and recreates the **helper**, not the user's target application/provider process. Confirm old-helper exit before admitting new work, advance helper generation, invalidate all old snapshots/tokens, and complete a fresh observe/action/readback against a responsive fixture target after restart. The target process must remain alive; release its injected hang through the test fixture if needed, and record that step. Specify restart count, backoff, recovery deadline, and whether restart is eager or next-request-driven. A successful handshake alone does not prove recovery.
### Target identity and invalidation
Define how `windowGeneration` is established and invalidated, how it relates to the retained UIA root, and how helper generation scopes snapshots. A field with that name is not evidence of detecting HWND reuse. PID/start-time checks do not distinguish replacement windows within the same process.
Use the fixture to verify window close/recreation, control replacement, and duplicate use of a spent token. Ambiguous or changed identity must fail closed, never rematch by name/index. A replacement or modal HWND requires new explicit selection. No old token may become usable after helper restart.
### Parent death and Windows shutdown
Treat stdin EOF as a shutdown trigger, **not as sufficient proof of orphan prevention**. Verify normal protocol shutdown/EOF and abrupt host termination, including while UIA is blocked. The helper must exit within a declared deadline without waiting indefinitely for its MTA worker; record residual-process evidence.
Evaluate an independent parent-liveness mechanism or a correctly owned Windows kill-on-close Job Object if EOF handling alone cannot establish that guarantee. Record the chosen ownership and handle-lifetime behavior. Do not copy the macOS `SIGTERM` graceful-shutdown assumption: Node terminates Windows children forcefully for these signals. Use protocol shutdown/EOF for the graceful path and a bounded forced-termination path.
## Evidence environment
Run checks 1–6 on the published artifact on an **interactive Windows 11 x64 desktop**, with a deterministic adversarial fixture and committed reproduction scripts. Do not assume an ordinary GitHub-hosted runner supplies the required interactive desktop; local or self-hosted evidence is sufficient for this spike.
Include a clean-machine run with **no installed .NET runtime or development SDK**, copying only the declared deliverables. Record Windows build, Node/.NET SDK/CsWinRT versions, publish settings, artifact hashes, exact commands, and per-check pass/fail/blocked results. Do not treat a run blocked by environment setup as a demonstrated technical failure or a pass.
## Packaging assumptions — verify or explicitly revise
- Initial target: **self-contained single-file win-x64**, with trimming disabled. Pin the actual Windows target framework/SDK version and all required native dependencies.
- Evaluate **.NET 10 LTS** as the forward-looking baseline. .NET 8 remains an allowed short-term spike choice with a documented reason and upgrade plan; its support ends **2026-11-10**. Self-contained runtime patching requires rebuilding and redistributing the helper.
- Measure executable and total distribution size, native-library extraction location/permissions/footprint if applicable, cold/warm startup, first-frame latency, and memory. Do not assume a single exe implies no extraction or external prerequisites.
- Helper is a directly supervised TypeScript-host child over stdio, subject to the parent-death tests above. No orphan helper guarantee may rest only on graceful shutdown.
- Spike artifacts remain unsigned (`signature: none` / `unsigned` in spike metadata), with `distributionReady: false`. Authenticode and production distribution readiness are deferred; macOS `adhoc` signing terminology does not apply here.
- No changes to `apps/desktop/bundled-tools.json` pins and no new public protocol revision.
## Decision points — measured evidence and recommendation
| # | Decision | Evidence to collect |
|---|---|---|
| D1 | Managed `UIAutomationClient` vs direct COM (`UIAutomationCore`) | Cache requests, UIA3 availability, thread/object ownership, provider timeout behavior |
| D2 | Bounded base64 on stdout vs negotiated temporary files | Frame latency/size, base64/envelope overhead and response limits, stdout backpressure/control responsiveness; file ownership and cleanup if files are used |
| D3 | CsWinRT/WGC publishing and runtime baseline | Clean-machine WGC, single-file/native extraction behavior, cold/warm timings, .NET version/support plan; self-contained multi-file feasibility if single-file fails |
| D4 | Windows supervision divergences from `MakaCuService` | Cancellation settlement, blocked-provider recovery, confirmed exit, generation invalidation, restart budget, graceful shutdown and host-death evidence |
| D5 | In-tree `experiments/maka-cu-windows/` vs `maka-agent/maka-cu` | Integration findings; final code home decided after the spike |
| D6 | Go compatibility boundary and any justified retention | Concrete existing CLI/MCP or distribution/integration requirements; no Go-to-.NET forwarding layer by default |
## Deliverables and completion criteria
- A draft prototype PR under the agreed code home, with fixture/reproduction scripts for the six checks and lifecycle/identity cases above.
- A measured results table with sizes, timings, memory, lifecycle evidence, and reproducible failure evidence where applicable.
- A short D1–D6 decision record: decision, evidence, recommendation, and every verified or revised packaging assumption.
- A **go/no-go recommendation**. A reproducible negative feasibility result can complete this research issue; it must not be presented as a successful preview implementation. Environmental blockers or untested assumptions are not a completed feasibility result.
- **Proceed to the production child issue only when all six checks and the stated identity/parent-death cases pass** against the selected published layout on the declared interactive environment, including clean-machine evidence. Any revised packaging choice must be explicit in the decision record and follow-up proposal.
- If single-file publishing fails, first isolate the cause and evaluate self-contained multi-file .NET distribution. Consider a long-lived Go helper only if measured language/distribution/integration constraints justify it, not automatically because one packaging mode failed.
- No macOS executor, bundled pin, public protocol, or foreground/global-input fallback changes. The helper speaks only the private protocol needed for the spike.
## Non-goals
- No keyboard, scroll, coordinates, `PostMessage`, `SendInput`, or user-facing window manipulation. Fixture-driven window replacement is test setup only.
- No new consent/approval UI: reuse the existing product approval flow.
- No locked/session-0/secure-desktop/elevation support beyond typed `capture_unavailable`/refusal.
- No multi-monitor, mixed-DPI, or broad toolkit parity guarantees.
- No CI lane or production signing/distribution integration; these remain follow-up work.
## References
- #3785 (epic), #2142 (roadmap).
- [Maintainer scope and safety clarification](https://github.com/apache/maka/issues/3785#issuecomment-5412213116); [language trade-offs](https://github.com/apache/maka/issues/3785#issuecomment-5472699000); [maintainer spike direction](https://github.com/apache/maka/issues/3785#issuecomment-5474303032).
- Host code to compare, not copy blindly: `packages/computer-use/src/maka-cu-service.ts`, `stdio-json-rpc.ts`, `maka-cu-backend.ts`, `select-backend.ts`; `apps/desktop/bundled-tools.json`, `scripts/computer-use/prepare.mjs`.
- [UIA threading](https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-threading), [WGC window interop](https://learn.microsoft.com/en-us/windows/win32/api/windows.graphics.capture.interop/nf-windows-graphics-capture-interop-igraphicscaptureiteminterop-createforwindow), [single-file deployment](https://learn.microsoft.com/en-us/dotnet/core/deploying/single-file/overview), [.NET support policy](https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core), [Node child termination](https://nodejs.org/docs/latest-v24.x/api/child_process.html#subprocesskillsignal), [Windows Job Objects](https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects).
---
中文说明
## 关系与背景
本 issue 是 #3785(Windows Computer Use epic)的子项,属于 #2142 的 Phase 5。范围以 #3785 后续维护者澄清为准,不沿用正文原有的宽泛实现清单。
维持 **C#/.NET helper 由现有 TypeScript host 直接监管**的方向,通过小型 spike 验证 Windows 打包与生命周期可行性。本项不定义最终公开协议、不交付生产语义 v1 切片、不修改 macOS `maka.cu`。
必须使用**目标应用打包布局中的发布产物**,初始尝试 self-contained 单文件 exe,不能仅运行 `dotnet run`。单文件是待验证假设,失败不自动否定 .NET helper。
## 六项检查
1. **长驻启动与握手。** stdio 上使用换行分隔 JSON-RPC 2.0,私有协议 id 为 `maka.cu.windows/0` 或约定值,声明版本、能力和限制。基准为握手 **10 秒**、请求 **20 秒**、适用时追加取消宽限 **2 秒**;偏离需说明理由。
2. **一次 MTA UIA 观察。** 专用非 UI MTA worker(`CoInitializeEx(MTA)`),仅操作显式选择且已运行的顶层 HWND。限制节点数、深度、文本/响应大小与耗时,绑定 `pid + processStartTime + hwnd + windowGeneration` 及 retained UIA root。遍历计时不能证明阻塞中的 provider 调用可中断,第 6 项负责恢复边界。
3. **一个受支持的语义动作。** 必做 `ValuePattern` 的 `set_value`;仅在简单可用时选做 `Invoke`/`Toggle`/`SelectionItem` 点击。opaque token 只能在原快照内解析;变更派发前原子消费快照,动作前后校验目标,通过 readback 返回 `verified`/`refused`/`unknown`。结果未知时禁止自动重放变更。
4. **目标窗口 WGC 截图。** 使用 `CreateForWindow(HWND)`,与观察绑定同一目标代次;capture/action 路径独立上报。验证窗口被遮挡时不会返回遮挡窗口的像素;失败返回 `capture_unavailable`,不静默回退屏幕矩形。树与帧必须属于同一目标,不承诺动态内容的时间原子性。
5. **取消与执行结束。** 覆盖派发前、派发后和 provider 阻塞期间的取消。stdio/控制处理必须独立于 UIA worker 保持响应。取消 ACK 不等于完成:原请求必须以已知真实结果结束,可能已派发但无法确认时返回 `unknown`;派发前取消须证明没有变更。只有旧操作结束或旧 helper 确认退出后才能释放执行 lane,不能后台工作仍在运行就宣布取消完成。无法在 **2 秒**宽限内结束则进入第 6 项。终止 helper 不会撤销已经交给目标应用的动作。
6. **provider 卡死后的恢复。** 用确定性 fixture 阻塞真实 UIA provider 调用,仅在 helper 中 sleep 不算证据。supervisor 终止并重建的是 **helper**,不是用户目标应用/provider 进程。确认旧 helper 退出后才接收新工作,增加 helper 代次,使旧 snapshot/token 全部失效;重启后在可响应 fixture 目标上完成新的 observe/action/readback。目标进程必须存活;必要时通过 fixture 解除注入的阻塞,并记录此步骤。明确重启次数、退避、恢复时限以及立即重启还是下次请求触发;仅握手成功不算恢复。
## 目标身份与失效
定义 `windowGeneration` 如何建立/失效、与 retained UIA root 的关系,以及 helper 代次如何约束快照。增加同名字段不能证明识别了 HWND 复用;PID/创建时间也不能区分同一进程内的替换窗口。
fixture 应验证窗口关闭重建、控件替换、重复使用已消费 token。身份变化或无法确认时必须拒绝,不按名称/索引重新匹配。替换窗口或 modal HWND 必须重新显式选择;helper 重启后旧 token 不得重新有效。
## 父进程死亡与 Windows 关闭
stdin EOF 是关闭触发条件,**不能单独证明没有孤儿进程**。测试正常协议 shutdown/EOF,以及强制终止 host,包含 UIA 阻塞期间。helper 必须在声明时限内退出,不能无限等待 MTA worker,并提供残留进程检查证据。
若仅靠 EOF 无法保证,评估独立父进程存活监测或正确归属的 Windows kill-on-close Job Object,记录所有权与句柄生命周期。不要照搬 macOS 的 `SIGTERM` 优雅退出假设:Node 在 Windows 下通过这些信号强制终止子进程。正常路径使用协议 shutdown/EOF,异常路径有界强制终止。
## 证据环境
六项检查必须针对发布产物在**交互式 Windows 11 x64 桌面**运行,并提交确定性对抗 fixture 和复现脚本。不要假定普通 GitHub 托管 runner 提供所需交互桌面;本地或 self-hosted 证据足够。
补充一次**未安装 .NET 运行时或开发 SDK**的干净机器运行,只复制声明的交付物。记录 Windows build、Node/.NET SDK/CsWinRT 版本、发布参数、产物哈希、完整命令及每项 pass/fail/blocked。环境未就绪不能算技术不可行,也不能算通过。
## 打包假设——验证或明确修订
- 初始为 self-contained 单文件 win-x64,禁用 trimming;固定真实 Windows TFM/SDK 版本及所需原生依赖。
- 评估 **.NET 10 LTS** 作为后续基线。.NET 8 可作为有理由、有升级计划的短期 spike 选择,但其支持于 **2026-11-10**结束。self-contained 运行时补丁需重新构建并分发 helper。
- 测量 exe 与整体分发体积、原生库解压位置/权限/磁盘占用(如适用)、冷/热启动、首帧延迟及内存。单 exe 不代表无需解压或外部前置依赖。
- TypeScript host 通过 stdio 直接监管 helper,必须满足父进程死亡测试,不能仅凭正常退出宣称无孤儿。
- Spike 不签名,元数据使用 `signature: none` / `unsigned`,`distributionReady: false`;Authenticode 与生产分发留给后续。不使用 macOS `adhoc` 签名术语。
- 不修改 `apps/desktop/bundled-tools.json` pin,不引入公开协议版本。
## 决策记录 D1–D6
| # | 决策 | 证据 |
|---|---|---|
| D1 | 托管 UIAutomationClient 或直接 COM | Cache request、UIA3、线程/对象所有权、provider 超时行为 |
| D2 | stdout 有界 base64 或协商临时文件 | 帧延迟/大小、base64 与信封开销、响应上限、stdout 背压及控制响应;文件方案需记录所有权与清理 |
| D3 | CsWinRT/WGC 发布方式和运行时基线 | 干净机器 WGC、单文件/原生库解压、冷/热启动、.NET 支持计划;单文件失败时评估 self-contained 多文件 |
| D4 | 与 MakaCuService 的 Windows 监管差异 | 取消结束、provider 卡死恢复、确认退出、代次失效、重启预算、正常关闭及 host 死亡 |
| D5 | experiments/maka-cu-windows/ 或 maka-agent/maka-cu | 集成发现;最终位置在 spike 后决定 |
| D6 | Go 兼容边界及保留理由 | 真实 CLI/MCP 或分发/集成需求,默认不增加 Go → .NET 转发层 |
## 交付与完成条件
- 在约定代码位置提交原型 draft PR,以及六项检查和上述生命周期/身份测试的 fixture、复现脚本。
- 实测结果表包含大小、耗时、内存、生命周期证据;失败时提供可复现证据。
- D1–D6 简短决策记录,列明决策、证据、建议及每项打包假设的验证/修订。
- 给出 go/no-go 建议。**可复现的不可行结论也可完成研究 issue**,但不能被描述为预览功能已实现。环境阻塞或未验证假设不算完成研究。
- **进入生产子 issue 的前提**是:六项检查及身份/父进程死亡场景,在声明的交互环境中对选定发布布局全部通过,包括干净机器证据。打包选择修订须写入决策记录及后续提案。
- 单文件失败先定位原因,再评估 .NET self-contained 多文件分发;只有实测语言/分发/集成约束成立时才考虑长驻 Go helper,不因某种打包模式失败自动换语言。
- 不修改 macOS executor、bundled pin 或公开协议,不加入前台/全局输入 fallback;只实现 spike 所需私有协议。
## 非目标
- 不做键盘、滚动、坐标、PostMessage、SendInput 或产品窗口操作;fixture 窗口替换仅为测试设置。
- 不新增审批 UI,复用现有产品审批流程。
- 锁屏/session-0/安全桌面/提权仅返回 typed capture_unavailable/拒绝,不提供支持。
- 不承诺多屏、混合 DPI 或广泛 toolkit 对等性。
- CI、生产签名与分发集成留给后续。
参考链接与代码位置见上方英文 References;范围和选型分别对应 #3785 中 M4n5ter、sunheyi6、liugddx 的讨论。
Contributor guide
Research direction
Read packages/computer-use/src/maka-cu-service.ts, stdio-json-rpc.ts, maka-cu-backend.ts, and select-backend.ts first, then run the published helper on an interactive Windows 11 x64 desktop. Use the declared fixture and reproduction scripts to exercise checks 1–6, identity, packaging, and parent-death behavior. Done means a prototype PR, measured results, D1–D6 decision record, and a reproducible go/no-go recommendation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, typescript
- Domain
- desktop, testing
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 25/100