ferrycat 0.0.0-stage → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Guo Qiang (GuoBug)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,210 @@
1
- # Temporary Holding Version
1
+ # FerryCat: Contract-Driven Cross-Host AI Agent Handoff Bus
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ <p align="center">
4
+ <b>English</b> •
5
+ <a href="README_zh.md">中文</a>
6
+ </p>
7
+
8
+ [![Node.js Version](https://img.shields.io/badge/Node.js-22.5%2B-339933?style=flat&logo=nodedotjs)](https://nodejs.org/)
9
+ [![Native Addons](https://img.shields.io/badge/Native_Addons-0_(node%3Asqlite)-success?style=flat)]()
10
+ [![Protocol](https://img.shields.io/badge/Protocol-MCP_Stdio-orange?style=flat)](https://modelcontextprotocol.io/)
11
+ [![Glama](https://img.shields.io/badge/Glama-MCP_Server-18181b?style=flat)](https://glama.ai/mcp/servers/GuoBug/FerryCat)
12
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
13
+
14
+ > **FerryCat** (`ferrycat`) is a lightweight, deterministic cross-host AI agent handoff and code review bus built on the Model Context Protocol (MCP) and Node.js built-in `node:sqlite` (WAL mode).
15
+ > Purpose-built to safely bridge isolated desktop IDE agent hosts (such as Google Antigravity, WorkBuddy, Claude Code, and Cursor) across separate OS processes without context drift or blind reviews on stale files.
16
+
17
+ <p align="center">
18
+ <img src="https://guobug.github.io/assets/images/flowchart-ferrycat-cross-host-demand-and-workflow.svg" alt="FerryCat Architecture and Workflow" width="960" />
19
+ </p>
20
+
21
+ > 📖 **Deep Dive**: [Why I Started Building FerryCat: When Two AI Coding Hosts Live on the Same Desktop](https://guobug.github.io/posts/2026/10/22/why-we-built-ferrycat-cross-host-agent-review-bus/)
22
+
23
+ ---
24
+
25
+ ## Core Pain Points & Engineering Solutions
26
+
27
+ While the official MCP `Tasks` specification (2025-11 / 2026-07) addresses long-running operations within a **single `Client ↔ Server` connection**, it does not solve cross-host collaboration on a developer workstation:
28
+ 1. **`stdio` Process Isolation**: Desktop IDEs spawn local MCP servers as independent child processes with isolated memory spaces and distinct Node.js runtimes.
29
+ 2. **Silent Blind Reviews on Stale Code (`Stale Context`)**: When handing off a task from Host A to Host B, unushed IDE buffers, branch switches, or mid-review edits cause the receiving agent to audit outdated code and produce plausible but invalid conclusions.
30
+ 3. **Context Window Bloat**: Pasting hundreds of lines of source code across chat windows duplicates token consumption on both ends.
31
+ 4. **Unstructured Handoff Outputs**: Free-form prose responses lack deterministic schema enforcement, making downstream consumption unreliable and prone to infinite retry loops.
32
+
33
+ **FerryCat resolves these failure modes through a human-scheduled, contract-driven SQLite WAL bus:**
34
+
35
+ ```
36
+ [Developer] [Host A: Antigravity] [~/.ferrycat/mesh.db] [Host B: WorkBuddy]
37
+ | | | |
38
+ |-- 1. "Dispatch review" --->| | |
39
+ | |-- dispatch_contract() -------->| Write status = SUBMITTED |
40
+ | | (Full SHA-256 + Line Bounds) | Store size / sha256 snapshot |
41
+ | | | |
42
+ |-- 2. Switch IDE: "Poll" ---|--------------------------------|------------------------------>|
43
+ | | |<-- poll_next_task() ----------|
44
+ | | | (Atomic claim changes===1 |
45
+ | | | + Verify disk SHA-256) |
46
+ | | | |-- Read local files
47
+ | | |<-- fulfill_contract() --------|-- Re-verify SHA-256
48
+ | | | (Validate Ajv JSON Schema | & submit JSON
49
+ | | | -> status = COMPLETED) |
50
+ | | | |
51
+ |-- 3. Switch IDE: "Inspect" | | |
52
+ | |-- inspect_mesh_state() ------->| Read-only query of COMPLETED |
53
+ |<-- Present structured res -| | |
54
+ ```
55
+
56
+ ---
57
+
58
+ ## Key Architectural Pillars
59
+
60
+ ### 1. Deterministic 7-State Machine
61
+ Every state transition is bound to an explicit SQL transaction with optimistic concurrency control (`changes === 1`), eliminating phantom or dangling states:
62
+
63
+ ```
64
+ dispatch_contract()
65
+ │
66
+ ▼
67
+ ┌───────────┐
68
+ │ SUBMITTED │ (Queue TTL: default 1800s)
69
+ └─────┬─────┘
70
+ │
71
+ poll_next_task()
72
+ ┌────────────────┼────────────────┐
73
+ │ (Queue TTL) │ (Hash Match) │ (Missing / Size Diff / SHA-256 Mismatch)
74
+ ▼ ▼ ▼
75
+ ┌───────────┐ ┌───────────┐ ┌───────────────┐
76
+ │ TIMED_OUT │ │ CLAIMED │ │ POINTER_STALE │ (Terminal: Rejects blind review)
77
+ └───────────┘ └─────┬─────┘ └───────────────┘
78
+ ▲ │ (Exec TTL: default 300s)
79
+ │ fulfill_contract()
80
+ │ ┌────────────────┼────────────────┬────────────────┐
81
+ └──┤ (Exec TTL) │ (Schema Valid) │ (Retries > 2) │ (Mid-Review File Edit)
82
+ │ ▼ ▼ ▼
83
+ │ ┌───────────┐ ┌───────────────────┐ ┌───────────────┐
84
+ └──────────│ COMPLETED │ │ VALIDATION_FAILED │ │ POINTER_STALE │
85
+ └───────────┘ └───────────────────┘ └───────────────┘
86
+ ```
87
+
88
+ Holder-side lifecycle controls on a `CLAIMED` contract (all require `agent_id === target_agent`):
89
+
90
+ | Call | Transition | Guard rails |
91
+ | :--- | :--- | :--- |
92
+ | `release_contract` | `CLAIMED → SUBMITTED` (tail of queue, fresh queue window) | Resets `extend_count` (renewal budget is per holder) but **preserves** `retry_count`: the schema-failure circuit breaker is a contract-level quota, so releasing cannot launder retry budget. Refused past the absolute lifetime deadline. Max 3 releases per contract. |
93
+ | `extend_ttl` | `CLAIMED → CLAIMED` (`claimed_at = now`) | Must be called inside the active window; max 5 renewals per holder; refused once `now ≥ created_at + 4 × queue_ttl_sec`. |
94
+ | `fail_contract` | `CLAIMED → FAILED` (terminal) | Requires a `reason`, stored as `WORKER_FAILED: <reason>`. A report arriving after the window closed becomes `TIMED_OUT` with the reason attached. |
95
+
96
+ ### 2. Full-File SHA-256 Fingerprinting, Pre-Flight Verify & TOCTOU Soft-Failure Buffering (ADR-0004)
97
+ - **Dispatch Snapshot**: `dispatch_contract` reads target files on disk, recording byte `size`, full-file `sha256` (64 hex chars), `first_line`, and `last_line`. If a file was modified within the last 3 seconds, a `warning` is emitted to alert the requester of potential unsaved IDE buffers.
98
+ - **Claim Verification**: `poll_next_task` verifies `Existence -> Byte Size -> Full-File SHA-256` (never rejecting on `mtime_ms` alone to tolerate no-op IDE saves). On mismatch, it waits `150ms` and retries once to rule out transient IDE flushes before marking the contract `POINTER_STALE`.
99
+ - **Pre-Flight Verify (`verify_contract`)**: A strictly read-only check allowing workers or scripts to audit pointer health (`valid: true` vs `valid: false` with dirty files list) before submitting.
100
+ - **TOCTOU Soft-Failure Buffering**: `fulfill_contract` checks on-disk pointer fingerprints. If dirty pointers are detected:
101
+ - **Strike 1** returns `RETRY_NEEDED`, leaves the contract `CLAIMED`, renews the execution window, and lists dirty files with recovery instructions (`git stash` / `git checkout -- <file>`).
102
+ - **Strike 2** transitions to terminal `POINTER_STALE` and saves the full submitted review payload into `salvaged_result_json` so review effort is never lost.
103
+ - **TOCTOU Race Narrowing**: While no file-based system can eliminate the microsecond race between reading disk bytes and committing a database row, FerryCat's dual-verification (at `poll` and at `fulfill`) drastically narrows the TOCTOU window from minutes of blind review down to the sub-millisecond atomic SQL transition boundary.
104
+
105
+ ### 3. Bounded Schema Validation (`RETRY_NEEDED` $\le 2$)
106
+ - Outputs are validated against the contract's JSON Schema via cached Ajv validators, with errors flattened into concise single-line paths (e.g., `["/passed: must be boolean"]`).
107
+ - Up to 2 validation failures return `RETRY_NEEDED` while keeping the contract `CLAIMED` for immediate self-correction (each rejection also renews the execution window); a 3rd failure transitions the contract to terminal `VALIDATION_FAILED`.
108
+
109
+ ### 4. Dual-Stage TTL & Read-Before-Write Sweeping
110
+ - Separates `queue_ttl_sec` (default 1800s for `SUBMITTED`) from `exec_ttl_sec` (measured from `claimed_at`), preventing tasks claimed late in the queue window from timing out prematurely during execution.
111
+ - `exec_ttl_sec` defaults are tiered by `task_type`: `code_review` / `architecture_audit` → 1800s (deep reasoning or human-in-the-loop), `implementation` / `test_generation` → 300s. An explicit `exec_ttl_sec` always wins.
112
+ - `poll_next_task` executes a lock-free `SELECT COUNT(*)` probe before attempting any `UPDATE` on expired rows; `inspect_mesh_state` is strictly read-only.
113
+
114
+ ### 5. Defense-in-Depth Path Security
115
+ - Enforces path containment within `workspace_root` and blocks sensitive file patterns (`.env*`, `*.pem`, `*.key`, `id_rsa*`, `*credentials*`, `*secret*`, `.npmrc`, `.git/config`) on both `dispatch` and `worker` verification paths.
116
+ - **Path Resolution**: `workspace_root` is strongly recommended to be passed as an absolute path. If a relative path is provided, it is deterministically resolved against `process.cwd()` via `path.resolve`.
117
+
118
+ ### 6. Threat Model & Security Assumptions (Local Workstation IPC)
119
+ - **Assumed Trust Environment**: FerryCat is intentionally designed as a **Trusted Local Workstation Bus** bridging isolated IDE hosts operating under the **same OS user account**. Callers and workers share the local file system (`workspace_root`) and SQLite database (`~/.ferrycat/mesh.db`).
120
+ - **Defense Scope**: Path containment (`assertSafePointerPath`) and sensitive file pattern filters prevent unintentional workspace escapes, symlink traversals, or accidental leakage of credentials/secret files during handoffs.
121
+ - **Out of Scope (Non-Goals)**: FerryCat does **not** provide adversarial sandboxing, multi-tenant isolation, or network authorization against malicious local processes or adversarial agents spoofing `AGENT_NAME`.
122
+
123
+ ---
124
+
125
+ ## Applicability Boundaries (When NOT to Use FerryCat)
126
+
127
+ 1. **Disjoint File Systems**: FerryCat offloads context by file reference; both hosts must share access to the same local `workspace_root`.
128
+ 2. **Trivial Single-Turn Queries**: Each cross-host handoff involves 3 explicit human window switches (~15–25s overhead).
129
+ 3. **Unattended Background Pipelines**: Desktop IDE hosts follow a request-response model and cannot be woken up asynchronously by an idle MCP server. For unattended multi-step pipelines, use a standalone DAG workflow engine such as [PatchCat](https://github.com/GuoBug/ai-prompt-orchestrator).
130
+
131
+ ---
132
+
133
+ ## Quickstart
134
+
135
+ ### 1. Build & Verify
136
+ ```bash
137
+ git clone https://github.com/GuoBug/FerryCat.git
138
+ cd FerryCat
139
+ npm install
140
+ npm run build
141
+ npm test
142
+ ```
143
+
144
+ ### 2. Mount in Antigravity
145
+ Edit `%USERPROFILE%\.gemini\antigravity\mcp_config.json` (or `~/.gemini/antigravity/mcp_config.json` on macOS/Linux):
146
+ ```json
147
+ {
148
+ "mcpServers": {
149
+ "ferrycat": {
150
+ "command": "node",
151
+ "args": [
152
+ "--experimental-sqlite",
153
+ "F:/git/FerryCat/dist/index.js"
154
+ ],
155
+ "env": {
156
+ "AGENT_NAME": "antigravity"
157
+ }
158
+ }
159
+ }
160
+ }
161
+ ```
162
+
163
+ ### 3. Mount in WorkBuddy / Claude Code / Cursor
164
+ Configure the target host's MCP settings with its respective `AGENT_NAME`:
165
+ ```json
166
+ {
167
+ "mcpServers": {
168
+ "ferrycat": {
169
+ "command": "node",
170
+ "args": [
171
+ "--experimental-sqlite",
172
+ "F:/git/FerryCat/dist/index.js"
173
+ ],
174
+ "env": {
175
+ "AGENT_NAME": "workbuddy"
176
+ }
177
+ }
178
+ }
179
+ }
180
+ ```
181
+ > **Note**: Both MCP Stdio and direct CLI invocation (`ferrycat-cli <dispatch|poll|verify|fulfill|release|extend|fail|inspect|worker>`) share the same SQLite WAL database at `~/.ferrycat/mesh.db` and the same Zod validation schemas. CLI supports `--help` tree, stdin payloads (`--file -` or `-`), `verify` pre-flight check, `inspect` ASCII table formatting (`--target <agent>`), `worker --mode=watch|claim|exec` and direct script execution via `--exec-file <script>`. In `worker --exec` mode, a non-zero exit of the child command closes the contract as `FAILED` instead of leaving it `CLAIMED` until the TTL sweep.
182
+
183
+ ---
184
+
185
+ ## Exposed MCP Tools
186
+
187
+ | Tool Name | Role | Transactional Behavior |
188
+ | :--- | :--- | :--- |
189
+ | **`dispatch_contract`** | Requester | Enforces path blacklist $\to$ computes full-file SHA-256 & line bounds $\to$ inserts `SUBMITTED`. |
190
+ | **`poll_next_task`** | Worker | Sweeps expired TTLs (read-before-write) $\to$ claims via `changes === 1` (`CLAIMED`) $\to$ verifies disk SHA-256 (or marks `POINTER_STALE`). |
191
+ | **`fulfill_contract`** | Worker | Re-verifies SHA-256 (TOCTOU guard) $\to$ validates Ajv JSON Schema ($\le 2$ retries via `RETRY_NEEDED`, 3rd marks `VALIDATION_FAILED`) $\to$ marks `COMPLETED`. |
192
+ | **`release_contract`** | Worker (holder) | Hands a `CLAIMED` contract back to the queue tail as `SUBMITTED`. `retry_count` is preserved (contract-level circuit breaker); `extend_count` is reset. Max 3 releases; refused once past the absolute lifetime deadline. |
193
+ | **`extend_ttl`** | Worker (holder) | Heartbeat: restarts the execution window (`claimed_at = now`), max 5 renewals and bounded total lifetime. |
194
+ | **`fail_contract`** | Worker (holder) | Marks a `CLAIMED` contract terminal `FAILED` with a mandatory `reason`. |
195
+ | **`verify_contract`** | Worker / Any (Read-Only) | Pre-flight check: validates on-disk pointer files against the baseline SHA-256 without mutating contract state (ADR-0004). |
196
+ | **`inspect_mesh_state`** | Any (Read-Only) | Pure read-only query of contract states, outputs, and dynamic `is_logically_expired` flags. |
197
+
198
+ > 💡 **Architecture Decision Records & Engineering Verification**:
199
+ > - [ADR-0001: Cross-Host SQLite WAL Bus & Human-Scheduled Poll Model](docs/adr/0001-cross-host-sqlite-wal-and-poll-model.md) (Why `node:sqlite` WAL + `poll_next_task` instead of 60s Blocking Hold or single-connection MCP Tasks)
200
+ > - [ADR-0002: Full-File SHA-256 Fingerprinting, TOCTOU Guard & Cross-Model Audit](docs/adr/0002-fingerprint-toctou-and-cross-model-audit.md) (Stale-context prevention and real-world cross-host verification records)
201
+ > - [ADR-0003: GUI Sandbox vs CLI Process Hybrid Layering Architecture & Wake-Up Boundaries](docs/adr/0003-gui-cli-hybrid-architecture.md) (Desktop HITL approval vs in-house CLI dual-channel autonomy)
202
+ > - [ADR-0004: Separation of Review and Mutation (SRM), TOCTOU Soft-Failure Buffering & Immutable Baseline Protocol](docs/adr/0004-review-mutation-separation-and-toctou-buffer.md) (How to prevent review abortion from concurrent or well-intended reviewer edits)
203
+ > - [FerryCat Requirements Backlog & Roadmap](docs/roadmap.md)
204
+
205
+ ---
206
+
207
+ > **About the Author**
208
+ > **Guo Qiang (GuoBug)**, Product Engineer specializing in platform engineering and growth. Currently focused on AI workflow orchestration, DAG state machines, and deterministic system architectures.
209
+ > Open Source & Homepage: [https://github.com/GuoBug](https://github.com/GuoBug) · [https://guobug.github.io](https://guobug.github.io)
210
+ > Inquiries and discussions on workflow engine architecture, topological scheduling, and developer experience are warmly welcomed.
package/README_zh.md ADDED
@@ -0,0 +1,212 @@
1
+ # FerryCat: 契约驱动的跨宿主 AI Agent 交接与评审总线
2
+
3
+ <p align="center">
4
+ <a href="README.md">English</a> •
5
+ <b>中文</b>
6
+ </p>
7
+
8
+ [![Node.js Version](https://img.shields.io/badge/Node.js-22.5%2B-339933?style=flat&logo=nodedotjs)](https://nodejs.org/)
9
+ [![Native Addons](https://img.shields.io/badge/Native_Addons-0_(node%3Asqlite)-success?style=flat)]()
10
+ [![Protocol](https://img.shields.io/badge/Protocol-MCP_Stdio-orange?style=flat)](https://modelcontextprotocol.io/)
11
+ [![Glama](https://img.shields.io/badge/Glama-MCP_Server-18181b?style=flat)](https://glama.ai/mcp/servers/GuoBug/FerryCat)
12
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
13
+
14
+ > **FerryCat** (`ferrycat`) 是一个基于 Model Context Protocol (MCP) 与 Node.js 内置 `node:sqlite`(WAL 并发模式)构建的轻量级、确定性跨宿主 AI Agent 工单交接与交叉评审总线。
15
+ > 专为解决单机桌面环境下不同 IDE 宿主(如 Google Antigravity、WorkBuddy、Claude Code、Cursor)因进程物理隔离导致的上下文割裂、未落盘代码静默盲审与非结构化交付痛点而设计。
16
+
17
+ <p align="center">
18
+ <img src="https://guobug.github.io/assets/images/flowchart-ferrycat-cross-host-demand-and-workflow.svg" alt="FerryCat 跨宿主契约摆渡全景" width="960" />
19
+ </p>
20
+
21
+ > 📖 **深度长文**:[为什么我开始做 FerryCat:当你的电脑上同时开着两个 AI 编程软件](https://guobug.github.io/posts/2026/10/22/why-we-built-ferrycat-cross-host-agent-review-bus/)
22
+
23
+ ---
24
+
25
+ ## 核心痛点与工程解法
26
+
27
+ MCP 官方的 `Tasks` 规范(2025-11 / 2026-07)解决的是**单条 `Client ↔ Server` 连接内部**的长耗时任务状态跟踪,但无法解决桌面端跨宿主协同的四个物理难题:
28
+ 1. **`stdio` 子进程内存隔离**:各 IDE 宿主分别拉起独立的 MCP 子进程,内存空间与注入的 Node.js 运行时完全隔离;
29
+ 2. **陈旧代码静默盲审(Stale Context)**:宿主 A 修改代码尚未保存或切换了分支,宿主 B 在旧文件上输出看似专业实则失真的审查结论;
30
+ 3. **跨窗口全文粘贴导致 Token 膨胀**:将数百行源码塞入两侧对话上下文,造成重复的 Token 消耗与截断失真;
31
+ 4. **自由文本交付缺乏结构化约束**:缺乏强 Schema 校验与重试上限控制,容易引发模型无限重试。
32
+
33
+ **FerryCat 采用“人类担任调度器,协议保障不丢、不重、不盲审、结构化交付”的 SQLite WAL 总线架构化解上述问题:**
34
+
35
+ ```
36
+ [开发者] [宿主 A: Antigravity] [~/.ferrycat/mesh.db] [宿主 B: WorkBuddy]
37
+ | | | |
38
+ |-- 1. "派发代码审查工单" ----->| | |
39
+ | |-- dispatch_contract() -------->| 写入 status = SUBMITTED |
40
+ | | (全文件 SHA-256 + 首末行快照) | 记录文件 size / sha256 |
41
+ | | | |
42
+ |-- 2. 切窗口:"拉取待办工单" ---|--------------------------------|------------------------------>|
43
+ | | |<-- poll_next_task() ----------|
44
+ | | | (原子抢占 changes===1 |
45
+ | | | + 校验磁盘文件 SHA-256) |
46
+ | | | |-- 就地读取本地文件
47
+ | | |<-- fulfill_contract() --------|-- 二次校验 SHA-256
48
+ | | | (校验 Ajv JSON Schema | 并提交结构化结果
49
+ | | | -> status = COMPLETED) |
50
+ | | | |
51
+ |-- 3. 切回:"查看审查结论" ---->| | |
52
+ | |-- inspect_mesh_state() ------->| 纯只读查询 COMPLETED 结论 |
53
+ |<-- 汇总展示并决定是否合入 ----| | |
54
+ ```
55
+
56
+ ---
57
+
58
+ ## 五大核心架构支柱
59
+
60
+ ### 1. 确定性 7 态状态机 (Deterministic 7-State Machine)
61
+ 每个状态跃迁均绑定独立的 SQL 事务与乐观锁校验(`changes === 1`),杜绝悬空状态与并发重复领单:
62
+
63
+ ```
64
+ dispatch_contract()
65
+ │
66
+ ▼
67
+ ┌───────────┐
68
+ │ SUBMITTED │ (排队期 TTL: 默认 1800s)
69
+ └─────┬─────┘
70
+ │
71
+ poll_next_task()
72
+ ┌────────────────┼────────────────┐
73
+ │ (排队超时) │ (指纹匹配) │ (文件缺失 / 大小变动 / 全文 SHA-256 不一致)
74
+ ▼ ▼ ▼
75
+ ┌───────────┐ ┌───────────┐ ┌───────────────┐
76
+ │ TIMED_OUT │ │ CLAIMED │ │ POINTER_STALE │ (终态:拒绝在陈旧代码上盲审)
77
+ └───────────┘ └─────┬─────┘ └───────────────┘
78
+ ▲ │ (执行期 TTL: 默认 300s)
79
+ │ fulfill_contract()
80
+ │ ┌────────────────┼────────────────┬────────────────┐
81
+ └──┤ (执行超时) │ (Schema 通过) │ (重试 > 2 次) │ (审查期间文件被篡改)
82
+ │ ▼ ▼ ▼
83
+ │ ┌───────────┐ ┌───────────────────┐ ┌───────────────┐
84
+ └──────────│ COMPLETED │ │ VALIDATION_FAILED │ │ POINTER_STALE │
85
+ └───────────┘ └───────────────────┘ └───────────────┘
86
+ ```
87
+
88
+ 持单方对 `CLAIMED` 工单的生命周期控制(均要求 `agent_id === target_agent`):
89
+
90
+ | 调用 | 状态流转 | 护栏 |
91
+ | :--- | :--- | :--- |
92
+ | `release_contract` | `CLAIMED → SUBMITTED`(回到队尾,排队窗口重新计时) | 清零 `extend_count`(续期额度属于当前持有者),但**保留** `retry_count`:Schema 失败熔断是工单级配额,退单不能洗掉已消耗的重试预算。超出绝对生命期上限时拒绝。单张工单最多退回 3 次。 |
93
+ | `extend_ttl` | `CLAIMED → CLAIMED`(`claimed_at = now`) | 必须在当前执行窗口内调用;每位持单者最多续期 5 次;`now ≥ created_at + 4 × queue_ttl_sec` 后拒绝续期。 |
94
+ | `fail_contract` | `CLAIMED → FAILED`(终态) | 必须提供 `reason`,落库为 `WORKER_FAILED: <reason>`;若窗口已过期才上报,则置为 `TIMED_OUT` 并保留失败原因。 |
95
+
96
+ ### 2. 全文件 SHA-256 指纹校验、预检与 TOCTOU 软失败缓冲 (ADR-0004)
97
+ - **派单快照**:`dispatch_contract` 读取磁盘文件,记录字节数 `size`、全文件 `sha256`(64 位 hex)以及非空首末行 `first_line` / `last_line`。若文件在最近 3 秒内刚被修改,返回 `warning` 提醒确认 IDE 缓冲区已落盘。
98
+ - **领单核验**:`poll_next_task` 严格按 `存在性 -> 字节数 size -> 全文件 sha256` 顺序核验(绝不以 `mtime` 单独判死刑,避免 IDE 无变更保存导致误杀)。若不一致,等待 `150ms` 重试一次排除瞬态写入,仍不一致则直接置为终态 `POINTER_STALE`。
99
+ - **只读预检 (`verify_contract`)**:纯只读命令,允许 Worker 或外部脚本在交单前核验指针健康度(返回 `valid: true/false` 与脏文件清单),不消耗工单状态。
100
+ - **TOCTOU 软失败缓冲**:`fulfill_contract` 在交单时二次核验全文件指纹。若检测到脏指针:
101
+ - **第 1 次超标(Strike 1)**:返回 `RETRY_NEEDED`,工单保持 `CLAIMED` 并自动续期执行窗口,返回脏文件清单与抢救指引(`git stash` / `git checkout -- <file>`)。
102
+ - **第 2 次超标(Strike 2)**:流转至终态 `POINTER_STALE`,但将已提交的审查结果完整保存在 `salvaged_result_json`,确保长文本 Token 产出绝不因指针陈旧而白费。
103
+ - **TOCTOU 竞态收窄**:任何基于文件系统的协作协议都无法从物理上完全消除磁盘读取与数据库行提交之间的微秒级理论时间差;FerryCat 通过领单(`poll`)与交单(`fulfill`)两阶段双重 SHA-256 复核,将传统长达数分钟的盲审窗口极限收窄至 SQL 原子事务提交前的微秒/纳秒级间隙。
104
+
105
+ ### 3. 单方向交付与有界 Schema 重试 (`RETRY_NEEDED` $\le 2$)
106
+ - 使用带缓存的 Ajv 校验器验证 `structured_output`,并将校验错误压平为单行路径列表(如 `["/passed: must be boolean"]`)。
107
+ - 前 2 次校验失败返回 `RETRY_NEEDED` 供模型当轮修正(每次驳回同时重置执行窗口,修正过程不会被 TTL 饿死);第 3 次失败直接流转至终态 `VALIDATION_FAILED`,防止模型陷入无限重试消耗 Token。
108
+
109
+ ### 4. 两阶段独立 TTL 与先读后写惰性清理
110
+ - 排队期 `queue_ttl_sec`(默认 1800s)与执行期 `exec_ttl_sec`(从 `claimed_at` 起算)分离,防止排队 29 分钟的任务刚被认领 1 分钟就被误判超时。
111
+ - `exec_ttl_sec` 按 `task_type` 分级默认:`code_review` / `architecture_audit` → 1800s(深度思考或人在回路),`implementation` / `test_generation` → 300s;派单时显式传入的 `exec_ttl_sec` 优先。
112
+ - `poll_next_task` 先执行无写锁的 `SELECT COUNT(*)` 探测过期项,仅当存在过期工单时才执行 `UPDATE`;`inspect_mesh_state` 保持纯只读,零写锁竞争。
113
+
114
+ ### 5. 双端纵深路径安全防线
115
+ - 在派单(`dispatch`)与领单/交单(`worker`)两侧同时强制执行工作区目录包含校验(阻断 `..` 路径逃逸)与敏感文件黑名单拦截(`.env*`、`*.pem`、`*.key`、`id_rsa*`、`*credentials*`、`*secret*`、`.npmrc`、`.git/config`)。
116
+ - **工作区路径解析**:建议显式传入 `workspace_root` 绝对路径。若传入相对路径,系统将自动基于当前工作目录(`process.cwd()`)通过 `path.resolve` 规范化为确定性绝对路径。
117
+
118
+ ### 6. 威胁模型与安全边界声明 (同机同用户受信 IPC)
119
+ - **环境信任假设**:FerryCat 定位为**面向同机同操作系统用户的跨宿主 AI 开发者协作总线**。所有 IDE 宿主与 Worker 均运行在同一本地用户权限下,共享本地工作区(`workspace_root`)与本地 SQLite 文件(`~/.ferrycat/mesh.db`)。
120
+ - **防护范畴**:双端路径防逃逸(`assertSafePointerPath`)与敏感文件模式黑名单旨在防范跨宿主误触敏感配置(如 `.env`、SSH 密钥)、软链接越界与无意识的目录逃逸。
121
+ - **非防护目标**:FerryCat **不作为**面向不受信外部网络、多租户强隔离或恶意本地进程伪造 `AGENT_NAME` 的零信任安全网关。
122
+
123
+ ---
124
+
125
+ ## 适用边界(什么情况下不该用 FerryCat)
126
+
127
+ 1. **两端不共享同一本地文件系统**:FerryCat 依赖按引用传递文件指针(Context Offloading by Reference),要求双方能访问同一份 `workspace_root`。
128
+ 2. **单轮简单问答即可解决的任务**:每次跨宿主交接需要 3 次人工窗口切换(约 15~25 秒操作开销),简单问题直接在单个 IDE 内完成即可。
129
+ 3. **需要无人值守全自动流水线的场景**:桌面 IDE 宿主受限于请求-响应模型,无法在空闲时被 MCP 服务端主动推流唤醒;若需无人值守编排,请使用独立的 DAG 状态机工作流引擎(如 [PatchCat](https://github.com/GuoBug/ai-prompt-orchestrator))。
130
+
131
+ ---
132
+
133
+ ## 快速上手
134
+
135
+ ### 1. 安装与编译测试
136
+ ```bash
137
+ git clone https://github.com/GuoBug/FerryCat.git
138
+ cd FerryCat
139
+ npm install
140
+ npm run build
141
+ npm test
142
+ ```
143
+
144
+ > **关于 `--experimental-sqlite`**:启动参数中恒定携带 `--experimental-sqlite`,以同时兼容 Node 22.12 LTS(需 flag)与 Node 22.22+ / Node 24+(原生免 flag,携带亦不报错),实现零原生 C++ 编译依赖。
145
+
146
+ ### 2. Antigravity 侧挂载
147
+ 编辑 `%USERPROFILE%\.gemini\antigravity\mcp_config.json`(macOS/Linux 为 `~/.gemini/antigravity/mcp_config.json`):
148
+ ```json
149
+ {
150
+ "mcpServers": {
151
+ "ferrycat": {
152
+ "command": "node",
153
+ "args": [
154
+ "--experimental-sqlite",
155
+ "F:/git/FerryCat/dist/index.js"
156
+ ],
157
+ "env": {
158
+ "AGENT_NAME": "antigravity"
159
+ }
160
+ }
161
+ }
162
+ }
163
+ ```
164
+
165
+ ### 3. WorkBuddy / Claude Code / Cursor 侧挂载
166
+ 在目标宿主的 MCP 配置文件中添加对应 `AGENT_NAME`:
167
+ ```json
168
+ {
169
+ "mcpServers": {
170
+ "ferrycat": {
171
+ "command": "node",
172
+ "args": [
173
+ "--experimental-sqlite",
174
+ "F:/git/FerryCat/dist/index.js"
175
+ ],
176
+ "env": {
177
+ "AGENT_NAME": "workbuddy"
178
+ }
179
+ }
180
+ }
181
+ }
182
+ ```
183
+ > **注**:MCP `stdio` 模式与 CLI 直接调用模式(`ferrycat-cli <dispatch|poll|verify|fulfill|release|extend|fail|inspect|worker>`)共享同一份 `~/.ferrycat/mesh.db` 数据库与同一套 Zod 参数校验契约。CLI 提供全量 `--help` 树、stdin 管道输入(`--file -` 或 `-`)、`verify` 只读预检、`inspect` ASCII 表格输出(支持 `--target <agent>` 过滤)、`worker --mode=watch|claim|exec` 以及 `--exec-file <script>` 无转义直接执行脚本。在 `worker --exec` 模式下子进程非零退出时,工单会被直接置为 `FAILED`,不再挂在 `CLAIMED` 等超时。
184
+
185
+ ---
186
+
187
+ ## 暴露的 8 个 MCP 工具契约
188
+
189
+ | 工具名 | 调用角色 | 职责与事务行为 |
190
+ | :--- | :--- | :--- |
191
+ | **`dispatch_contract`** | 发起方 (Requester) | 校验路径黑名单 $\to$ 计算全文件 SHA-256 与首末行快照 $\to$ 写入 `SUBMITTED`。 |
192
+ | **`poll_next_task`** | 接收方 (Worker) | 先读后写清理超时工单 $\to$ `changes === 1` 原子抢占为 `CLAIMED` $\to$ 校验文件指纹(不匹配则置为 `POINTER_STALE`)。 |
193
+ | **`verify_contract`** | 接收方 / 任意方 (Read-Only) | 只读预检:校验磁盘文件指纹与基线是否一致,不变更任何状态(ADR-0004)。 |
194
+ | **`fulfill_contract`** | 接收方 (Worker) | 二次复核文件指纹(TOCTOU 软失败缓冲:Strike 1 返回 `RETRY_NEEDED`,Strike 2 置为 `POINTER_STALE` 并在 `salvaged_result` 抢救产出)$\to$ 校验 Ajv JSON Schema $\to$ 通过则置为 `COMPLETED`。 |
195
+ | **`release_contract`** | 持单方 (Worker) | 将 `CLAIMED` 工单退回队尾重新置为 `SUBMITTED`。保留 `retry_count`(工单级熔断配额),清零 `extend_count`。最多退回 3 次;超出绝对生命期上限时拒绝。 |
196
+ | **`extend_ttl`** | 持单方 (Worker) | 心跳续期:重置执行窗口(`claimed_at = now`),最多 5 次,总生命周期有上限。 |
197
+ | **`fail_contract`** | 持单方 (Worker) | 携带必填 `reason` 将 `CLAIMED` 工单置为终态 `FAILED`。 |
198
+ | **`inspect_mesh_state`** | 任意方 (Read-Only) | 纯只读查询工单状态、结构化产出与错误原因,动态计算 `is_logically_expired`。 |
199
+
200
+ > 💡 **架构决策记录 (ADR) 与工程验证沉淀**:
201
+ > - [ADR-0001: 采用跨宿主 SQLite WAL 总线与人工调度即时轮询模型](docs/adr/0001-cross-host-sqlite-wal-and-poll-model_zh.md)(为什么采用 `node:sqlite` WAL + `poll_next_task`,而非 60s 长轮询挂起或单连接 MCP Tasks)
202
+ > - [ADR-0002: 全文件 SHA-256 指纹防盲审、TOCTOU 闭合与跨宿主多模型实测记录](docs/adr/0002-fingerprint-toctou-and-cross-model-audit_zh.md)(指针陈旧防护设计与 3 模型真实互审复盘记录)
203
+ > - [ADR-0003: GUI 沙箱与 CLI 进程混合分层架构、唤醒边界与双通道 Worker 选型](docs/adr/0003-gui-cli-hybrid-architecture_zh.md)(桌面人在回路审批闸 vs 自研 CLI 双通道自治)
204
+ > - [ADR-0004: 审议分离协议、TOCTOU 软失败缓冲与审查期不可变基线规范](docs/adr/0004-review-mutation-separation-and-toctou-buffer_zh.md)(如何彻底规避审查者“边审边修”或并发写入引发的工单报废)
205
+ > - [FerryCat 演进路线图 (Roadmap)](docs/roadmap_zh.md)
206
+
207
+ ---
208
+
209
+ > **关于作者**
210
+ > **郭强 (GuoBug)**,Product Engineer,做平台工程也做业务增长。目前主要在折腾 AI 工作流编排、DAG 状态机与确定性系统架构。
211
+ > 开源项目与主页:[https://github.com/GuoBug](https://github.com/GuoBug) · [https://guobug.github.io](https://guobug.github.io)
212
+ > 欢迎就工作流引擎架构、拓扑调度和低门槛开发体验交流指教。
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'node:child_process';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
4
+
5
+ const [major, minor] = process.versions.node.split('.').map(Number);
6
+ if (major < 22 || (major === 22 && minor < 5)) {
7
+ console.error(
8
+ `FerryCat requires Node.js >= 22.5.0 (current: ${process.version}). Please upgrade Node.js.`
9
+ );
10
+ process.exit(1);
11
+ }
12
+
13
+ const targetPath = fileURLToPath(new URL('../dist/cli.js', import.meta.url));
14
+ const needsFlag = major === 22 && !process.execArgv.includes('--experimental-sqlite');
15
+
16
+ if (needsFlag) {
17
+ const childEnv = { ...process.env };
18
+ if (!childEnv.FERRYCAT_SHOW_NODE_WARNINGS) {
19
+ childEnv.NODE_NO_WARNINGS = '1';
20
+ }
21
+ const child = spawn(
22
+ process.execPath,
23
+ ['--experimental-sqlite', ...process.execArgv, targetPath, ...process.argv.slice(2)],
24
+ { stdio: 'inherit', env: childEnv }
25
+ );
26
+
27
+ child.on('exit', (code, signal) => {
28
+ if (signal) {
29
+ process.kill(process.pid, signal);
30
+ } else {
31
+ process.exit(code ?? 0);
32
+ }
33
+ });
34
+ } else {
35
+ await import(pathToFileURL(targetPath).href);
36
+ }
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'node:child_process';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
4
+
5
+ const [major, minor] = process.versions.node.split('.').map(Number);
6
+ if (major < 22 || (major === 22 && minor < 5)) {
7
+ console.error(
8
+ `FerryCat requires Node.js >= 22.5.0 (current: ${process.version}). Please upgrade Node.js.`
9
+ );
10
+ process.exit(1);
11
+ }
12
+
13
+ const targetPath = fileURLToPath(new URL('../dist/index.js', import.meta.url));
14
+ const needsFlag = major === 22 && !process.execArgv.includes('--experimental-sqlite');
15
+
16
+ if (needsFlag) {
17
+ const childEnv = { ...process.env };
18
+ if (!childEnv.FERRYCAT_SHOW_NODE_WARNINGS) {
19
+ childEnv.NODE_NO_WARNINGS = '1';
20
+ }
21
+ const child = spawn(
22
+ process.execPath,
23
+ ['--experimental-sqlite', ...process.execArgv, targetPath, ...process.argv.slice(2)],
24
+ { stdio: 'inherit', env: childEnv }
25
+ );
26
+
27
+ child.on('exit', (code, signal) => {
28
+ if (signal) {
29
+ process.kill(process.pid, signal);
30
+ } else {
31
+ process.exit(code ?? 0);
32
+ }
33
+ });
34
+ } else {
35
+ await import(pathToFileURL(targetPath).href);
36
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};