☰
为Codex打造一个实时跟踪任务状态的悬浮窗
2026/10/27 0:29:17 网站建设 项目流程

我做了个会跟着 Codex 走的窗口。用大白话说,就是把 Codex 跑任务时的状态、输出、队列和当前错误单独挖出来,放在一个可以置顶、可以半透明、可以随时唤出的小窗口里。只要 Codex 还在跑,这个小窗口就一直显示实时进度,我在编辑器、浏览器、终端之间来回切换也不会漏掉任务状态。

很多用 Codex 的人都有同一个问题:任务一长,人就容易切到别的窗口。切过去之后,Codex 到底跑完没有、中间是不是报错了、当前在哪一个文件上操作,完全看不到。再切回来的时候,可能已经卡了很久,或者已经生成了一堆没注意到的改动。这个跟随窗口解决的就是这类问题,不是去改 Codex 内部的实现,也不是做一个功能更复杂的 AI 客户端,而是用一个独立的伴生窗口,把最关键的运行信息放到你眼睛最容易看到的位置。

如果你正在用 Codex CLI 或桌面版本跑自动编码任务,又经常需要同时处理其他工作,这篇文章值得看完。下面会从方案选型、环境准备、核心代码、多任务处理、错误排查和边界限制几个角度完整拆开讲。

1. 先明确:这个窗口盯的不是聊天记录,而是任务运行状态

1.1 跟随窗口到底跟的是什么

很多人第一反应是,做一个小窗口来显示 Codex 的聊天记录。但实际用过之后会发现,聊天记录信息密度太低,而且重新解析文本很麻烦。真正值得盯住的其实是四类信息:

  1. 当前任务状态:是初始化、处理中、等待模型返回,还是已经结束。
  2. 当前操作对象:正在改哪个文件、执行哪条命令。
  3. 输出关键信息:最近几行日志,尤其是报错信息。
  4. 队列情况:还有多少任务排队,当前任务是第几个。

这四类信息有一个共同来源,就是 Codex 运行时的日志输出。不管你是用命令行启动,还是用桌面版启动,只要把运行过程写到日志文件里,伴生窗口就可以通过读取日志文件来同步状态。

我做的这个窗口,本质上是一个“日志状态观察器”。它把 Codex 的日志文件当作数据源,定时读取增量内容,按照关键词匹配当前状态,然后显示在一个可以置顶的小窗口里。这样做的好处很明显:不侵入 Codex 本身,不修改配置,也不影响正常使用流程。

1.2 什么场景下最值得用

我自己的实践场景是批量修改多个项目文件。Codex 在后台跑,我得同时打开浏览器查文档、打开另一个项目写代码。如果没有跟随窗口,每隔几分钟就要切回 Codex 所在的终端看一眼,效率很低。有了小窗口之后,任务状态一直挂在屏幕角落里,跑完会自动弹提示,报错会变色,不需要频繁切换窗口。

如果你的使用习惯是让 Codex 一口气跑完一个任务,然后全程盯着终端等结果,那这个方案对你帮助不大。它更适合长时间任务、多任务排队、以及需要在 Codex 工作同时做其他事情的场景。

这类工具最值得先看的不是功能列表,而是能不能在常见环境里稳定跑起来。下面先讲方案选型,再讲具体实现。

2. 为什么我没去改 Codex 客户端,而是选择了日志文件监听

2.1 三种可选的实现路线

做跟随窗口,技术上有三条路可以走:

实现路线工作方式优点是缺点是
改造客户端代码直接修改或扩展 Codex 的前端界面能拿到完整内部状态,显示最精确要维护内部接口,升级容易失效
读日志文件监听 Codex 写出的日志文件,提取状态侵入最小,逻辑简单,跨版本稳定日志格式变化时要调整解析逻辑
监听终端输出在终端或进程层捕获标准输出能拿到实时原始文本进程管理复杂,后台运行场景不好处理

我最终选择的是日志文件监听。原因很直接:Codex 本身就在持续写日志,这些日志包含时间、任务编号、命令、错误信息等关键数据。读取文件不需要依赖内部接口,也不需要常驻挂载终端,即使 Codex 在桌面应用中运行,只要日志文件存在就能拿到数据。

这里要提醒一句:日志文件的具体路径、格式、字段分隔方式,不同版本之间可能有差异。如果你第一次跑,不要直接照搬我后文的路径,而是先去日志目录里看清楚实际输出的是什么格式。这个工作只有提前做了,后面的解析代码才靠谱。

