Maka 逆向工程实录:从 Codex 二进制反汇编到 Electron 画中画镜像的实现移植
2026/9/18 5:43:19 网站建设 项目流程

Maka 逆向工程实录:从 Codex 二进制反汇编到 Electron 画中画镜像的实现移植

【免费下载链接】makaApache Maka (Incubating) is a high-performance agent workspace that keeps a complete record of everything it did.项目地址: https://gitcode.com/GitHub_Trending/mak/maka

Maka(Apache Maka, Incubating)的 Computer Use 功能在驱动后台窗口时,需要一个画中画(Picture-in-Picture,PiP)镜像窗口来实时展示 Agent 正在操作的目标。本文完整记录了 Maka 团队如何对 Codex 的 PiP 窗口实现进行二进制级逆向工程——从sky.node原生插件和 XPC 服务反汇编中恢复窗口结构、弹簧运动常量与交互算法,再逐条移植到 Electron 主进程,并刻意在四个设计点上做出差异化选择。读完本文,你将掌握一套"从反汇编取证 → 行为实验验证 → 源码级移植"的完整方法论,以及 Maka PiP 镜像(pip-window.ts + pip-motion.ts)从窗口语义到运动物理的全部实现细节。

逆向工程概述:从两份二进制工件到完整行为模型

本文档(codex-pip-reverse-engineering.md)的核心立场是:确认的原生事实(confirmed native facts)与实现推断(implementation inference)必须分开记录。这一点与仓库中另一份取证文档 computer-use-cursor-provenance.md(Agent 光标溯源)采用同样的方法论——每个移植的常量都附上来源与反汇编出处,未经验证的猜测绝不混入事实层。

逆向工程的产出物分三类:

类别内容证据来源
Confirmed(确认事实)面板属性、类结构、弹簧常量、抛出算法、尺寸/缩放/控件反汇编逐条读出
Measured(实测事实)Electron 43 上的基线行为真机行为实验
Inference(实现推断)阻尼比 ζ 的物理含义、设计取舍动机由常量推导

这种分层保证了移植代码可以被审计:任何后续维护者都能从 pip-motion.ts 的常量注释一路追溯到二进制里的某条指令。

检查的工件:为什么"只查一半"是致命的

逆向对象是两个进程,而两者之间的分工比任何单一个体都重要:

路径职责
service~/.codex/computer-use/Codex Computer Use.app/Contents/MacOS/SkyComputerUseService采集与发布(capture and publish)半边
host/Applications/ChatGPT.app/Contents/Resources/native/sky.node窗口(window)半边
main JS/Applications/ChatGPT.app/Contents/Resources/app.asar.vite/build/main-Be_0DBuv.js宿主注册

service 半边包含RemoteHostedPIPContentPublisherRemoteHostedPIPCaptureStreamCUAServiceRemoteHostedPIPController等类,链接了 AVFoundation 与 ScreenCaptureKit,并使用AVSampleBufferDisplayLayer——但没有链接 AVKit。它只负责通过 XPC 捕获与发布帧,并不承载窗口。

真正的窗口生活在 ChatGPT 自己的原生插件sky.node中,属于PIPStack*类族。文档特别警告了一个认知陷阱:如果在 service 里搜不到窗口符号就断定"窗口不存在",是错误的。类名中的RemoteHosted前缀本身就说明另一半在另一个进程里——必须在得出结论前先找到那个进程。这个原则(先确认"另一半"存在,再下"缺失"结论)是本文档存在的部分原因。

Confirmed:面板(Panel)——setLevel: 0addChildWindow:是设计支点

RemoteHostedPIPContentCreateStackPanel的反汇编中逐条读出面板创建序列:

