Skip to content
Open
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
6 changes: 4 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Runtime is **Bun** — do not change the local Bun version.
| Validate plugin manifest | `bun run validate:plugin` (requires `claude` CLI) |
| Local dev link | `bun link` then `agentbridge dev` (registers local marketplace + installs plugin; repo checkout only) |
| Update global CLI + plugin | `bun run install:global` (one command: rebuild, replace global install, sync plugin; `--skip-plugin` to opt out) |
| Remove legacy doc sections | `agentbridge deinit` (strips the `<!-- AgentBridge -->` blocks from CLAUDE.md / AGENTS.md; runtime delivery unaffected) |
| Start session | `agentbridge claude` (one terminal) + `agentbridge codex` (another) |
| Show quota snapshot | `agentbridge budget [--json]` (both agents' 5h/weekly usage, drift, pause state) |
| Stop everything | `agentbridge kill` |
Expand Down Expand Up @@ -59,8 +60,9 @@ Claude Code ── MCP stdio ──▶ bridge.ts (foreground)
- **`src/daemon-lifecycle.ts`** — shared `ensureRunning` / `kill` / startup-lock logic; both the CLI and `bridge.ts` call into this.
- **`src/daemon-client.ts`** — typed WS client used by `bridge.ts` to talk to the daemon control port.
- **`src/config-service.ts`** + **`src/state-dir.ts`** — read/write `.agentbridge/config.json` and resolve the platform state dir (`daemon.pid`, `status.json`, `agentbridge.log`, `killed` sentinel, `startup.lock`).
- **`src/cli.ts` + `src/cli/*.ts`** — `abg` / `agentbridge` command router (`init`, `claude`, `codex`, `pairs`, `doctor`, `budget`, `kill`, `dev`).
- **`src/marker-section.ts` + `src/collaboration-content.ts`** — idempotent marker-based injection of the `<!-- AgentBridge:start/end -->` block into `CLAUDE.md` and `AGENTS.md` during `abg init`. (Other agent config files — GEMINI.md / .cursorrules / .kiro etc. — are NOT injected today; backlog, not shipped behavior.)
- **`src/cli.ts` + `src/cli/*.ts`** — `abg` / `agentbridge` command router (`init`, `deinit`, `claude`, `codex`, `pairs`, `doctor`, `budget`, `kill`, `dev`).
- **`src/collaboration-contract.ts` + `src/session-context-hook.ts`** — the runtime collaboration carriers (the default since the pluggable-injection change): the codex proxy injects `CODEX_DEVELOPER_CONTRACT` as native developer context (idempotent per `(threadId, contractHash())`), and the plugin SessionStart hook emits `CLAUDE_SESSION_CONTEXT` via `bridge-server.js --print-session-context` only while the daemon is healthy + TUI attached. Projects keep zero static footprint.
- **`src/marker-section.ts` + `src/collaboration-content.ts`** — legacy marker-based injection of the `<!-- AgentBridge:start/end -->` block into `CLAUDE.md` and `AGENTS.md`; opt-in via `abg init --inject-docs`, removable with `abg deinit`. (Other agent config files — GEMINI.md / .cursorrules / .kiro etc. — are NOT injected; backlog, not shipped behavior.)
- **`src/bridge-disabled-state.ts` + `src/tui-connection-state.ts`** — disabled-reason and TUI-connect state machines used by the kickoff + reconnect UX.

### Data flow invariants
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ What that buys you, concretely:
A common worry about real-time bidirectional messaging is that the two agents' contexts merge and grow without bound. They don't. **The bridge passes messages, not context** — each agent keeps its own context window, and the bridge never copies one agent's full transcript into the other. (And which agent plans vs executes is your call — the roles aren't fixed; Codex can drive Claude just as easily.) Three filters keep what actually crosses small:

1. **Only `agentMessage` crosses.** The bridge forwards an agent's actual conclusions, not its tool-call noise — `commandExecution`, `fileChange`, and reasoning deltas never reach the other side, nor does its full scrollback.
2. **Three-tier marker routing** (default `filtered` mode). Each message is tagged and the daemon routes by tag: `[IMPORTANT]` forwards immediately, `[STATUS]` is buffered and batched into one periodic summary (default: 3 updates or 15s), `[FYI]` is dropped. The marker rules live once in the project's `AGENTS.md` (written by `abg init`), loaded at agent startup.
3. **The collaboration contract lives once** in `AGENTS.md`, not appended to every message (which would pollute every thread and its resume title).
2. **Three-tier marker routing** (default `filtered` mode). Each message is tagged and the daemon routes by tag: `[IMPORTANT]` forwards immediately, `[STATUS]` is buffered and batched into one periodic summary (default: 3 updates or 15s), `[FYI]` is dropped. The marker rules ride the runtime collaboration contract, delivered automatically while the bridge is attached.
3. **The collaboration contract lives once per thread** — injected as native developer context when the bridge attaches (and for Claude, per session via the plugin hook), not written into your project files and not appended to every message (which would pollute every thread and its resume title). Your `CLAUDE.md` / `AGENTS.md` stay untouched, and new sessions started without a bridge receive no collaboration contract (at most a short operational notice on how to start the bridge). One honest caveat: a Codex thread that *was* bridged keeps the injected contract in its own session history (Codex's storage, not your repo) — the contract's opening scope clause neutralizes it there ("no live bridge messages → ignore this, work solo").

Net effect: each side receives a curated stream of meaningful messages, so context grows with the number of real exchanges — not the other agent's raw activity. Set `AGENTBRIDGE_FILTER_MODE=full` (or the config equivalent) when you *do* want the unfiltered stream.

Expand Down Expand Up @@ -156,7 +156,8 @@ agentbridge codex # (another terminal) Start Codex TUI connected to the bridge

| Command | Description |
|---------|-------------|
| `abg init` | Install plugin, check dependencies (bun/claude/codex), generate `.agentbridge/config.json` |
| `abg init [--inject-docs]` | Install plugin, check dependencies (bun/claude/codex), generate `.agentbridge/config.json`. Collaboration guidance is delivered at runtime while the bridge runs; `--inject-docs` additionally writes the legacy static sections into `CLAUDE.md` / `AGENTS.md` |
| `abg deinit` | Remove AgentBridge sections previously injected into this project's `CLAUDE.md` / `AGENTS.md` (runtime delivery is unaffected) |
| `abg claude [args...]` | Start Claude Code with push channel enabled. **Runs with `--dangerously-skip-permissions` by default** (opt out: `--safe` or `AGENTBRIDGE_SAFE=1`). Clears any killed sentinel from a previous `kill`. Pass-through args are forwarded to `claude` |
| `abg codex [args...]` | Start Codex TUI connected to AgentBridge daemon. **Bare `abg codex` auto-resumes the pair's last thread; use `abg codex --new` for a fresh thread. TUI launches run with `--yolo` by default** (opt out: `--safe` or `AGENTBRIDGE_SAFE=1`; non-TUI subcommands like `exec` are never touched). Pass-through args forwarded to `codex` |
| `abg resume [claude\|codex]` | No target: print the resume commands for this directory's last Claude session and this pair's current Codex thread. With a target: resume that side directly |
Expand Down Expand Up @@ -231,7 +232,9 @@ Running `agentbridge init` creates a `.agentbridge/` directory in your project r

| File | Purpose |
|------|---------|
| `config.json` | Machine-readable project config (Codex ports, turn coordination, idle shutdown) |
| `config.json` | Machine-readable project config (Codex ports, turn coordination, idle shutdown, runtime injection) |

Notable switch: `"injection": {"runtime": true}` (default) controls the runtime delivery of the collaboration contract on **both** sides — set it to `false` to opt a project out entirely (Codex developer-context injection off, and the plugin's SessionStart hook goes fully silent for that workspace).

The config is loaded by the CLI and daemon at startup. Re-running `init` is idempotent and will not overwrite existing files.

Expand Down
11 changes: 7 additions & 4 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ English version: [README.md](README.md)
很多人对"实时双向通信"最大的担心是:两个 agent 的上下文会不会合并、越滚越大。不会。**桥传的是消息,不是上下文** —— 每个 agent 各自维护自己的上下文窗口,桥从不会把一方的完整对话历史拷进另一方。(而且谁规划、谁执行完全由你定,角色不写死,让 Codex 指挥 Claude 也一样。)在这个前提上,三层过滤让真正跨过桥的东西尽量少:

1. **只转发 `agentMessage`。** 桥只转发 agent 真正说出来的结论,它执行命令的输出、`commandExecution`、`fileChange`、推理过程这些中间噪声和完整 scrollback 都不过桥。每一方看到的是对方的结论,不是干活的流水账。
2. **三级标签路由**(默认 `filtered` 模式)。每条消息带标签,daemon 按标签决定去留:`[IMPORTANT]` 立刻转发,`[STATUS]` 先缓冲、攒几条(默认 3 条或 15 秒)合并成一条摘要,`[FYI]` 直接丢。标签规则一次性写在项目的 `AGENTS.md` 里(`abg init` 注入),agent 启动读一次
3. **协作契约只存一份**在 `AGENTS.md`,不附带在每条消息上(否则每个 thread 和它的 resume 标题都会被污染)。
2. **三级标签路由**(默认 `filtered` 模式)。每条消息带标签,daemon 按标签决定去留:`[IMPORTANT]` 立刻转发,`[STATUS]` 先缓冲、攒几条(默认 3 条或 15 秒)合并成一条摘要,`[FYI]` 直接丢。标签规则随运行时协作契约自动下发,桥接着才生效
3. **协作契约每个 thread 只注入一份**——桥接上时以原生 developer 上下文注入(Claude 侧则由插件 hook 按会话注入),既不写进你的项目文件,也不附带在每条消息上(否则每个 thread 和它的 resume 标题都会被污染)。你的 `CLAUDE.md` / `AGENTS.md` 保持原样,没有桥的新会话不会收到协作契约(至多一条"如何启动桥"的运营短提示)。一个诚实的边界:**桥接过的** Codex thread 会在它自己的会话历史里保留已注入的契约(存在 Codex 的存储里,不在你的仓库)——契约开头的失效条款会在脱桥后使其自动中和("没有真实桥消息就忽略本契约、独立工作")。

最终效果:每一方收到的是对方精选过的有意义消息,上下文的增长跟的是"有效交流的条数",不是"对方活动的原始量"。需要看完整原文时,设 `AGENTBRIDGE_FILTER_MODE=full` 即可关掉过滤。

Expand Down Expand Up @@ -156,7 +156,8 @@ agentbridge codex # (另一个终端)启动 Codex TUI 连接 Bridge

| 命令 | 说明 |
|------|------|
| `abg init` | 安装插件、检查依赖(bun/claude/codex)、生成 `.agentbridge/config.json` |
| `abg init [--inject-docs]` | 安装插件、检查依赖(bun/claude/codex)、生成 `.agentbridge/config.json`。协作指引在桥运行时自动下发;`--inject-docs` 才会额外把旧版静态段落写进 `CLAUDE.md` / `AGENTS.md` |
| `abg deinit` | 移除以前注入到本项目 `CLAUDE.md` / `AGENTS.md` 的 AgentBridge 段落(不影响运行时下发) |
| `abg claude [args...]` | 启动 Claude Code 并启用 push channel。**默认带 `--dangerously-skip-permissions`**(关闭:`--safe` 或 `AGENTBRIDGE_SAFE=1`)。自动清除上次 `kill` 留下的 sentinel。额外参数透传给 `claude` |
| `abg codex [args...]` | 启动连接 AgentBridge daemon 的 Codex TUI。**裸 `abg codex` 自动续接该对上次的 thread;`abg codex --new` 开新 thread。TUI 默认带 `--yolo`**(关闭:`--safe` 或 `AGENTBRIDGE_SAFE=1`;`exec` 等非 TUI 子命令不受影响)。额外参数透传给 `codex` |
| `abg resume [claude\|codex]` | 不带目标:打印本目录上次 Claude 会话 + 本对当前 Codex thread 的续接命令。带目标:直接续接该侧 |
Expand Down Expand Up @@ -231,7 +232,9 @@ AgentBridge 是一个**两进程**本地 Bridge:

| 文件 | 用途 |
|------|------|
| `config.json` | 机器可读的项目配置(Codex 端口、回合协调、空闲关闭) |
| `config.json` | 机器可读的项目配置(Codex 端口、回合协调、空闲关闭、运行时注入) |

值得一提的开关:`"injection": {"runtime": true}`(默认)控制协作契约在**双侧**的运行时下发——设为 `false` 可让项目整体退出(Codex 侧 developer 上下文注入关闭,Claude 侧 SessionStart hook 对该工作区完全静默)。

CLI 和 daemon 启动时会加载该配置。重复运行 `init` 是幂等的,不会覆盖已有文件。

Expand Down
57 changes: 57 additions & 0 deletions docs/test-plans/pluggable-injection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# E2E 测试计划:可插拔协作注入 / Pluggable collaboration injection

- **分支 / Branch**: `feat/pluggable-injection`
- **日期 / Date**: 2026-07-10
- **前置 / Precondition**: `bun run typecheck && bun test src` 全绿;改过 `src/` 后已执行 `bun run build:plugin`(安装的插件加载的是 bundle,不是 TS 源码)。

## 变更摘要 / What changed

静态注入退役为可选项:`abg init` 不再默认写 `CLAUDE.md` / `AGENTS.md`。协作指引改为运行时下发:

- **Claude 侧**:SessionStart hook(`health-check.sh` → `bridge-server.js --print-session-context`)在 daemon 健康 + Codex TUI 已连接时输出完整协作上下文(绕过冷却戳,每个新会话必达);桥不在时只有原有短提示。
- **Codex 侧**:daemon 的 codex proxy 在 `thread/start` 合并注入 `developerInstructions`,对既有线程在 `thread/resume` 后、放行首个 turn 前用 `thread/inject_items` 注入 developer contract item;按 `(threadId, contractHash())` 幂等,hash 变化走 `codexContractSupersedePayload`(重发完整合同)。
- **CLI**:`abg init --inject-docs`(旧行为,选择性开启)、`abg deinit`(摘除存量注入段落)。
- **配置**:`.agentbridge/config.json` 的 `injection.runtime: false` 关闭双侧运行时注入——Codex 侧 proxy 完全旁路,Claude 侧 SessionStart hook 对该工作区**完全静默**(唯一例外:resume-ack 恢复哨兵,一次性消费)。接受的拼写与 `normalizeBoolean` 一致(`false`/`"false"`/`"0"` 关,`true`/`"true"`/`"1"` 开,其余走默认开)。
- **裁定备注**:桥未启动时的"如何启动"短提示是运营提示、先于本 PR 存在,默认配置下保留(A3/A4);用户显式 `runtime=false` 表达的是"这个项目别打扰我",因此该配置下短提示也一并静默(A5)。

## A. Claude 侧 hook(人工 / manual)

| # | 步骤 | 预期 |
|---|------|------|
| A1 | 桥全启(`abg claude` + `abg codex`),另开一个新 Claude Code 会话 | SessionStart 附加上下文包含 "AgentBridge is running" 状态行 + `[AgentBridge runtime context]` 完整协作块 |
| A2 | A1 后 2 分钟内(冷却窗口内)再开一个新会话 | 仍然收到完整协作块(上下文路径绕过冷却) |
| A3 | `abg kill` 后开新会话 | 只有 "daemon is not reachable" 短提示,无协作块 |
| A4 | daemon 在跑但 Codex TUI 未连接时开新会话 | 只有 "Codex TUI is not connected" 短提示,无协作块 |
| A5 | 项目 config 写入 `{"injection": {"runtime": false}}`,桥全启,开新会话 | hook **零输出**(无协作块、无短提示;`"false"`/`"0"` 拼写等效) |
| A5b | 同 A5 配置但桥未启动,开新会话 | 同样零输出(短提示也被显式退出静默) |
| A6 | 直接执行 `bun plugins/agentbridge/server/bridge-server.js --print-session-context --workspace <项目路径> --notice ""` | stdout 输出单行合法 hook JSON;不启动任何桥进程、不创建 state 目录 |

## B. init / deinit(人工 / manual)

| # | 步骤 | 预期 |
|---|------|------|
| B1 | 空目录 `abg init` | 生成 `.agentbridge/config.json`;**不创建/不修改** `CLAUDE.md` / `AGENTS.md`;输出说明运行时下发 + 两个相关命令 |
| B2 | `abg init --inject-docs` | 与旧版一致:两文件写入 `<!-- AgentBridge:start/end -->` 段落,幂等可重跑 |
| B3 | B2 之后 `abg deinit` | 两文件恢复原状(仅注入块的文件变空并提示可删除);重复执行提示 "no AgentBridge section found" |
| B4 | 对本仓库执行 `abg deinit`(真实存量项目) | 仅摘除 marker 块,块外内容零丢失(`git diff` 验证) |
| B5 | 手工破坏 marker(删掉 end 标记)后 `abg deinit` | 报 "skipped — Malformed",文件未被改动 |

## C. Codex 侧 developer contract(Codex 负责验证 / verified by Codex)

| # | 场景 | 预期 |
|---|------|------|
| C1 | 桥接下新建线程 | 真实 `thread/start` 请求携带合并后的 `developerInstructions`(不覆盖客户端已有值);pair state 记录 `(threadId, contractHash())` |
| C2 | 首次桥接一个既有线程(resume) | resume 成功响应被扣住,`thread/inject_items`(role=developer)成功且 pair state 原子落盘后才放行;注入失败/超时以明确错误结束原请求,resume 不悬挂 |
| C3 | 同一线程再次 resume(hash 未变) | 不重复注入 |
| C4 | 修改合同内容后(hash 变化)resume 老线程 | 注入 `codexContractSupersedePayload(旧hash)`(取代头 + 完整新合同),落账新 hash |
| C5 | `injection.runtime=false` | 双向完全旁路,行为与改动前一致 |
| C6 | 老版本 Codex(协议无该字段) | 显式兼容性错误,不静默回退写 AGENTS.md |
| C7 | 残留边界确认:桥接过的线程脱离桥后 `codex resume` | 旧合同仍在 rollout(已知硬边界);观察模型遵循开头失效条款(无桥消息即独立工作,不等待 Claude) |

## D. 回归 / Regression

| # | 步骤 | 预期 |
|---|------|------|
| D1 | `bun run check` | typecheck + 全量测试 + plugin sync + plugin versions 全绿 |
| D2 | 正常双端会话互发消息([IMPORTANT]/[STATUS]) | 路由行为与改动前一致(marker 规则现在随 contract 下发) |
| D3 | `abg doctor` | 无新增 FAIL |
4 changes: 2 additions & 2 deletions plugins/agentbridge/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AgentBridge Plugin

Claude Code plugin for AgentBridge. This plugin packages the AgentBridge MCP frontend with push channel delivery (a failed push falls back to an in-memory queue drained by `get_messages`), the `/agentbridge:init` command, and a non-blocking SessionStart health check.
Claude Code plugin for AgentBridge. This plugin packages the AgentBridge MCP frontend with push channel delivery (a failed push falls back to an in-memory queue drained by `get_messages`), the `/agentbridge:init` command, and a non-blocking SessionStart hook that delivers the runtime collaboration context while the bridge is up (and short health notices otherwise).

## Structure

Expand Down Expand Up @@ -39,5 +39,5 @@ This creates self-contained bundles at:

- The plugin frontend launches the sibling daemon bundle via `AGENTBRIDGE_DAEMON_ENTRY=./daemon.js`.
- Claude delivery is always push notifications. If a push fails, the message is queued and can be drained via `get_messages` (per-message fallback — the legacy `AGENTBRIDGE_MODE=pull` mode was removed and the env var is ignored with a one-time warning).
- The SessionStart hook is informational only. It never starts or stops the daemon.
- The SessionStart hook never starts or stops the daemon. While the bridge is up (daemon healthy + Codex TUI attached) it delegates to `bridge-server.js --print-session-context` and injects the load-bearing collaboration context (bypassing the notice cooldown); otherwise it emits cooldown-limited informational notices. An explicit `injection.runtime=false` in `.agentbridge/config.json` silences it entirely (except the one-shot resume-ack recovery notice).
- The command at `/agentbridge:init` edits project-local `.agentbridge/` files only; plugin installation and marketplace registration remain terminal-side tasks (`agentbridge init` / `agentbridge dev`).
3 changes: 3 additions & 0 deletions plugins/agentbridge/commands/init.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ If `.agentbridge/config.json` is missing, create it with this default template:
"attentionWindowSeconds": 15,
"busyGuard": true
},
"injection": {
"runtime": true
},
"idleShutdownSeconds": 30
}
```
Expand Down
Loading
Loading