2.2 数据源稳定,比界面好看优先级更高

一开始我也想过直接用 Electron 做一个漂亮的悬浮面板,效果确实比系统自带控件好很多。但后来发现,真正影响体验的不是界面好不好看,而是数据能不能及时刷新、窗口会不会抢占焦点、以及长时间运行会不会内存暴涨。

所以我把实现优先级重新排了一下:

  1. 获取日志内容不遗漏、不重复读取。
  2. 窗口置顶、不抢焦点、可半透明。
  3. 状态变化时及时刷新。
  4. 最后才是颜色、字体、动画这些视觉细节。

这篇文章的实现代码也是按这个顺序组织的。先保证数据链路通,再去调界面。

3. 环境准备:本方案的最小运行条件

3.1 前置条件不需要太高

整个方案只是读取日志文件和显示窗口,不需要 GPU,不需要高内存。普通办公电脑就可以跑。我在 Windows 11 和 Ubuntu 22.04 上分别试过,都正常。macOS 因为窗口置顶权限处理方式不同,需要额外做一步授权,后面单独说。

你需要准备的工具只有一个:Python 3.9 以上版本,支持 tkinter 即可。如果只是在终端里跑,不需要额外安装依赖。如果要用到快捷键全局监听,则根据系统不同装一个键盘监听库。

这里有一个重点:你本机必须先能正常使用 Codex,并且确认 Codex 的日志目录是存在的。如果 Codex 还没有完整跑通过,日志文件都生成不出来,做窗口也就没有意义。我一般会先在终端手动运行一条 Codex 任务,确认日志文件成功写入,再开始写伴生窗口。

3.2 Windows 下的注意事项

在 Windows 上,tkinter 的topmost属性可以直接让窗口置顶,不需要额外装库。如果你的窗口管理器或者修改过的系统主题有特殊效果,可能会挡住置顶,这时候需要确认一下系统是否有第三方桌面增强软件在管理窗口层级。

全局快捷键建议依赖keyboard或pynput,但这两个库在部分杀毒软件下会触发提示。如果只想自己学习使用,也可以直接用 tkinter 自身的bind绑定焦点内快捷键,前提是窗口已经处于激活状态。

我在 Windows 上第一次遇到的问题是:窗口能置顶,但总是抢占输入焦点。每刷新一次,焦点就会跳到小窗口上,导致正在写的代码突然失去输入框焦点。解决方法是把窗口设置为overrideredirect风格,或者定期调用focus_force时注意控制,不能每次刷新都抢焦点。

3.3 macOS 和 Linux 的差异

macOS 上,普通 tkinter 窗口设置topmost在部分场景下失效,因为系统对窗口层级有更严格的约束。常见处理方式是使用辅助功能权限,让应用能够控制窗口层级,或者用原生控件重新实现。如果你只是自己用,建议先用最简单的方式跑通日志读取,窗口层级问题再慢慢调。

Linux 和桌面环境关系很大。GNOME 下快捷键和置顶的表现与 KDE 不一致,部分轻量级窗口管理器甚至没有严格的置顶协议。我测试时用了 GNOME,开启窗口置顶后表现正常,但如果你的桌面环境是 Wayland,可能还需要额外处理窗口管理权限。

注意:这套方案的核心是日志转发和窗口刷新,目标是在本地开发机上运行。不要把它当作远程服务器上的守护进程来用,也不要用它去采集不是你自己的日志。

4. 第一版实现:置顶窗口、半透明、自动读取日志

4.1 为什么我用了 tkinter 而不是 Electron

对一个小型伴生窗口来说,tkinter 足够用了。它不需要打包 node_modules,不需要处理浏览器内核的内存占用,启动速度快,跨平台也是现成的。缺点是界面比较朴素,按钮、下拉框、阴影效果都不如 Web 界面好看。

如果你想把界面做得更精致,也可以换成 Electron 方案。但需要注意,Electron 的每个窗口实例都会占用额外内存,长时间挂着再加上日志内容越来越长,内存占用会明显上涨。我更建议先用 tkinter 把核心逻辑验证完,确定日志解析和状态判断没有问题,再考虑要不要换界面框架。

4.2 核心代码示例

下面是一个很基础的 tkinter 窗口逻辑,只做了三件事:读取日志文件、按行解析状态、显示最近状态。这只是示例,具体日志格式要按你本地的 Codex 日志结构调整。