NSPanel initWithContentRect:… styleMask:0x80 // NSWindowStyleMaskNonactivatingPanel backing:2 defer:NO setTitle: setBackgroundColor: [NSColor clearColor] setOpaque: NO setHasShadow: NO setLevel: 0 // NSNormalWindowLevel — 刻意不置顶 setAcceptsMouseMovedEvents: YES setCollectionBehavior: 0x108 // FullScreenAuxiliary | Transient setHidesOnDeactivate: NO setMovableByWindowBackground: NO // 拖拽是手写的,见下文

随后从-[PIPStackWindow attachToOwnerWindowForPositioning]调用addChildWindow:ordered:

支撑整个设计的是两个事实:

  1. setLevel: 0NSNormalWindowLevel)——刻意不浮动(floating)。镜像窗口不应悬浮在所有无关应用之上。
  2. addChildWindow:——子窗口相对于父窗口排序,而非相对于桌面排序,并且随父窗口在同一次 window-server 事务中一起移动。

这两个选择在 Electron 端的对应实现见 pip-electron.ts 的pipWindowOptionsfocusable: false(对应 nonactivating panel 的焦点契约)、hasShadow: false(原生阴影由pip.html自绘)、以及parent: parent as BrowserWindow——只有在找不到应用窗口时才退化为alwaysOnTop: true

Confirmed:类结构(Structures)——PIPStack 家族与窗口服务器的分工

PIPStackHost { hostID, ownerWindow, anchorContentRect, anchors[], presentationScope } PIPStackHostAnchor{ contentPoint, alignment } PIPStackWindow attachToHost: / startFollowingOwnerWindow / ownerWindowFrameMayHaveChanged: PIPStackController drag beginDragAtContentPoint: → dragToContentPoint: → endDrag snap nearestTargetAnchorForCurrentAnchor:draggingVelocity: → moveStackToAnchorAlongCurve: motion configureMotionSpringsWithLeadItem:dragging:programmaticMove: hosts moveStackToHostID: / compatibleAnchorsIncludingDragHosts: clamp clampEnvelopeToVisibleScreen: PIPStackItemMotion{ springStiffness, springDamping, velocity, target, origin, restOffset } PIPStackContentView reinstallMouseEventMonitors → addGlobalMonitorForEventsMatchingMask:handler: addLocalMonitorForEventsMatchingMask:handler: updateHoverFromCurrentMouseLocation / contentPointForCurrentMouseLocation

关键洞察来自ownerWindowFrameMayHaveChanged::它读取notification.object并比较notification.name,说明它是一个针对宿主窗口移动/缩放的通知处理器。它只负责重算锚点(recompute the anchor),不移动窗口——移动由 window-server 完成,因为面板是子窗口。

Maka 的移植对应物是 pip-window.ts 中subscribeAnchorChanges订阅回调的注释逻辑:只有 resize 被响应,move 被刻意忽略。因为子窗口随父窗口在同一次事务中移动,若再在此处重定位,既冗余又落后一帧——那一帧的滞后正是此前拖动时镜像"拖尾"(trailing)的根源。

Confirmed:常量(Constants)——弹簧、抛出、尺寸与缩放的逐条复原

弹簧:两组四列的fcsel

configureMotionSpringsWithLeadItem:dragging:programmaticMove:反汇编出一个cmp w26, #0后跟四个fcsel,即两列四组常量:

非拖动(settling)拖动(dragging)
lead stiffness(主项刚度)320900
lead damping(主项阻尼)4255
follower stiffness base(跟随项刚度基准)150260
follower damping base(跟随项阻尼基准)3032

跟随项的值分别除以1 + 0.18·s1 + 0.08·s,其中s = |index| · (1 + 0.45·|index|)是索引衰减因子。

阻尼比是这些数字而非其他数字的原因:非拖动时 ζ ≈ 1.17(略过阻尼),窗口永远不会越过目标角回弹;拖动时 ζ ≈ 0.92(略欠临界),指针拉动时窗口保留一丝"韧性"。Maka 只有一个镜像(没有堆叠),所以只使用 lead 列,follower 衰减没有适用对象——这一判断直接写在 pip-motion.ts 的注释里。

