搭建个人网页版Codex

1. 背景说明

Codex 在 ChatGPT 5.5 以后,特别 5.6 以后,个人体验越来越强大好用了。

但依旧有一些局限性,当前,这些局限性主要是相对于我的需求:

  1. 我希望有一个地方可以常驻随时都能方便地执行一些我任意时候的想法,并且让这些想法的运行环境基于同一套知识库(也包括想法到知识库的更新)
  2. 我希望能随时都能通过 Codex 管理我的服务器(上面跑着我的各个服务,以及知识库等等)
  3. 我老婆和少数密友,可能因为特殊原因,不方便使用 Codex 甚至 ChatGPT,但这些顶端的Agent以及模型,理论上是能很好辅助做一些日常工作的(比如成绩分析PPT等等)

对于上面1,2需求,其实 OpenAI 官方也有给解决方案,就是用手机的 ChatGPT App 自带的远程,去控制 Win/Mac 电脑上的 Codex。但是我实际体验起来并不好,主要因为

    1. IOS上ChatGPT APP 远程控制电脑上的 Codex 连接非常不稳定,随时都在重连
    2. 我的 ChatGPT APP 远程模式下中文输入非常难受,经常输入不了,不知道为什么。导致只能英语
    3. ChatGPT APP 远程模式下目前没有语音输入的功能,不太高效。

    基于以上这些原因,近期做了一个私人 Codex 网页版平台(主要用 Codex 进行开发,但还是调了很多轮,并且一边试用一边调 bug)

    有类似需求的朋友可以直接基于我打磨过的工程,让 Codex/Claude Code 等等 Agent 来部署/进一步优化。可以少踩一些坑。

    2. Github

    公开仓库:

    https://github.com/Raytto/codex-web

    3. 介绍(主要由Codex编写)

    3.1 PP Agent:把 Codex CLI 变成一台可持续使用的私人 AI 工作站

    很多 AI 对话产品解决的是“问一句、答一句”,但真实工作往往没有这么简单:用户会连续上传文件,会在任务执行中追加要求,会关掉浏览器后再回来,也会希望 AI 记得长期偏好、继续昨天的工程,甚至直接管理一台服务器。

    PP Agent 正是围绕这些需求构建的一套私人 Codex Web 工作站。它的核心不是给 Codex CLI 套一层聊天界面,而是把 Codex 变成一个具有账号隔离、任务调度、文件生命周期、长期知识、线程恢复、停止与审计能力的持续服务。

    3.2 先用一分钟认识 PP Agent

    简单来说,PP Agent 是一个可以自己部署、连接自己 Codex 账号的网页版 AI 工作站。它复刻并扩展了 ChatGPT App 中最实用的工作体验,让 Codex 不只能够聊天,还能持续处理文件和长时间任务:

    • 支持直接用 ChatGPT 包月账号的额度,也可以基于这个工具小范围把账号共享给亲朋(但不要过分,OpenAI 本身是禁止共享的)
    • 支持 ChatGPT Codex App 本身的大部分功能
      • 支持文字、图片和各类文件一起发送,生成的结果也能直接在网页中查看和下载。
      • 支持语音输入(个人接的阿里云语音模型)、录音状态反馈和中英文混合转写;转写文字会自动加入当前草稿,再和附件一起发送。
      • 正在执行任务时,新任务会保存在服务器队列中;关闭浏览器再回来,任务和附件仍然存在。
      • 排队任务可以编辑、删除、拖动调整顺序,也可以随时“引导”正在工作的 Agent 改变方向。
      • 同一个会话按顺序执行,多个会话可以并行工作,还能随时停止正在运行或等待中的任务。
    • 可以创建多个用户,每个用户都有独立的对话、文件、Codex 登录状态和长期知识库。
    • 每个会话都有自己的持久化工作文件夹,Codex 可以继续操作之前上传或生成的内容,而不是每次从零开始。
    • 支持任务自动命名、历史恢复、实时进度、字体调节等更适合长期使用的网页体验。

    因此,它更像是一台装在浏览器里的私人 AI 电脑:用户负责提出目标、补充资料和随时调整方向,PP Agent 负责保存现场、安排任务并让 Codex 持续完成工作。

    本文介绍的是 PP Agent 当前私人生产版的完整思路。另有一个去除了私人账号、生产网络和宿主 root 桥接的公开衍生项目 Codex Web,适合普通用户自行部署;文中 PPHK 管理账号属于私人版的高权限扩展,并不是公开版的默认能力。

    3.3 从浏览器到 Codex:整体运行架构

    用户首先访问 HTTPS 网站。公网请求由 Nginx 接收,再转发到只监听服务器回环地址的 PP Agent 容器。容器内的 Web/API 负责登录、会话、SQLite、附件、队列和实时进度,但真正执行 Codex 的进程不直接复用 Web 进程身份。

    普通账号的任务会由一个最小 supervisor 派生为对应用户的 tenant worker;每个用户都有独立 Unix UID/GID、独立 Codex Home、独立长期知识库和独立会话目录。特殊的 PPHK 管理账号则不会进入普通 tenant worker,而是通过受限 Unix Socket 把结构化任务交给宿主机上的 root 服务。

    这套架构刻意避免了两个危险捷径:Web 容器不挂 Docker Socket,也不挂宿主机根目录。普通用户即使让 Codex 执行命令,默认也只能在自己的会话工作区和长期知识库范围内活动;需要宿主 root 权限的能力被拆成独立服务,并且只向一个固定管理账号开放。

    3.4 Docker 不是全部边界:容器内还有用户级隔离

    PP Agent 当前保留一个主应用容器,但容器内部不是“所有任务都用同一个 Linux 用户”。它分成三层身份:

    1. Supervisor:以容器 root 启动,只负责派生降权进程、转发结构化任务和在必要时终止进程组。
    2. Web/API:固定使用低权限 UID/GID,负责联网服务和持久化业务数据。
    3. Tenant worker:每个普通网站账号映射到独立 Unix UID/GID,Codex 和它启动的 Python、LibreOffice 等工具都以该身份运行。

    租户目录归对应 Unix 用户所有,Web 进程仅通过命名 ACL 获得完成上传、下载、结果登记和清理所需的访问权。普通租户不能列出其他租户目录,也不能读取其他账号的认证文件、Codex session 或知识库。

    容器本身还采用只读根文件系统、no-new-privileges、能力白名单、资源限制和最小挂载。Codex 内部继续使用 workspace-write 与 bubblewrap 工作区沙箱。需要说明的是,为了允许 bubblewrap 创建用户命名空间,当前专用容器放宽了 seccomp/AppArmor;因此这不是面向陌生攻击者的绝对沙箱。如果未来开放注册或处理强合规数据,更合适的升级方向是每用户独立容器或独立主机。

    3.5 每个用户拥有三种不同的“记忆”

    PP Agent 不把所有上下文混成一个无限增长的提示词,而是将它拆成三个层次。

    3.5.1 Codex Thread:负责“这段对话聊过什么”

    每个网页会话保存自己的 codex_thread_id。新任务使用 thread/start,后续任务使用 thread/resume,所以服务端不需要在每一轮把 SQLite 中所有历史消息重新拼成一份巨大提示词。长线程由 Codex 自己负责 compact;PP Agent 只向当前回合追加用户新指令、本轮附件,以及确实触发的安全说明。

    这样做既减少重复 token,也保留了真正的对话连续性。数据库中的消息主要用于网页展示、恢复和审计,而不是每一轮都重新灌给模型。

    3.5.2 Conversation Workspace:负责“这次任务正在处理哪些文件”

    每个会话都有一个真正持久化的目录,并且本身初始化为独立 Git 工作区:

    tenants/<user>/conversations/<conversation>/
    ├── AGENTS.md          # 本会话长期有效的工作规则
    ├── uploads/           # 用户上传的附件
    ├── outputs/           # 最终交付文件
    ├── .runtime/jobs/     # 每个任务的临时 HOME、缓存和过程文件
    └── .git/              # 会话级 Git 工作区

    AGENTS.md 固定描述租户边界、附件位置、输出要求、Python/Excel 处理方式和不得读取认证数据等规则。这些不随每个回合重复发送,而是由 Codex 在工作目录中自动加载。

    每个 job 还拥有独立的临时 HOME、TMP、XDG、pip/uv 缓存和 LibreOffice 运行目录,减少并发任务之间的锁、缓存和配置冲突。回合结束后过程目录会被清理;最终文件则从 outputs/ 复制到 Web 进程管理的不可变 deliverables 区域。这样即使 Agent 后来整理或清空工作区,历史消息中的下载链接也不会因为原文件被移动而失效。

    3.5.3 User Library:负责“以后还要继续使用什么知识”

    每个用户还有一个跨会话长期存在的知识库:

    tenants/<user>/library/
    ├── AGENTS.md
    ├── PROFILE.md
    ├── INDEX.md
    ├── inbox/
    ├── projects/
    └── archive/

    PROFILE.md 适合保存稳定偏好,例如常用语言、输出风格或工作习惯;INDEX.md 是简洁索引;projects/ 保存项目事实和资料;inbox/ 接收暂未整理的信息;archive/ 存放不再活跃但仍需保留的内容。

    知识库不是把所有聊天永久塞给模型,而是让 Agent 在相关任务中按需读取普通 Markdown 和文件。用户也可以明确要求:“把这份资料整理进我的知识库”,使它从一次性会话附件变成后续任务可复用的长期资料。认证文件、密码、Cookie 和 Token 明确禁止写入知识库。

    3.6 任务不是浏览器里的假队列,而是服务器状态

    PP Agent 的任务队列完全存在服务器端。文字、模型选择、顺序、附件关系和编辑状态都会进入 SQLite,附件实体也已经上传到服务器。因此用户关闭浏览器、换设备或重新登录后,排队任务仍然存在。

    同一个会话一次只运行一个 Codex turn,后续任务按持久 FIFO 依次执行;不同会话可以并行。当前按私人部署需求没有设置全站或每用户的应用层并发上限,但容器仍有 CPU、内存和 PID 资源上限。

    排队任务支持四个核心操作:重排、编辑、删除和引导。所谓“引导”不是再排一条新消息,而是使用 Codex app-server 的 turn/steer,将补充要求直接注入当前正在运行的 turn。停止则先调用 turn/interrupt;若任务没有及时退出,系统会升级为进程组 SIGTERM,最后使用 SIGKILL 强制结束。

    如果用户只上传文件却没有输入任何文字,系统不会猜测该做什么,也不会启动 Codex。附件会先持久化,页面提示用户补充具体操作;等真正收到指令后,原附件和新文字才作为同一任务交给 Codex。

    3.7 删除会话:删实体,但保留排障证据

    删除会话采用“文件硬删除、数据库软删除”的组合策略。

    系统会先删除尚未转正的排队草稿及附件,再取消 queued job、停止 running job,并等待进程退出。之后删除该会话的整个工作目录、不可变交付文件,以及只被该会话引用的 Codex thread/session 文件。

    但 SQLite 中的会话、消息、文件元数据、jobs 和 job events 不会级联消失。会话会写入 deleted_at 标记,从正常网页列表和接口中隐藏,数据库审计记录则保留给管理员排查。这意味着用户看到的是“已经删除”,运维人员仍能回答“当时到底发生了什么”。

    3.8 支持管理账号:从网页控制宿主机 root

    PP Agent 最特殊的能力,是一个专门用于服务器管理的 PPHK 账号。它和普通账号共用登录、会话、SQLite、队列、附件、进度展示和软删除逻辑,但执行路径完全不同。

    普通账号的“root”最多只是容器内部的身份;PPHK 账号调用的则是服务器宿主机真正的 UID 0。它可以检查和修改宿主文件、systemd、Docker 以及服务器工程,因此能够把 PP Agent 变成一套比聊天机器人更完整的服务器管理前端。

    为了避免把“网页输入”直接变成任意 root 路径,桥接服务只接受结构化字段,并执行多重约束:

    • 只允许固定的 PPHK 管理账号使用该桥接。
    • job、conversation、thread、模型和思考深度都要重新校验。
    • 会话目录由桥接服务根据可信根目录和 conversation ID 自行计算,不接受 Web 指定任意宿主路径。
    • 附件必须位于该会话 uploads/ 内,禁止绝对路径和目录穿越。
    • 每个任务使用独立 root 进程组;连接断开、用户停止或删除会话都会触发中断和强制终止后备。
    • 任务结束后修复会话目录所有权,让低权限 Web 进程仍能下载、登记和清理 root 产生的文件。
    • PPHK 使用独立 Codex Home,和普通账号的登录、线程及模型缓存分开。

    但root 账号也需要知识库

    拥有 root 权限并不等于了解服务器。PPHK Codex 默认从服务器管理知识库开始工作。这个知识库记录工程索引、真实目录映射、systemd 服务、数据目录、部署入口、历史故障和操作约束,并通过根级 AGENTS.md 强制要求 Agent 在操作服务器前先查本地资料、完成后再沉淀经过验证的新知识。

    因此,PPHK 的能力可以理解为:

    宿主 root 权限 + Codex 推理能力 + 持久会话 + 服务器知识库 + 可审计的网页任务系统。

    这比单纯给一个聊天机器人执行 shell 更可靠,因为它不仅能“运行命令”,还知道每个项目真实在哪里、应该通过哪个发布入口、哪些数据不能删除、历史上出现过什么问题,以及任务完成后哪些结论值得留给下一次使用。

    当然,这仍然是高风险能力。它只适合服务器所有者自己的受控账号,不应该开放注册,也不能因为有路径校验就把它视为无风险沙箱。任何公开发行版默认都应删除这条 root 执行链。

    3.9 网络、模型与语音输入

    普通 tenant worker 和 PPHK root Codex 的外网请求都经专用网络出口。当前私人部署使用 WireGuard 将相关流量导向独立出口,并设置 fail-closed 规则:隧道异常时拒绝回落到宿主默认公网。

    不同用户拥有独立 Codex Home,可以分别完成 Codex device-auth,保存各自的 thread、session、模型目录和插件状态。Web 页面从当前用户自己的模型目录读取可用模型和思考深度,并把选择持久化到 SQLite,而不是只放在浏览器 localStorage 中。

    语音输入采用浏览器录音、服务端转写的方式。录音结束后,服务器把临时音频交给 DashScope Omni 模型,中英文混合转写结果追加到输入框;若用户在录音状态直接发送,则转写文字会和原有草稿、附件一起进入正常队列。用于纠正专有名词的上下文被严格限制长度,只包含少量近期消息、草稿、附件名和固定词表,并明确要求模型不得把未说出口的上下文复制进转写结果。原始音频在完成或失败后清理,不进入 Codex thread 或长期知识库。

    3.10 可维护性:CLI 更新与应用发布分开

    Codex CLI 本身位于持久 runtime 中,而不是完全固定死在应用镜像里。定时任务可以在不重启正式容器的情况下准备新版本,在生产状态副本中使用真实账号、模型目录和并发任务做候选验收,然后原子切换 current 符号链接。失败则保留旧版本。

    应用重建走另一条受控流程:检查活动任务、运行完整测试、备份 SQLite 与状态、构建候选镜像、保留登录、检查普通租户和 PPHK root bridge,最后做容器与公网健康验收。这样“更新网页代码”和“升级 Codex CLI”不会互相绑死。

    3.11 这套设计解决了什么

    从用户视角看,PP Agent 仍然只是一个简洁的聊天网页。但它实际提供的是一套完整工作系统:

    • 浏览器关闭后,任务、附件和队列仍在服务器上。
    • 同一会话保持顺序,不同会话可以并行。
    • 用户可以停止、强制终止或实时引导正在运行的任务。
    • 每个用户拥有独立登录、线程、模型、文件和长期知识库。
    • 每个会话拥有可持续操作的文件夹,而不只是一次上传缓存。
    • 历史交付文件不会因为 Agent 清理工作区而失效。
    • 删除面向用户彻底生效,同时保留数据库排障记录。
    • 普通用户在容器和租户权限内工作;服务器所有者可通过单独的高权限桥接管理宿主机。
    • Codex 上下文由 thread、文件夹规则和知识库分层管理,避免反复发送整段历史与固定提示。

    3.12 仍然需要诚实面对的边界

    PP Agent 是为私人、彼此信任的少量用户设计的,不是一个已经完成公有云多租户安全认证的平台。

    普通租户虽然拥有独立 Unix UID 和目录 ACL,但仍共享同一个容器内核、网络和资源上限。不同会话同时修改同一个用户知识库文件时,仍可能发生应用层写冲突;当前没有 library 写锁。不同会话不设应用层并发上限,也意味着多个 PPT、PDF、LibreOffice 或 Python 重任务可能争抢 CPU、内存和临时空间。

    PPHK root 账号更不应被误解为普通功能开关。它拥有真实宿主权限,安全性依赖账号控制、协议白名单、路径重算、进程隔离、知识库规则、审计和严格运维共同组成的纵深防线。

    如果要让更多人使用,合理路线是公开版 Codex Web:让部署者使用自己的 Codex 登录和语音 API,保留队列、文件、线程和普通容器隔离,但去除私人账号、生产网络以及宿主 root 桥接。若进一步开放陌生用户注册,则应继续升级为每用户或每任务独立容器,并增加资源配额、知识库写锁、用量审计和更完整的安全边界。

    发表评论