Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/compiled-process-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Compiled POSIX process smoke

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

permissions: {}

jobs:
compiled-process-smoke:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: '24'
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version: 1.3.11
- run: npm ci
- name: Compile and exercise native override without Node on child PATH
timeout-minutes: 3
run: npm run test:compiled-process
43 changes: 43 additions & 0 deletions .github/workflows/native-e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Native loopback E2E

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

permissions: {}

jobs:
native-e2e:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: '24'
- uses: actions/setup-python@v6
with:
python-version: '3.13'
- name: Install locked dependencies (including native optional package)
run: npm ci
- name: Fresh build and real native stdio tests with loopback model
timeout-minutes: 5
run: npm run test:native
- name: Preserve build hashes, RPC, model, spawn and failure evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: native-e2e-${{ matrix.os }}
path: tmp/native-e2e/
include-hidden-files: true
if-no-files-found: warn
retention-days: 7
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Use [OpenAI Codex](https://github.com/openai/codex) from [Agent Client Protocol]
- Typed warnings and errors through the opt-in [AIR session failure extension](docs/air-extensions.md#session-failure).
- All AIR extensions, capabilities, and `_meta` keys: [AIR extensions](docs/air-extensions.md).
- A per-turn [agent file-change report](docs/air-extensions.md#agent-file-change-report) after capability negotiation.
- In-place message editing through the AIR [session rewind extension](docs/session-rewind-extension.md), without a provider fork.
- Client-provided MCP servers over command-based stdio config and HTTP transport.
- Slash commands: `/status`, `/mcp` (with `/mcp reconnect`), `/skills`, `/goal`, `/review`, `/review-branch`, `/review-commit`, `/compact`, and `/logout`, as well as configured skills.

Expand Down Expand Up @@ -107,3 +108,5 @@ See [AIR extensions](docs/air-extensions.md#async-tasks) for the capability, lif
## License

By contributing, you agree that your contributions will be licensed under the Apache 2.0 License.

Local native-session test extensions: [English](docs/native-session-extensions.md) | [简体中文](docs/native-session-extensions.zh-CN.md).
41 changes: 41 additions & 0 deletions docs/native-session-extensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Local native session extensions

This branch integrates the same-session rewind contract from [PR #508](https://github.com/agentclientprotocol/codex-acp/pull/508) on upstream `202e66e` (adapter 2.2.2, locked Codex 0.160.1). It preserves upstream custom instructions, session-index subscriptions and app-server recovery, alongside hooks, attachments, tool-call reports and compaction handling.

## Negotiated operations

Read the `initialize` metadata before invoking any extension. These are named methods with strict schemas, not a generic native RPC tunnel.

| Capability | Method | Contract |
|---|---|---|
| AIR `sessionRewind` | `_session/rewind` | Same ID, exclusive `beforeMessage`, optional `resumeAtMessage`; `{rewound:true}` only after persisted-prefix verification. |
| `_meta.steering.idleBehavior` | `_session/steering` | Opt in with `_meta.steering.idleBehavior:"promptRequired"`; idle returns `{outcome:"promptRequired"}` without starting a model request. Non-opted clients retain the existing idle new-turn behavior. |
| `_meta.runtime` | `_session/runtime/read` | `resource`: context, usage, mcp, commands, plugins. Unknown measurements stay null. Commands are native skills; this is not a list of every Desktop UI command. |
| `_meta.runtime` | `_session/runtime/control` | `action`: reloadSkills, reconnectMcp, reloadPlugins. Skill reload republishes available commands. MCP/plugins are provider-wide idle operations; their acknowledgement is not runtime readiness. |
| `_meta.archive` | `_session/archive`, `_session/unarchive` | `{sessionId}`; reversible native archive, same ID. Clients declaring AIR `sessionIndex` use the upstream index implementation, including list notifications and idempotence; other clients retain this extension's archive response. Upstream session/delete behavior, including indexed permanent deletion, is preserved. |
| `_meta.discovery` | `_session/search` | Bounded searchTerm/cursor/limit/archived. Searches user sources appServer/cli/vscode. Current native search can miss text retained only through ancestor rollout lineage after rewind. |
| `_meta.discovery` | `_session/attachments` | list/add/remove native metadata only. Does not create/delete a file, worktree, PR or prompt attachment. Loaded-session ownership required. |
| `_meta.queue` | `_session/queue` | Only list is advertised after actual-native version and positive nonmutating method probe. Writes/start return unsupported before native mutation. Pending native queues prevent ACP load/resume to avoid unowned auto-dispatch. |
| `_meta.fileRevert` | `_session/files/revert` | `{sessionId,toolCallId,dryRun,previewToken?}`; reverse one completed native fileChange tool's recorded text patches in Git. Preview token required for apply. History stays unchanged. |

## File reversal

Preview and commit re-read native history and fingerprint affected files. A stale preview, conflict, unknown change kind, binary/unsupported patch or unsafe path fails closed for the entire batch. Git uses reverse check/apply without --index/--cached/--3way, preserving the user's index and unrelated bytes. Reverted text follows Git attributes/autocrlf and is not promised to be byte-identical to its original encoding/newlines. Shell changes, unrecorded subagents and whole-workspace checkpoints are outside coverage.

The server reserves the provider while validating loaded native threads and background terminals. External editors and unrelated processes are not controlled by this fence; Git conflict checks and fresh fingerprints narrow but do not eliminate an OS-level race.

## Timeouts and recovery

POSIX supervision uses the current runtime and adds no external Node dependency. Compiled Bun executables re-enter fixed built-in supervisor/guardian modes before importing the ACP application or reading configuration/log settings; these modes require private pipe descriptors and accept no executable source. Node and direct Bun runs execute the same built-in helper implementation. The two small helpers preserve the provider's exit status and perform cleanup from within the owned group; they do not interpret ACP traffic. stdin EOF and stdout/stderr use inherited descriptors, common termination signals are forwarded, and the extra control pipe is unreferenced in the adapter. A POSIX provider spawn error is reported on stderr with supervisor exit code 1. Orphaned processes must be reaped by the host init (use --init in containers); unreaped groups can time out rather than be reported stopped. Compiled native CODEX_PATH launch is tested with Node absent from PATH; this does not establish compatibility of the separate bundled JavaScript launcher path in compiled distributions.

Runtime mutations have a 30-second deadline. Timeout invalidates local sessions and attempts to terminate the owned provider; reconnect the ACP adapter and explicitly load a session. No mutation is replayed. POSIX launches the provider (including the default Node launcher and native child) in a dedicated process group with a guardian. A private control pipe requests SIGKILL from inside that group; launcher/supervisor exit or adapter pipe closure also triggers cleanup. The adapter never sends a destructive signal to a remembered POSIX PID or PGID. Termination succeeds only after the supervisor exits and the group disappears; an unverifiable group or a five-second deadline is a failure, not a stopped-provider result. Processes that deliberately leave the group are outside this guarantee. Windows retains taskkill /T /F for a live owned launcher; if the launcher has already exited, tree ownership can no longer be verified and termination fails without targeting its old PID. Repeated termination calls share the same result, including failures. Read timeouts return unavailable. A history mutation with an uncertain outcome blocks further sends until successful load/reconciliation.

## Desktop parity boundaries

Supported and locally exercised: same-ID first/latest/historical/repeated rewind, cancel-and-settle, cold load, resend context truncation, runtime status/control, archive/unarchive, attachment metadata persistence, safe idle steering, native search of current rollout, queue inspection and per-tool text patch reversal.

Not claimed: full native queue automatic-turn adoption, realtime audio/voice, timeline API, complete Desktop project/worktree/UI management, whole-session file checkpoint restore, binary reverse patches, all child process/background writer isolation, or complete search over retained ancestor rollouts. A native schema method does not establish an implemented ACP handler.

## Validation

Run `npm run typecheck`, `npm test`, `npm run build`, `npm run bundle:all`, `npm run test:native` and `npm run test:compiled-process`. The committed native harness uses isolated homes, workspaces and a loopback model; it checks cold same-session rewind and actual text-patch restoration without user accounts. The refresh also tests crash recovery before extension calls, pending-queue refusal before lazy resume, and session-index mutation exclusion. See the PR validation record for results on its exact source revision; earlier branch test counts do not establish results for this revision.
33 changes: 33 additions & 0 deletions docs/native-session-extensions.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 本地原生会话扩展

本分支在上游 `202e66e` 上整合 [PR #508](https://github.com/agentclientprotocol/codex-acp/pull/508) 的同会话回退协议,adapter 2.2.2,冻结 Codex 0.160.1。保留上游自定义指令、会话索引订阅、进程崩溃恢复,以及 hooks、附件回放、工具调用与压缩处理。

## 已接入

- `_session/rewind`:保留原接口并注明作者来源;同 ID 回退首条、历史、最近消息。验证指纹/保留边界,生成中先取消并等待旧 prompt 清理,再调用原生回退并核验持久前缀。
- `_session/steering`:opt-in `_meta.steering.idleBehavior:"promptRequired"`,空闲返回 promptRequired,不偷偷启动模型。未 opt-in 保留旧行为。
- runtime read:context、usage、mcp、commands(native skills)、plugins。未知用量保留 null,不捏造分类。
- runtime control:reloadSkills、reconnectMcp、reloadPlugins;后两者要求 provider 空闲,确认回执不等于 runtimeReady。技能刷新后重发可用命令。
- archive/unarchive:原生可逆归档、同 ID。声明 AIR `sessionIndex` 的客户端使用上游索引实现,保留列表通知和幂等性;其它客户端保留本扩展的归档回执。保留上游 session/delete 行为,包括索引客户端的永久删除。
- search:限定参数和 appServer/cli/vscode 来源;已验证当前 rollout synthetic 文本命中。回退后仅保留在祖先 rollout 的文本可能被原生搜索漏掉。
- attachments:原生 thread 元数据 list/add/remove、冷重启持久化;不创建/删除磁盘文件、PR 或 worktree。
- queue:只在真实 native 版本和无副作用探测通过后广告 list。写入/start 在 native mutation 前 unsupported;已有 durable pending queue 阻止 ACP load/resume,避免无人归属的自动生成。
- files/revert:限定单个已完成 native fileChange 工具的 Git 文本补丁。preview token 绑定原生补丁与文件指纹,提交前复核;不改会话历史。

## 文件语义与恢复

文件撤销不等于 whole checkpoint:不覆盖 shell、未记录子代理、二进制补丁。Git reverse check/apply 不用 --index/--cached/--3way;用户 index 和无关文件严格保持。恢复后的文本走 Git 属性/autocrlf,不保证原始字节或换行完全一致。未知 change kind、缺失 diff、冲突、陈旧 token、越界/链接路径整批拒绝。

runtime mutation 30 秒超时后使会话失效并尝试终止 provider,用户重连 ACP 再显式 load;不会自动重放 mutation。POSIX 将 provider(包括默认 Node launcher 及 native 子进程)置于独立进程组,通过私有控制管道请求组内 guardian 执行 SIGKILL;launcher/supervisor 退出或适配器管道关闭也触发清理。适配器不会向保存的 POSIX PID/PGID 发破坏性信号。只有 supervisor 已退出且组已消失才报告终止成功;无法确认或五秒超时均报失败。主动脱离该组的进程不在保证内。Windows 对仍存活的 owned launcher 使用 taskkill /T /F;launcher 已退出时无法再确认整树归属,明确报错且不针对旧 PID 清树。重复关闭复用同一结果,包括失败。读取超时返回 unavailable。历史回退 dispatch 后失败可能已改变磁盘,必须成功 load 对账再继续发送。

POSIX 监督复用当前运行时,不新增外部 Node 依赖。Bun 编译产物通过固定内置 supervisor/guardian 模式重入,在加载 ACP 模块或读取配置/日志设置前分发;模式要求私有管道描述符,不接收可执行源码。Node 和直接 Bun 运行使用同一份内置 helper 实现。两个小型 helper 分别负责保留 provider 退出状态和从 owned 组内部清理,不解释 ACP 消息;stdin EOF 和 stdout/stderr 使用继承的文件描述符,常规终止信号转发给 provider,额外控制管道在适配器中 unref。POSIX provider 启动失败通过 stderr 和 supervisor 退出码 1 报告。宿主 init 必须回收孤儿进程(容器使用 --init);未回收的组可能超时报错,不虚报已停止。compiled native CODEX_PATH 路径已在 PATH 不含 Node 的条件下验证;这不代表另一个 bundled JavaScript launcher 路径在 compiled 发行物中的兼容性已得到验证。

## 尚未实现的 Desktop 能力

完整队列自动回合 observer/adoption、realtime voice、timeline、Desktop 项目/worktree/UI 全部管理、全工作区文件 checkpoint、二进制反向恢复、外部写入进程完全隔离、祖先 rollout 全文搜索均不宣称完成。native schema 存在某方法不等于本 ACP 已支持。

## 验证

运行 `npm run typecheck`、`npm test`、`npm run build`、`npm run bundle:all`、`npm run test:native` 和 `npm run test:compiled-process`。仓库内原生测试使用隔离 home/workspace 和本地模拟模型,检查同会话冷启动回退及真实文本补丁恢复,不使用用户账号。本次同步还覆盖扩展请求前的崩溃恢复、恢复会话前拒绝待发原生队列,以及会话索引修改互斥。具体提交的结果以 PR 验证记录为准,旧分支测试数字不代表当前修订已通过。

协议细节见 [English](native-session-extensions.md);原 rewind 作者 Nikita Ashikhmin,来源 [PR #508](https://github.com/agentclientprotocol/codex-acp/pull/508)。
Loading