在 Maka 中,这两组常量以PIP_SPRING形式定义,并由stepSpring(半隐式 Euler 积分,每轴独立)驱动。选择半隐式而非显式 Euler 的原因也在注释中交代:显式形式在大dt下会增益能量——掉一帧就会让镜像飞走而不是迟到,这是动画绝不能出现的失败模式。同时dt被钳制在1/30秒以内,因为后台窗口可能给你数秒的间隙,没有任何弹簧能在那种步长下保持稳定。springAtRest用"半像素内且速度低于每秒半像素"判定到达,见 pip-motion.ts。

抛出(Throw):0.55 的比例与点积评分

nearestTargetAnchorForCurrentAnchor:draggingVelocity:恢复的算法:

throw = velocity * 0.55 t = min(|throw| / 5000, 0.45) target = currentAnchor + unit(throw) * t score(a)= |a.point − target| − |throw| · max(0, dot(unit(throw), unit(a.point − current))) pick min score

另有独立阈值hypot(velocity) >= 120决定其他宿主上的锚点是否进入候选集。

0.55 是"窗口飞向你扔的方向"与"窗口飞向你指的方向"之间的差别——只有前者有"重量感"。点积项则让一次有意的跨窗抛出落在瞄准的位置;单靠距离只会选到你正在离开的那个角。

Maka 移植在 pip-motion.ts 的pickPipAnchorPIP_THROW_SCALE = 0.55PIP_THROW_REFERENCE = 5000PIP_THROW_MAX = 0.45。值得注意的差异化处理:Codex 的hypot(velocity) >= 120阈值被刻意不移植——它用于门控"其他显示器上的锚点"是否候选,而 Maka 的四个锚点是同一宿主窗口的四角,没有跨屏候选可门控;慢速释放本身已读作"放置"而非"抛出",其覆盖距离趋近于零,最近角凭距离即可胜出(源码注释还记录了一个讽刺的细节:该阈值曾移植过一次,没有任何读者,且断言把字面量和自己比较)。

尺寸:默认 200pt,钳制 [100, 400],内边距 24pt

默认最长边 200pt,钳制到 [100, 400],通过缩放到较短边保持宽高比;锚点内边距 24pt。三个边界就是二进制中的字面量双精度浮点0x4069…0x4059…0x4079…。Maka 以PIP_MIN_EDGE/PIP_DEFAULT_EDGE/PIP_MAX_EDGE/PIP_MARGIN定义在 pip-motion.ts,尺寸计算pipDisplaySize在 pip-electron.ts(按长边等比缩放,非有限值回退默认)。

缩放(Resize):四条指令的垂直手势

-[PIPStackResizeInteraction maxDisplaySizeForPointerScreenPoint:]只有四条指令:

sign = (alignment & ~1) == 2 ? +1 : -1 size = initialMaxDisplaySize + (pointer.y - initialPointer.y) * sign

只有垂直分量驱动缩放,符号由镜像所靠的角决定,因此手势在任何角落读起来都一样:远离锚点即放大。交互保持开始时的边与指针高度,不做任何增量累加——抖动指针无法累积漂移。Maka 的pipResizeEdge(pip-motion.ts)等价实现,并补上了 CodexsnapPointToBackingScale:同样的整数化理由:透明窗口上的分数像素边缘会闪烁。

控件:三个标识符减为一个

performControlWithIdentifier:接受stophideclosesetHoveredControlIdentifier:_pressedControlIdentifier驱动外观。

实测基线:写任何代码之前在 Electron 43 上测量

结果
documentPictureInPictureundefined,加不加--enable-features=DocumentPictureInPictureAPI都是
子窗口 + 父窗口移动子窗口自动跟随,零代码,同一事务
子窗口 + 父窗口缩放子窗口不动——锚点需要重算
子窗口isAlwaysOnTop()false
子窗口焦点从不抢焦点
向点击穿透窗口注入鼠标事件可送达但被合并且丢包——5 个中 2 个,随后 5 个中 1 个

这些测量直接定义了 pip-window.ts 的设计注释:alwaysOnTop默认floating级别会把镜像放到所有应用之上;在每次move时重定位则让第二个事务比拖动落后一帧,产生可见拖尾——子窗口排序解决前者,子窗口定位解决后者

关于 Document Picture-in-Picture:它不仅是在 Electron 中被禁用,其实现位于 Chromium 的//chrome浏览器层(PictureInPictureWindowManager),而 Electron 根本没有这一层。这条路是关闭的,而不是变窄的

Maka 复制了什么

  • 应用窗口的子窗口,普通层级。镜像最初的两个缺陷——浮在无关应用之上、拖动时落后一帧——同源于一个根因:镜像自己定位而不是被父窗口携带。
  • hasShadow: falsepip.html自绘阴影;原生阴影会由 window-server 根据内容 alpha 在每一帧落地时重新计算——而这正是那个每帧都收到新帧的窗口。
  • 200pt 默认边、[100, 400] 钳制、24pt 内边距。
  • 弹簧常量、抛出投影与锚点评分,全部实现在 pip-motion.ts,每个常量旁边都引用了反汇编。
  • hover 由主进程侧指针决定,因为这正是 Codex 的 global/localNSEventmonitor 所做之事——它从不询问窗口"指针是否在内部"。Electron 没有全局鼠标监视器,Maka 用 20Hz 轮询(scheduleHoverCheck)替代,见 pip-window.ts 与watchHover
  • 只对 resize 做出反应。move 是 window-server 的职责。

五处"Maka 刻意不同"及其理由

两个控件而非三个

Codex 的close关掉一块 tile、hide关掉整个栈。Maka 一次只镜像一个窗口,两者是同一手势,只有一个配得上按钮。PipControlId = 'stop' | 'hide'见 pip-window.ts。

指到才响应点击(click-through until pointed at)

Codex 的 tile 总是吃掉点击——acceptsFirstMouse:返回 YES,hitTest:声明整个视图。但 Codex 的 tile 是可选功能(设置文案为 "Show backgrounded apps that Computer Use is working on in Picture-in-Picture mode");Maka 的镜像在每次 run 开始时就会出现,必须做到不打扰才值得存在。因此用setIgnoreMouseEvents(true, { forward: true })——鼠标移动仍然送达,主进程可以判断何时收回点击权。setPointerInside逻辑见 pip-window.ts。

没有堆叠

Codex 用 lead item + followers 镜像多块 tile;follower 弹簧列及其索引衰减在单镜像场景无的放矢。

抓手(grip)随控件一起出现

Codex 的缩放把手属于同一套 hover 镀铬;静止时 tile 不携带任何常驻把手。Maka 同理——200pt 的小窗口承担不起常驻交互元素。

帧序列(flipbook)而非视频流

Codex 通过 ScreenCaptureKit 跨 XPC 流式传输。Maka 完全不需要:每个改变状态的 Computer Use 动作已经返回目标窗口的截图(在 settle 等待之后捕获,因此展示的是稳定后的结果),动作完成时帧已在手,镜像只是 post-action 帧的翻页簿,零额外采集成本。这条通路在 pip-feed.ts 中实现——withComputerUsePip包装 cursor 的onActionEnd钩子,消费每次动作返回的截图(含语义动作,光标自身的onActionEnd反而因无结束坐标而跳过它们),并把光标坐标从屏幕坐标经窗口缩放换算进截图像素。代价是真实且刻意的:动作之间镜像不更新——足够看到 Agent 在做什么,而这正是它的目的。

没有跨宿主移动

moveStackToHostID:宿主窗口之间移动栈——Codex 注册了多个宿主,包括avatar-overlay。Maka 只有一个窗口,无可移动之物。跨显示器天然成立:因为镜像是应用窗口的子窗口,父窗口去哪它去哪。

验证:真机 smoke 与可精确测试的物理层

真窗口集成测试

scripts/pip-interaction-smoke.mjs 运行真实链路:真实父窗口、真实子面板、真实 preload 与 renderer、真实输入事件。它断言:

  • 子窗口属性(不置顶isAlwaysOnTop() === false、不抢焦点、getParentWindow() === parent);
  • 座席位置(应用窗口右下角、内缩 24pt);
  • 随父窗口移动被携带(移动 160px 后断言跟随);
  • hover 出控件(主进程侧指针移动触发controls可见);
  • 一次抛出落在瞄准的锚点上(左上角),且控制器currentAlignment()一致;
  • 应用窗口 resize 后回到用户选中的角而非默认角;
  • grip 拖拽放大(实测 200x160 → 290x232,宽高比保持、仍在角上、在钳制范围内),且 grip 位于锚点对角——这个断言修复了一个真实 bug:grip 曾钉死在 CSS 的左上角,只有默认的右下锚点对齐,把镜像扔到左上角后指针在拖拽第一个像素就脱离抓手;
  • hide 控件收起镜像(pip.isVisible() === false)。

它不需要辅助功能权限、不需要解锁屏幕——只驱动 Electron 窗口——因此在其他真机检查无法运行时,它是唯一可持续的真机检查。文件头还记录了运行前置:npm --workspace @maka/desktop run build:main && npm --workspace @maka/desktop run build:overlay && npx electron scripts/pip-interaction-smoke.mjs

脚本中值得学习的两处工程细节:settled()轮询要求连续 6 个相同采样才算静止(过阻尼弹簧的收尾段每帧移动不足 1 点,两个连续读取可能因取整相同而误判到达);拖拽夹具用三个递进采样点保证释放时被读作"抛出"而非"放置"。

物理层单元测试的现状

物理与锚点评分原本由apps/desktop/src/main/__tests__/computer-use-pip-motion.test.ts覆盖(无需桌面环境);PR #2478 删除了该测试文件,只结束了它提供的覆盖——没有替代品。但被测模块本身未受影响:pip-motion.ts在 #3293 光标替换(#3456 实现,重建了 Agent 光标引擎与字形——包括 PiP 字形)前后字节级一致,上文转录的拖动/落位弹簧与抛出系数仍在那里定义,仍驱动着pip-window.ts。文档同时划清了边界:光标溯源记录在 computer-use-cursor-provenance.md,只涉及 Agent 光标;PiP 窗口的运动模型是独立表面,没有需要记录溯源变更。

方法论总结:可复用的逆向-移植流程

  1. 先分进程,再下结论RemoteHosted前缀 = 另一半在另一个进程;找不到符号 ≠ 功能不存在。
  2. 事实分层:confirmed(反汇编逐条读出)/ measured(真机行为实验)/ inference(由常量推导的动机),三层永不混淆。
  3. 常量带出处移植:每个数字在源码注释中附上反汇编来源,后续维护者可审计。
  4. 用真实窗口冒烟测试锁定行为:单元测试证明物理与状态机,smoke 测试证明 Electron 集成,二者互补。
  5. 差异化而非照搬:Codex 的多 tile 栈、无条件点击、视频流等设计在 Maka 单镜像场景下各有明确的不适用理由——每处差异都写明了"为什么"。

这套流程沉淀在 pip-motion.ts(纯逻辑、可精确测试、零 Electron 依赖)、pip-window.ts(控制器与状态机)、pip-electron.ts(Electron 边界)、pip-feed.ts(帧来源)四个文件与 pip-interaction-smoke.mjs 一个真机验证脚本中,构成 Maka Computer Use 镜像的完整证据链。

【免费下载链接】makaApache Maka (Incubating) is a high-performance agent workspace that keeps a complete record of everything it did.项目地址: https://gitcode.com/GitHub_Trending/mak/maka

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询