import tkinter as tk import os import time import threading class CodexWindow: def __init__(self, log_path): self.log_path = log_path self.last_position = 0 self.root = tk.Tk() self.root.title("Codex 跟随窗口") self.root.geometry("360x160+100+100") # 置顶 self.root.attributes("-topmost", True) # 半透明 self.root.attributes("-alpha", 0.92) self.status_label = tk.Label(self.root, text="等待任务", font=("Microsoft YaHei", 12)) self.status_label.pack(pady=8) self.detail_text = tk.Text(self.root, height=5, font=("Consolas", 9)) self.detail_text.pack(fill="both", expand=True) self.root.after(500, self.refresh_poll) self.root.mainloop() def read_new_log(self): """读取日志文件中的新增内容""" if not os.path.exists(self.log_path): return "" with open(self.log_path, "r", encoding="utf-8", errors="ignore") as f: f.seek(self.last_position) new_data = f.read() self.last_position = f.tell() return new_data def refresh_poll(self): """定时刷新窗口内容""" new_log = self.read_new_log() if new_log: self.detail_text.insert("end", new_log) self.detail_text.see("end") self.parse_status(new_log) self.root.after(500, self.refresh_poll) def parse_status(self, log_content): """按关键词简单判断状态""" if "error" in log_content.lower(): self.status_label.config(text="任务报错", fg="red") elif "complete" in log_content.lower() or "done" in log_content.lower(): self.status_label.config(text="任务完成", fg="green") else: self.status_label.config(text="任务处理中", fg="blue") if __name__ == "__main__": window = CodexWindow("/path/to/codex.log")

这个版本能用,但还比较粗糙。它把整个日志内容都塞进了文本框,如果日志很大,界面会越卡越慢。真实使用时要加两个限制:只保留最近一段日志,以及解析状态时不要反复读全量内容。

4.3 位置跟随:让窗口贴着 Codex 旁边走

标题里说的“跟着 Codex 走”,除了逻辑上跟任务状态,还指物理位置上的跟随。最常见的情况是,Codex 跑在终端里,终端可能放在屏幕左侧,也可能放在右侧。如果伴生窗口固定在屏幕中心,会挡住其他内容。

位置跟随可以这样做:当你把 Codex 所在窗口移动到某个位置时,伴生窗口也跟着移动到它旁边。实现方式是,先获取 Codex 窗口的位置和尺寸,然后算出伴生窗口的新位置。

# 伪代码,具体方法因系统而异 def move_near_codex(self, codex_x, codex_y, codex_width, codex_height): new_x = codex_x + codex_width + 10 new_y = codex_y self.root.geometry(f"360x160+{new_x}+{new_y}")

在 Windows 上,获取其他窗口位置需要用 Win32 API 或者pygetwindow库。在 Linux 上可以用wmctrl工具查询窗口位置。macOS 上限制较多,取得其他应用窗口位置需要辅助功能权限。如果你只需要最简单的方式,也可以不自动跟,单纯让窗口置顶,然后手动拖到屏幕右下角。

实测时我建议先手动拖位置,不需要一开始就做自动跟随。自动跟随的逻辑看起来简单,实际运行时会遇到多显示器、窗口最小化、终端标签切换等边界情况。先把状态刷新跑稳,再优化位置。

4.4 刷新频率和内存控制

刷新频率不要设置得太高。我一般设置在 500 到 1000 毫秒一次,也就是半秒到一秒。太快的轮询会让磁盘占用和 CPU 使用率上升,尤其是在日志文件被频繁写入的时候。太慢则会让状态更新延迟明显。

关于日志文件过大的处理,可以加一个偏移量记录,每次只读新增部分。就是上面代码里的last_position思路。不要每次重新打开文件读全量内容,否则时间一长,整个窗口会越来越卡。

注意:要区分读取新增内容和读取全量内容。频繁读全量日志文件,不仅浪费磁盘 IO,还会让伴生窗口的文本框数据无限增长,最终表现为界面卡死。

5. 从单任务到多任务:队列、状态切换和失败标记

5.1 为什么单任务能跑通,不代表批量任务没问题

上面第一版代码只适合单条任务。你把 Codex 跑起来,窗口跟着刷新,任务结束之后状态变成完成,这个流程没有问题。但如果你的目的是让 Codex 连续处理多个文件、多个仓库,或者多个独立请求,那情况会复杂很多。

批量任务带来的新问题有三个:

  1. 日志会混在一起,难以分清当前日志是哪条任务的。
  2. 任务失败时,队列里的下一个任务是否继续,需要明确策略。
  3. 窗口显示的内容需要包含任务序号、总任务数、当前任务名称,否则根本不知道批量跑到哪了。

所以我在后面加了一个任务状态对象,用一个列表保存任务队列,每条任务记录它的名称、状态、开始时间、结束时间、日志区间。

5.2 任务状态机的设计

一个任务的状态可以拆成:

pending -> running -> success -> failed -> skipped

pending 表示等待执行,running 表示正在处理,success 表示成功完成,failed 表示执行失败,skipped 表示因为前面任务失败或策略原因被跳过。

跟随窗口里显示的不再是单一状态,而是当前任务的完整状态。例如:

任务:处理 login.py 单测补全 进度:3 / 5 状态:running 最近输出:正在修改 src/auth/login.py

这个状态表可以同时容纳两条信息:当前任务和整体进度。从使用者角度来说,真正想知道的是“现在跑到哪了”,所以任务号比具体输出内容更重要。

5.3 失败任务的处理策略

这里最容易踩坑的是失败重试。新手做批量任务时,经常默认所有失败任务都自动重试。实际跑起来后会发现,有些任务失败是因为输入内容不合法,不是因为 Codex 临时报错,自动重试会反复卡在同一条任务上。

更稳妥的做法是:

  1. 记录失败次数。
  2. 失败次数小于等于 2 时自动重试。
  3. 超过次数后标记为 failed,不再重试。
  4. 窗口里用红色显示失败任务,并保留错误信息片段。

你也可以把重试做成手动确认。伴生窗口上加一个按钮,只有点击才重试失败任务。这种方式适合交互场景,但需要处理窗口按钮回调逻辑,第一版可以先不做。

我的实际操作建议是,先用“失败跳过 + 日志记录”模式跑一遍。确认没有大面积失败之后,再开启自动重试,并把重试次数限制在一到两次。不要一上来就开最大重试次数,否则遇到网络波动或输入格式问题,任务队列会被拖得很长。

6. 不稳定和报错时,按这个顺序排查

6.1 现象怎么分层

任何一个伴生窗口类工具,出问题时都要先分清问题到底在哪一层。我通常把问题分成四层。

问题层典型现象排查重点
窗口层窗口不显示、不置顶、不能拖动窗口管理器、tkinter 属性、系统权限
数据层窗口正常但内容不刷新日志路径、文件编码、偏移量记录
Codex 层Codex 本身报错或没有输出Codex 配置、模型名称、API 端点、网络访问
资源层窗口卡死、内存持续增长日志量过大、刷新频率过高、线程阻塞

这个顺序很重要。很多人遇到窗口不刷新,马上怀疑是 Codex 出了问题,结果查了半天发现是日志文件路径写错了。先看窗口本身,再看数据来源,最后查 Codex 环境,这样效率最高。

6.2 日志文件读不到内容

最常见的原因是日志文件路径不正确,或者 Codex 没有输出到标准日志目录。第一版代码一定要先确认日志目录下确实有文件,并且文件在增长。可以用以下命令观察:

ls -l /path/to/log/dir tail -f /path/to/log/codex.log

如果tail -f能看到 Codex 输出,但窗口读不到,那就是读取代码里的路径或编码问题。比如日志编码是 UTF-8,但代码用了默认编码读取,就会报错或者读出来全是乱码。

还有一种情况是日志文件被轮转。Codex 或系统日志管理器可能在文件达到一定大小后把旧日志重命名,新日志继续写到原路径。这时候你的偏移量还停留在旧文件的位置,新文件从 0 开始,但偏移量不对,会导致内容读取为空。处理方式是在读取前判断文件句柄的 inode 或文件修改时间,如果发现文件被替换,就把偏移量重置为 0。

6.3 Codex 端点类报错怎么排查

如果你的 Codex 日志里出现类似于“endpoint”或者“responses”之类的报错,先不要慌着改窗口代码。这类问题大概率不在跟随窗口,而在 Codex 本身的配置或网络环境。

排查建议按这个顺序来:

  1. 看错误文本里有没有给出模型名称或端点名称。
  2. 确认名称和当前使用的版本是否匹配。
  3. 确认网络访问是否正常,API 配置是否完整。
  4. 如果是配置变更后才出现的问题,回滚到之前正常配置。
  5. 确认 Codex 单独运行是否还会报同样的错。

如果 Codex 单独运行也报同样的错,那窗口这边再折腾也没用。先把 Codex 恢复到能正常完成一条任务的基线,再回来做日志读取。

我遇到过一种情况,window 显示报错,logs 里看起来像“处理命令失败”,但实际上是输入文件路径中包含中文,导致命令行解析异常。这种问题和模型、端点都没关系,纯粹是输入材料没有处理好。所以看到报错之后,先看错误里提到的文件名和路径,再考虑深层原因。

6.4 窗口卡死和资源占用

如果你发现跟随窗口开着开着就不响应了,优先级最高的是看内存占用和日志文本框长度。tkinter 的Text控件在内容越来越多时,重绘成本会不断上升,最终表现为界面卡死。

解决办法有两种:

  1. 每次都只保留最近 50 行输出。
  2. 把日志内容从文本框里移出去,只用标签显示状态,不显示原始日志。

第二种方式更省资源,但调试时不方便。更平衡的方案是:默认只在窗口里显示状态摘要,点“展开日志”按钮时才加载完整日志内容。

另外,如果你用多线程读取日志,又不小心在子线程里直接操作 tkinter 控件,大概率会触发线程安全问题。tkinter 的控件操作要回到主线程执行,子线程只负责读数据和解析,通过队列把更新事件发给主线程,这样最稳。

6.5 常用的排查清单

现象首选检查项备选检查项
窗口不置顶系统是否允许窗口置顶是否有其他桌面工具干扰
窗口内容不更新日志文件路径日志文件偏移量是否被重置
状态一直不变日志里是否没有新行关键词匹配规则是否对不上
界面卡死Text 控件内容过多子线程操作控件
Codex 报端点错误配置是否存在网络访问是否正常
批量任务乱跑失败重试策略日志任务编号解析

按照这个清单排查,很快就能定位问题。不要一上来就重写代码,也不要直接把问题归因到“Codex 这个工具不行”,大部分时候都是路径、权限、日志格式或任务策略的问题。

7. 不适合用这个方案的场景,以及还能怎么升级

7.1 什么时候不要用跟随窗口

首先,如果你只是偶尔给 Codex 发一两条指令,全程盯着终端看结果,那这个方案只会增加复杂度。置顶窗口反而会挡住其他界面,显得多余。

其次,如果你的 Codex 任务是交互式的,需要你中途确认再继续,那跟随窗口不能替代原终端的输入功能。它只能展示状态,不能代替你回答问题。

另外,如果日志文件包含敏感信息,而你又把日志内容展示在了半透明置顶窗口里,这会有安全风险。公共场合演示时,注意日志内容会不会泄漏代码中的隐私信息,比如路径、用户名、内部服务地址。

7.2 后续可以拓展的方向

第一版只做了状态显示,后续还可以做更细化的东西:

  1. 把窗口变成任务控制台,支持手动停止当前任务。
  2. 增加声音提示,任务完成或失败时播放短提示音。
  3. 把日志解析结果同步到本地文件,方便后续统计每次批量任务的耗时。
  4. 增加 Web 页面,把状态推到浏览器标签页上。
  5. 给不同的 Codex 任务打标签,按项目维度管理任务状态。

如果要支持停止任务,就需要获取 Codex 进程句柄,并在窗口按钮回调中实现进程终止。这一步要非常谨慎,因为强制终止任务可能导致文件写入不完整。建议先确认当前任务已经完成一个阶段,再执行停止操作。

窗口位置自动跟随可以继续优化。当你使用多显示器时,要判断 Codex 窗口所在的显示器,把伴生窗口放到同一块屏幕的边缘,而不是固定在主屏幕。这个逻辑在 Windows 上可以查每个显示器的坐标范围,然后在获取到的 Codex 窗口坐标基础上做一次边界判断。

如果你后续想发布给其他人使用,建议把 Python 脚本打包成可执行文件,同时把日志路径改成可配置的。每个人的 Codex 日志路径不一样,硬编码路径会让用户直接卡在第一步。

我自己平时用这个方案的感受是:单任务测试很顺利,批量任务一开始乱,后来把任务编号和状态机加上就好多了。整个项目做下来,真正花时间的地方不是窗口置顶和半透明这些视觉效果,而是日志格式解析和任务状态判断。

如果要总结一句最核心的经验,那就是:先确认日志能稳定读到,再考虑界面好不好看。窗口做得再漂亮,数据链路不通也是白搭。你如果也遇到 Codex 跑长任务时切窗口漏进度的问题,可以先从最简单的日志读取窗口开始做,跑通之后再加置顶、跟随和多任务队列。这个方向可以走很久。

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

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

立即咨询