Skip to content

feat(windows): feasibility spike for a supervised C#/.NET maka-cu Windows helper #4318

Description

@sunheyi6

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


中文说明

关系与背景

本 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. 一个受支持的语义动作。 必做 ValuePatternset_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 / unsigneddistributionReady: 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 的讨论。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions