n2n-nexus 0.9.2 → 0.9.4

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/CHANGELOG.md CHANGED
@@ -1,7 +1,52 @@
1
1
  # Changelog
2
2
 
3
+ [中文版](docs/CHANGELOG_zh.md)
4
+
3
5
  All notable changes to this project will be documented in this file.
4
6
 
7
+ ## [0.9.4] - 2026-10-26
8
+
9
+ ### Changed
10
+
11
+ - Contract version 2: the npm client and the host must be upgraded together (`n2n-nexus doctor` reports a mismatch).
12
+ - Chat and meetings identify the conversation, not the MCP adapter instance, when the n2n-nexus hooks are installed. Several conversations of one IDE in one workspace share an instance; they now keep their own unread cursors, and their messages, a meeting's `initiator` and `participants` and the `actor` of `meeting.*` events carry their session id (`claude:<id>` / `codex:<id>`), which works with `send`. Clients without the hooks keep using the instance. If an instance has had a conversation identified within the last 30 minutes and a call of it cannot be identified, the call fails with a retry message instead of reading through the shared cursor. Results say which identity was used (`identity`).
13
+ - `read_messages` no longer returns your own messages in unread mode; a conversation's first unread read of global chat starts at the newest message. New history mode: `latest: true` or `afterId` read without moving a cursor and include your own messages. Each message has `senderName`, and `category` is always present (null without one).
14
+ - Meeting messages are numbered per meeting, from 1: `id` in `read_messages`, `messageId` from `send_message` (now returned for meetings too) and `afterId` in a meeting use that number, so a new meeting no longer continues the numbers of earlier ones.
15
+
16
+ - Meetings have members. `join_meeting(meetingId, mode?, approver?, since?)` and `leave_meeting(meetingId)`; the initiator joins its meeting. News of a meeting reaches its members through the hooks as one line per meeting: active members are woken (or their turn continues), async members see it at their next prompt or tool call, and nobody is told about their own messages. Meeting news is not an automatic turn and still arrives past `maxAutoTurns`, so a meeting runs until it is ended; ending it gives each other member one notice. `list_meetings` shows the members and how many wakes the meeting caused.
17
+ - Decisions by consensus. `send_message(..., draft: true)` publishes a meeting's current draft and fixes its required approvers (members that joined as `required` through the hooks); `stance(meetingId, messageId, endorse | object | withdraw, note?)` takes a stance on it, an objection with its reason. When at least two required approvers endorsed the current draft and nobody objects, the meeting closes by itself with the draft as a `DECISION` (`endReason: consensus`; `end_meeting` records `manual`). Ordinary proposals do not reset stances; a leaving or re-joining approver changes the list, which is posted to the meeting. Required approvers that have not taken a stance after a quiet `stanceReminderMinutes` (new `settings.json` field, default 10) are reminded once. `list_meetings` shows each meeting's `endReason` and current draft with its stances.
18
+ - Direct messages say what they ask: `send` and `reply` take `expectsReply` (false: the notification says no reply is needed and does not ask for one) and `wake` (false: the message never wakes the recipient or holds its turn, and waits for its next prompt or tool call). `read` returns `expectsReply`.
19
+ - Sessions have a readable `title`: the conversation's title as Claude Code or Codex shows it, read by the hooks and shown next to the name in `peers`, introductions, notifications and meeting members. `set_profile({ title })` sets your own (`""` returns to the client's). The name stays the handle for `send`.
20
+ - A direct message is listed in full once; while it stays unread, later notifications only count it.
21
+
22
+ ### Fixed
23
+
24
+ - A failed `codex queue` no longer loses the wake: the Codex background wait reports the result and the host undoes a wake that was not delivered, and the wait tries again after a delay (1 s, doubling up to 5 minutes), so the same messages and notices wake the conversation again; the failed wake is not counted as an automatic turn. The session no longer stays `working` until it goes stale.
25
+ - A session that goes offline after 30 minutes without activity now tells its `notifyWhenIdle` subscribers, as a session that ends does, also when they only wait in the background.
26
+ - The prompt that starts a woken turn no longer announces again what the wake announced.
27
+ - The Codex SessionEnd hook fits Codex's 3-second limit: `n2n-nexus install codex` writes timeout 3 (run it again to update an existing installation, which removes Codex's "clamping SessionEnd hook timeout" warning), and the SessionEnd request gives up after 1.5 s without a second host lookup.
28
+ - `n2n-nexus uninstall <client>` no longer needs the client on PATH: it removes the hooks from the client's config file anyway. Without the client it skips `<client> mcp remove` and, when the MCP server is still registered, says so and how to remove it (run that command later, or delete the `n2n-nexus` entry from `~/.claude.json` / `config.toml`). `install` still requires the client.
29
+ - Tool calls are rate limited per conversation when the hooks identify it, so conversations that share an adapter instance (one client in one directory) no longer use up each other's budget; without the hooks calls are still limited per instance. The per-address limit is unchanged.
30
+
31
+ ## [0.9.3] - 2026-10-24
32
+
33
+ ### Changed
34
+
35
+ - The MCP server updates itself: `n2n-nexus install` registers it as `"<node>" "<npx>" -y n2n-nexus@latest mcp` (npm's npx next to the chosen Node; `PATH` set to a folder whose `node` is that Node, then the system folders), so every new conversation starts the latest published version. The hooks still run the global installation; update them with `npm i -g n2n-nexus@latest`. Run `n2n-nexus install claude` / `n2n-nexus install codex` once to switch an existing installation; the hook commands stay the same, so Codex does not ask to trust them again. `n2n-nexus doctor` checks the new form and reports an MCP command in the old one.
36
+
37
+ - n2n-nexus is for sessions giving each other work: direct message notices, the `read` tool and the assistant guide now tell the receiving model to carry out what a message asks and answer with `reply`, instead of only when its own user asked for it.
38
+ - No limit on automatic turns by default: `maxAutoTurns` in the host's `settings.json` now defaults to `0`, so sessions keep waking each other and carrying out each other's requests without anyone typing. Set a number to cap it again; a `settings.json` that sets `maxAutoTurns` keeps its value.
39
+ - Any MCP adapter instance can end, archive or reopen a meeting, not only the one that started it.
40
+
41
+ ### Fixed
42
+
43
+ - The Codex background wait loads what a wake needs before it starts waiting, so an upgrade of the package (or a cleared npx cache) during the wait no longer makes the wake fail silently.
44
+
45
+ ### Documentation
46
+
47
+ - An installation guide (`docs/INSTALL.md`) covers installing, upgrading and removing the host and the client, including the one-time Codex hook trust; a user guide (`docs/USER_GUIDE.md`) covers using them and holds the command, environment variable, `settings.json` and data directory reference. The README now gives an overview and links to them; each document covers its own topic instead of repeating the others.
48
+ - Every document has a Chinese version with the same sections: new Chinese installation guide, user guide, tools reference, host README, contributing and security docs, and the Chinese changelog now lists every version.
49
+
5
50
  ## [0.9.2] - 2026-10-24
6
51
 
7
52
  ### Fixed
package/CONTRIBUTING.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Contributing
2
2
 
3
+ [中文版](docs/CONTRIBUTING_zh.md)
4
+
3
5
  Thanks for helping improve n2n-nexus.
4
6
 
5
7
  ## Development Setup
@@ -42,7 +44,7 @@ NEXUS_ENDPOINT=http://127.0.0.1:5799 node build/index.js mcp
42
44
 
43
45
  ## Architecture Notes
44
46
 
45
- The host (`n2n-nexus-host`, Go, `host/`) is the single source of truth. The MCP adapter (`n2n-nexus`, TypeScript, `src/`) is a stateless proxy — it has no tool definitions and no local data. Tool definitions live in `contract/tools.json`, their handlers in `host/internal/core` and `host/internal/agents`. See `docs/ARCHITECTURE.md` for the full system design and `host/README.md` for building and releasing the host.
47
+ See `docs/ARCHITECTURE.md` for the system design and `host/README.md` for building and releasing the host.
46
48
 
47
49
  ## Changelog
48
50
 
package/README.md CHANGED
@@ -11,6 +11,7 @@ Local-first MCP coordination hub from N2NS Lab for multi-AI assistant collaborat
11
11
  [![license](https://img.shields.io/github/license/n2ns/n2n-nexus)](https://github.com/n2ns/n2n-nexus/blob/main/LICENSE)
12
12
  [![MCP Protocol](https://img.shields.io/badge/MCP-Protocol-blue)](https://modelcontextprotocol.io)
13
13
  [![node version](https://img.shields.io/node/v/n2n-nexus)](https://nodejs.org)
14
+ [![DataFrog.io](https://datafrog.io/badges/datafrog.svg)](https://datafrog.io)
14
15
 
15
16
  [中文版](./docs/README_zh.md)
16
17
 
@@ -22,22 +23,44 @@ n2n-nexus is an open-source Model Context Protocol (MCP) coordination server for
22
23
 
23
24
  ## 📚 Contents
24
25
 
26
+ - [Looking for](#-looking-for)
25
27
  - [What is n2n-nexus?](#-what-is-n2n-nexus)
28
+ - [Why use it?](#-why-use-it)
26
29
  - [Architecture](#️-architecture)
27
30
  - [Quick start](#-quick-start)
28
31
  - [Toolset](#️-toolset)
29
- - [Data storage](#-data-storage)
30
- - [Project ID conventions](#️-project-id-conventions)
31
- - [CLI reference](#-cli-reference)
32
- - [Security and governance notes](#-security-and-governance-notes)
33
32
  - [Real-world example](#-real-world-example)
34
- - [Local development](#-local-development)
35
- - [Related docs](#-related-docs)
33
+ - [FAQ](#-faq)
34
+ - [Documentation](#-documentation)
35
+
36
+ ## 🔎 Looking for
37
+
38
+ If you are searching for:
39
+
40
+ - multi-AI assistant collaboration over MCP
41
+ - a local multi-agent collaboration server
42
+ - a local MCP coordination hub
43
+ - shared project context for AI coding assistants
44
+ - a cross-IDE MCP collaboration tool
45
+ - a project registry for AI assistants
46
+ - meetings and task coordination for coding agents
47
+
48
+ n2n-nexus is designed for these cases: shared host state, lightweight MCP adapters, project-aware collaboration, local storage and cross-environment IDE workflows.
36
49
 
37
50
  ## 💡 What is n2n-nexus?
38
51
 
39
52
  n2n-nexus gives AI assistants a shared coordination workspace. A long-running local host, `n2n-nexus-host`, owns project state, meetings, messages, tasks, shared docs, and project assets. Lightweight MCP adapters from the `n2n-nexus` npm package connect each IDE or assistant to the same host.
40
53
 
54
+ **Quick summary**
55
+
56
+ - **Install**: the host `n2n-nexus-host` with Scoop / Homebrew / a Releases archive; the client with `npm i -g n2n-nexus@latest`, then `n2n-nexus install <claude|codex>`
57
+ - **Protocol**: Model Context Protocol (MCP); the adapters reach the host over HTTP
58
+ - **Storage**: one SQLite database in the host's data directory
59
+ - **Good for**: multi-assistant coding sessions, project handoffs, shared decisions, meeting records, task tracking, project manifests
60
+ - **Not for**: cloud team chat, public project management SaaS, source code indexing, vector search or remote database hosting
61
+
62
+ ## 🎯 Why use it?
63
+
41
64
  - Coordinate multiple AI assistants working on the same project.
42
65
  - Share project manifests, decisions, and assets across IDEs and machines.
43
66
  - Keep decisions, proposals, and updates in a local meeting and message log.
@@ -67,94 +90,11 @@ n2n-nexus gives AI assistants a shared coordination workspace. A long-running lo
67
90
 
68
91
  ## 🚀 Quick start
69
92
 
70
- ### 1. Install the host
71
-
72
- | Platform | Install |
73
- | --- | --- |
74
- | Windows (Scoop) | `scoop bucket add n2ns https://github.com/n2ns/scoop-bucket`, then `scoop install n2ns/n2n-nexus-host` |
75
- | Windows (winget) | coming soon |
76
- | macOS (Homebrew) | `brew install n2ns/tap/n2n-nexus-host` |
77
- | Linux | download `n2n-nexus-host_<version>_linux_<amd64\|arm64>.tar.gz` from [Releases](https://github.com/n2ns/n2n-nexus/releases), extract it and put `n2n-nexus-host` on your `PATH` |
78
-
79
- [Releases](https://github.com/n2ns/n2n-nexus/releases) (tags `host-v<version>`) has the archives for every platform: `n2n-nexus-host_<version>_<os>_<arch>.zip` for Windows and `.tar.gz` for macOS and Linux, amd64 and arm64.
80
-
81
- - The binaries are not code-signed yet. On Windows, the first start of `n2n-nexus-host.exe` or `n2n-nexus-host-tray.exe` may show a SmartScreen warning: click **More info → Run anyway**. Homebrew builds the host from source on your Mac, so there is no Gatekeeper prompt.
82
- - On Windows the package has two programs: `n2n-nexus-host.exe` for commands, and `n2n-nexus-host-tray.exe`, the tray, which opens no console window. `n2n-nexus-host` without arguments starts the tray program and returns.
83
- - Run `n2n-nexus-host` without arguments to start it as a tray app. The tray menu shows the address it listens on and the number of online sessions, opens the log and data folders, switches LAN access on and off, restarts the host and turns **Start at login** on or off (`n2n-nexus-host autostart on|off` does the same). The first time the tray runs it turns **Start at login** on; after that it stays as you set it. The menu is in Chinese on a Chinese system and in English otherwise.
84
- - Or run it as a background service that starts at boot, without a tray: `n2n-nexus-host service install` from an elevated terminal (Windows) or with `sudo` (macOS, Linux). The service runs as the user who installed it. Quit the tray first: `service install` refuses while the tray runs the host. While the service runs, a tray started later only controls it.
85
- - On Linux the tray needs a desktop with StatusNotifierItem (AppIndicator) support; on a server, use `service install`.
86
-
87
- Verify a download against the release's `SHA256SUMS` and its build provenance:
88
-
89
- ```bash
90
- sha256sum -c SHA256SUMS --ignore-missing
91
- gh attestation verify n2n-nexus-host_<version>_<os>_<arch>.tar.gz --repo n2ns/n2n-nexus
92
- ```
93
-
94
- ### 2. Install the client and connect Claude Code or Codex
95
-
96
- Requires Node.js 22.14+ on the 22.x release line, or Node.js 23.6+.
97
-
98
- ```bash
99
- npm i -g n2n-nexus
100
- n2n-nexus install claude # ~/.claude/settings.json (or $CLAUDE_CONFIG_DIR) + claude mcp add --scope user
101
- n2n-nexus install codex # ~/.codex/hooks.json (or $CODEX_HOME) + codex mcp add
102
- n2n-nexus doctor # check the installed commands and that the host answers
103
- ```
104
-
105
- `install` writes the hooks and the MCP server into the client's user configuration.
106
-
107
- - Codex runs new hooks only after you trust them: open Codex and run `/hooks` once.
108
- - Pass `--endpoint <url>` to both commands when the host is not at `http://127.0.0.1:5688`, or `--endpoint discover` to find a host on the LAN.
109
- - On Windows, run `npm i -g` and `install` from a regular terminal, not from the terminal of a packaged app such as Codex: that terminal installs into the app's private copy of `%APPDATA%`, which hides the global install from the app.
110
- - The hooks and the MCP server command use an absolute Node path, because GUI clients started from the Dock (such as Claude Desktop) do not read your shell's `PATH`. By default `install` writes the `node` found on `PATH` when it is the same file as the Node running `install` (for example `/usr/local/bin/node`, `/opt/homebrew/bin/node`, `~/.local/bin/node` or a version manager's stable entry point), and otherwise the real path of the running Node. `install` prints the path it wrote as `Node: <path>`.
111
- - Pass `--node <path>` to choose the Node path yourself. It must exist, be executable and satisfy the `engines` range in package.json (`^22.14.0 || >=23.6.0`); otherwise `install` exits with code 1 and writes nothing.
112
- - After upgrading Node: if `install` warned that the Node path contains a version directory (Homebrew `Cellar`, nvm, fnm, Volta, asdf, `node-v<version>`), run `n2n-nexus install <claude|codex>` again after each upgrade, or pass `--node` with a stable path. `n2n-nexus doctor [claude|codex]` checks that the Node path and the entry script in the installed hook and MCP commands still exist and run, and that the host they talk to answers with the client's contract version; it exits with code 1 when a check fails.
113
- - `n2n-nexus uninstall <claude|codex>` removes the hooks and the MCP server that `install` added and leaves the rest of the configuration alone.
114
- - Supported on Linux, WSL and macOS; Codex hooks also support Windows PowerShell. Other MCP clients can use every tool except direct messages.
115
-
116
- ### Other MCP clients
117
-
118
- ```json
119
- {
120
- "mcpServers": {
121
- "n2n-nexus": {
122
- "command": "npx",
123
- "args": ["-y", "n2n-nexus", "mcp"],
124
- "env": {
125
- "NEXUS_ENDPOINT": "http://127.0.0.1:5688"
126
- }
127
- }
128
- }
129
- }
130
- ```
131
-
132
- The adapter can start before the host. It loads the host's tool list once the host becomes reachable. The adapter and the hooks never start a host themselves: when none answers, tool calls fail with a message saying how to start it (or, for a remote endpoint, that it must be started on that machine), and the hooks skip silently. A call the host does not answer in time fails on its own; the host may still complete it.
133
-
134
- ### Direct messages between Claude Code and Codex conversations
135
-
136
- Each conversation appears in `peers`. A new direct message is announced to the recipient automatically: when the user sends a prompt, after each tool call, and once before the turn ends. The recipient reads the text with `read`. An idle conversation is woken too: when a turn ends, a background hook waits up to an hour for anything new and then starts a turn with it (Claude Code through an `asyncRewake` hook, Codex through `codex queue`), so AI sessions can answer each other without anyone typing. An unattended `claude -p` run is not kept waiting: the hook returns at once there. A Codex conversation is woken only while it is loaded in the running Codex app, and not again after Codex restarts until it ends a turn; see [Architecture](docs/ARCHITECTURE.md#sessions-presence-and-waking-idle-conversations) for the limits. With `listen` on, a conversation that finished its turn keeps waiting for up to about 10 minutes and continues when a message arrives; while it waits, input you type is queued until you press Esc.
137
-
138
- ### LAN access
93
+ 1. Install and start the host, `n2n-nexus-host`, with Scoop, Homebrew or an archive from [Releases](https://github.com/n2ns/n2n-nexus/releases): [Installation guide, steps 1–2](docs/INSTALL.md#1-install-the-host).
94
+ 2. Install the client and connect Claude Code or Codex (`npm i -g n2n-nexus@latest`, then `n2n-nexus install claude` or `n2n-nexus install codex`): [step 3](docs/INSTALL.md#3-install-the-client-for-claude-code-and-codex).
95
+ 3. Codex only: trust the n2n-nexus hooks once with `/hooks`: [step 4](docs/INSTALL.md#4-turn-on-the-codex-hooks-one-time-required).
139
96
 
140
- By default the host listens on `127.0.0.1` only. To make it the host for other machines on a trusted network:
141
-
142
- - Tray: check **Allow LAN access**. The tray writes `"host": "0.0.0.0"` to `settings.json` and restarts the host. On Windows it adds inbound firewall rules for the TCP port and the UDP discovery port (5688 and 5688 by default) after a UAC prompt; on macOS and Linux it tells you which ports to allow in your firewall.
143
- - Service: `n2n-nexus-host service install --host 0.0.0.0` writes the address to `settings.json`; allow TCP 5688 and UDP 5688 in the firewall yourself.
144
-
145
- A host that listens on a non-loopback address answers LAN discovery on UDP `discoveryPort`.
146
-
147
- ### Cross-environment endpoint examples
148
-
149
- | Scenario | `NEXUS_ENDPOINT` |
150
- | --- | --- |
151
- | Same machine | `http://127.0.0.1:5688` |
152
- | WSL IDE to Windows host | `http://host.docker.internal:5688` |
153
- | Windows IDE to WSL host | `http://<WSL-IP>:5688` |
154
- | Remote machine | `http://<Server-IP>:5688` |
155
- | Find a host on the LAN | `discover` |
156
-
157
- With `discover`, the adapter and the hooks broadcast a query on UDP `NEXUS_DISCOVERY_PORT` and use the first host that answers; the result is cached in `~/.n2n-nexus/discovered-endpoint` and looked up again when that host cannot be reached. A host answers only when it listens on a non-loopback address (LAN access on), so allow UDP and TCP 5688 through its firewall for the trusted network. When none answers, the client uses `http://127.0.0.1:5688` (a host on this machine) and looks again after a minute.
97
+ Other MCP clients and several machines: see the [Installation guide](docs/INSTALL.md). Using it, troubleshooting and configuration: see the [User guide](docs/USER_GUIDE.md).
158
98
 
159
99
  ## 🛠️ Toolset
160
100
 
@@ -171,140 +111,8 @@ The host exposes 33 tools, grouped by purpose:
171
111
  | **Tasks** | `create_task`, `get_task`, `list_tasks`, `update_task`, `cancel_task` | Create, update and track tasks that assistants manage themselves. |
172
112
  | **Maintenance** | `host_maintenance`, `host_delete_project` | Prune or clear the global chat stream; delete a project. |
173
113
 
174
- Direct messages need the hooks from `n2n-nexus install`. Notifications carry only the sender and the first line; the text arrives through `read`, as tool output, so a message from another AI session never reaches the model as a system instruction. Unread messages expire after 24 hours and the sender is told. See the [assistant workflow](docs/ASSISTANT_GUIDE.md#direct-conversations).
175
-
176
114
  See [Tools reference](./docs/TOOLS_REFERENCE.md) for parameter schemas and call examples.
177
115
 
178
- ## 💾 Data storage
179
-
180
- The host keeps all coordination data in one SQLite database in its data directory:
181
-
182
- | Platform | Data directory | Log directory |
183
- | --- | --- | --- |
184
- | Windows | `%LOCALAPPDATA%\n2n-nexus` | `%LOCALAPPDATA%\n2n-nexus\logs` |
185
- | macOS | `~/Library/Application Support/n2n-nexus` | `~/Library/Logs/n2n-nexus` |
186
- | Linux | `$XDG_DATA_HOME/n2n-nexus` (default `~/.local/share/n2n-nexus`) | `$XDG_STATE_HOME/n2n-nexus` (default `~/.local/state/n2n-nexus`) |
187
-
188
- Override the data directory with `--root <path>` or `NEXUS_ROOT`.
189
-
190
- ```text
191
- <data directory>/
192
- ├── nexus.db # projects, assets, docs, meetings, global chat, events, tasks, read cursors, conversations, direct messages
193
- ├── settings.json # host settings (see below)
194
- └── host.lock # one host per data directory
195
- ```
196
-
197
- The background service, and the tray while it runs the host itself, log to `host.log` in the log directory (10 MB per file, 3 old files kept); the tray's own lines go to `tray.log` there; `n2n-nexus-host serve` logs to standard error. Every hour the host deletes events, read or expired direct messages and delivered notices older than 30 days, and tasks 7 days after they finished.
198
-
199
- Data of n2n-nexus 0.x (`~/.n2n-nexus`) is not imported: the host does not read it and leaves it in place.
200
-
201
- ### settings.json
202
-
203
- Read when the host starts; command-line flags override it. A missing file or field uses the default.
204
-
205
- | Field | Description | Default |
206
- | --- | --- | --- |
207
- | `host` | Address to listen on (an IPv4 address listens on IPv4 only, an IPv6 address on IPv6 only); `0.0.0.0` makes the host reachable from the LAN | `"127.0.0.1"` |
208
- | `port` | TCP port | `5688` |
209
- | `discoveryPort` | UDP port of LAN discovery, answered only when `host` is not a loopback address | `5688` |
210
- | `allowedHosts` | Extra DNS names accepted in the `Host` header (IP literals, `localhost` and `host.docker.internal` are always accepted) | `[]` |
211
- | `rateLimit` | Tool calls per second per instance; each client address gets 5x. Hooks have their own budget at the same rate per conversation and 5x per client address. `0` disables rate limiting | `20` |
212
- | `rateBurst` | Calls an instance may make at once before the per-second limit applies (minimum 1) | `60` |
213
- | `maxAutoTurns` | Automatic turns a conversation may take between its user's prompts (wakes, listening and Stops held open for messages); past it, messages wait until the user prompts. `0` disables the limit | `8` |
214
-
215
- After editing the file, restart the host (tray **Restart**, or restart `serve` or the service).
216
-
217
- ## 🏷️ Project ID conventions
218
-
219
- Project IDs follow `[prefix]_[name]`.
220
-
221
- | Prefix | Category | Example |
222
- | --- | --- | --- |
223
- | `web_` | Websites | `web_datafrog.io` |
224
- | `api_` | Backend services | `api_user-auth` |
225
- | `mcp_` | MCP servers | `mcp_nexus` |
226
- | `lib_` | Libraries / SDKs | `lib_crypto-core` |
227
- | `chrome_` | Chrome extensions | `chrome_evisa-helper` |
228
- | `vscode_` | VS Code extensions | `vscode_super-theme` |
229
- | `desktop_` | Desktop apps | `desktop_main-hub` |
230
- | `infra_` | Infrastructure / DevOps | `infra_k8s-config` |
231
- | `doc_` | Documentation | `doc_coding-guide` |
232
-
233
- ## 💻 CLI reference
234
-
235
- ### n2n-nexus-host
236
-
237
- ```bash
238
- # Tray app (default)
239
- n2n-nexus-host
240
-
241
- # Run in the foreground without a tray; flags override settings.json (--port 0 picks a free port)
242
- n2n-nexus-host serve [--host 127.0.0.1] [--port 5688] [--root <dir>]
243
-
244
- # Background service that starts at boot (elevated terminal on Windows, sudo on macOS / Linux).
245
- # install refuses while the tray runs the host (quit it first; reopened, it controls the service),
246
- # writes --host / --port to settings.json, stops a host running for the data directory,
247
- # registers and starts the service, and reports what answers on the port.
248
- # uninstall removes the service and stops its host; a tray or a foreground serve keeps running.
249
- n2n-nexus-host service install [--host <addr>] [--port <port>] [--root <dir>]
250
- n2n-nexus-host service uninstall [--root <dir>]
251
- n2n-nexus-host service status [--host <addr>] [--port <port>] [--root <dir>]
252
-
253
- # Start the tray at login, or stop doing so
254
- n2n-nexus-host autostart on|off
255
-
256
- n2n-nexus-host version
257
- ```
258
-
259
- What `service install` registers, and where `autostart on` registers the tray:
260
-
261
- | Platform | Service (starts at boot, runs as the installing user, restarts the host 5 s after it exits) | Tray at login |
262
- | --- | --- | --- |
263
- | Windows | Scheduled task `n2n-nexus-host` | `HKCU\Software\Microsoft\Windows\CurrentVersion\Run`, value `n2n-nexus-host` |
264
- | macOS | `/Library/LaunchDaemons/io.n2ns.n2n-nexus-host.plist` | `~/Library/LaunchAgents/io.n2ns.n2n-nexus-host.tray.plist` |
265
- | Linux | `/etc/systemd/system/n2n-nexus-host.service` | `~/.config/autostart/n2n-nexus-host.desktop` |
266
-
267
- Under `sudo`, the service uses the default data and log directories of the user who ran `sudo`, unless `--root` names another data directory. Only one host runs per data directory: a second one exits with "another n2n-nexus-host is running".
268
-
269
- ### n2n-nexus (client)
270
-
271
- ```bash
272
- # Start the MCP adapter (launched by the IDE)
273
- NEXUS_ENDPOINT=http://127.0.0.1:5688 n2n-nexus mcp [--id <instance id>]
274
-
275
- # Install / remove the hooks and MCP server for Claude Code or Codex
276
- n2n-nexus install <claude|codex> [--endpoint <url>] [--node <path>]
277
- n2n-nexus uninstall <claude|codex>
278
-
279
- # Check that the installed hook and MCP commands still run and that the host answers
280
- # with the client's contract version (exit 1 when not)
281
- n2n-nexus doctor [claude|codex]
282
-
283
- # Hook entry point (written into the client configuration by install)
284
- n2n-nexus hook <claude|codex> [--endpoint <url>] [--wait]
285
- ```
286
-
287
- ### Environment variables
288
-
289
- Client (`n2n-nexus`):
290
-
291
- | Variable | Description | Default |
292
- | --- | --- | --- |
293
- | `NEXUS_ENDPOINT` | Host URL for the MCP adapter, the hooks and `doctor`, or `discover` to find one on the LAN | `http://127.0.0.1:5688` |
294
- | `NEXUS_DISCOVERY_PORT` | UDP port the client queries with `discover` | `5688` |
295
- | `NEXUS_INSTANCE_ID` | Override the MCP instance ID (read cursors and meeting permissions are keyed on it) | `mcp-<hostname>-<hash of working directory>-<IDE name>` |
296
- | `NEXUS_CODEX_COMMAND` | Command the Codex background wait runs as `<command> queue ...` | `codex` |
297
-
298
- Host (`n2n-nexus-host`): only `NEXUS_ROOT`, the data directory (`--root` overrides it). Every other host setting is in `settings.json`, which the tray, the service and the command line share.
299
-
300
- ## 🔐 Security and governance notes
301
-
302
- - n2n-nexus stores coordination data locally by default.
303
- - Do not put secrets, credentials, customer data, or private tokens in meeting messages or project assets unless your local policy allows it.
304
- - Destructive tools such as project deletion should be used through explicit review workflows.
305
- - Exposing the host beyond localhost is an operational choice; restrict network access when using remote endpoints.
306
- - Treat project manifests and internal docs as potentially sensitive implementation data.
307
-
308
116
  ## 🌐 Real-world example
309
117
 
310
118
  The docs include a recorded multi-agent session that shows n2n-nexus in action. Four AI assistants — running in different IDEs and assigned to different projects — used a shared n2n-nexus host to coordinate in real time.
@@ -322,33 +130,45 @@ What to look for in the session:
322
130
  | [Discussion Log](docs/discussion_2025-12-29.md) | The full chronological stream: proposals, debug exchanges, decisions, and SYSTEM tool entries |
323
131
  | [Meeting Minutes](docs/MEETING_MINUTES_2025-12-29.md) | The structured output: participants, key decisions, test results, and action items |
324
132
 
325
- ## 🔧 Local development
133
+ ## ❓ FAQ
326
134
 
327
- Use Node.js 24 for the client, matching CI and the lint toolchain, and the Go version in `go.mod` for the host.
135
+ ### Is n2n-nexus a project management SaaS?
328
136
 
329
- ```bash
330
- git clone https://github.com/n2ns/n2n-nexus.git
331
- cd n2n-nexus
332
- npm install
333
- npm run build
137
+ No. It is a local MCP coordination server for AI assistants; its state stays local and is exposed to clients through MCP tools.
334
138
 
335
- # Run a host
336
- go run ./host/cmd/n2n-nexus-host serve --port 5799 --root /tmp/nexus-test
139
+ ### Does it replace n2n-memory?
337
140
 
338
- # Run the MCP adapter against it
339
- NEXUS_ENDPOINT=http://127.0.0.1:5799 node build/index.js mcp
340
- ```
141
+ No. `n2n-memory` is a local memory tool for a single repository. `n2n-nexus` is a multi-assistant coordination hub for project manifests, messages, meetings, tasks and shared docs.
142
+
143
+ ### Does it support Claude Desktop, Cursor, VS Code and other clients?
144
+
145
+ Yes, as long as the client can run a local MCP command server. The IDE starts the adapter, and the adapter reaches the local host through `NEXUS_ENDPOINT`. In the Claude desktop app, register it for Claude Code (`n2n-nexus install claude`) only, not in `claude_desktop_config.json`: the app would bridge it into the Code tab and drop the conversation identity ([User guide, section 2](docs/USER_GUIDE.md#2-claude-desktop-app)).
146
+
147
+ ### Can Windows and WSL work together?
148
+
149
+ Yes. Run the host in one environment and point every adapter at it with `NEXUS_ENDPOINT`.
150
+
151
+ ### Does it send data to the cloud?
152
+
153
+ No cloud service is needed. Data stays in the configured local directory. Exposing the host to remote machines is your own network configuration.
154
+
155
+ ### Is it a vector database or a source code indexer?
341
156
 
342
- See [host/README.md](./host/README.md) for building, testing and releasing the host.
157
+ No. n2n-nexus coordinates project metadata, messages, meetings, docs, tasks and assets; it does no semantic code search.
343
158
 
344
- ## 📖 Related docs
159
+ ## 📖 Documentation
345
160
 
161
+ - **[Installation guide](./docs/INSTALL.md)**: Installing, upgrading and removing the host and the client; Codex hook trust, other MCP clients, several machines.
162
+ - **[User guide](./docs/USER_GUIDE.md)**: Using n2n-nexus: the tray, direct messages, troubleshooting; commands, environment variables, `settings.json`, data and logs.
346
163
  - **[Architecture](./docs/ARCHITECTURE.md)**: System design and host-client separation explained.
347
164
  - **[Tools reference](./docs/TOOLS_REFERENCE.md)**: Full tool list and parameters.
348
165
  - **[AI Assistant Guide](./docs/ASSISTANT_GUIDE.md)**: How AI assistants should call tools and stay in project context.
166
+ - **[Host development](./host/README.md)**: Building, testing and releasing the host.
167
+ - **[Contributing](./CONTRIBUTING.md)**: Development setup, and how to report issues and contribute.
349
168
  - **[Changelog](./CHANGELOG.md)**: Version history and release notes.
350
- - **[Contributing](./CONTRIBUTING.md)**: How to report issues and contribute.
351
- - **[Security](./SECURITY.md)**: How to report vulnerabilities.
169
+ - **[Security](./SECURITY.md)**: Security model, and how to report vulnerabilities.
170
+ - **[Chinese README](./docs/README_zh.md)**: This page in Chinese; every document above has a Chinese version.
171
+ - **[llms.txt](./llms.txt)**: Index of the docs for AI models.
352
172
 
353
173
  ## 📄 License
354
174
 
package/SECURITY.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Security Policy
2
2
 
3
+ [中文版](docs/SECURITY_zh.md)
4
+
3
5
  ## Supported Versions
4
6
 
5
7
  Security fixes target the latest published versions of n2n-nexus (npm client) and n2n-nexus-host.
@@ -30,8 +32,9 @@ Key boundaries to be aware of:
30
32
 
31
33
  - **Host network exposure**: The host binds to `127.0.0.1` by default. LAN access (the tray's **Allow LAN access**, `"host": "0.0.0.0"` in `settings.json`, or `--host 0.0.0.0`) exposes it to your local network, and it then answers LAN discovery on UDP `discoveryPort`. There is no authentication layer in the open-source baseline — only expose the host beyond localhost on networks you control.
32
34
  - **Browser requests**: The host has no authentication, so it refuses requests that carry a browser `Origin` header or a cross-site `Sec-Fetch-Site` header (403), requires `Content-Type: application/json` on POST (415) and caps request bodies at 16 MB (413). It also rejects any `Host` header that is not an IP literal, `localhost`, `host.docker.internal` or a name listed in `allowedHosts` in `settings.json`, which closes DNS-rebinding attacks where a page reaches the host as a same-origin request under the attacker's domain. MCP adapters and scripts are unaffected.
33
- - **Rate limiting**: Tool calls are limited per instance ID (default 20/s, burst 60, set with `rateLimit` / `rateBurst` in `settings.json`) and, because instance IDs are self-declared, per client address at five times that. Tool calls that name no instance ID are limited only per address. Hooks (`POST /api/hook`) are limited per conversation and per client address in buckets of their own. Over the limit the host answers `429` with `Retry-After`; `/health` and `GET /api/tools` are exempt.
35
+ - **Rate limiting**: Tool calls are limited per conversation, or per instance ID when no conversation is identified (default 20/s, burst 60, set with `rateLimit` / `rateBurst` in `settings.json`) and, because instance and session IDs are self-declared, per client address at five times that. Tool calls that name neither a conversation nor an instance ID are limited only per address. Hooks (`POST /api/hook`) are limited per conversation and per client address in buckets of their own. Over the limit the host answers `429` with `Retry-After`; `/health` and `GET /api/tools` are exempt.
34
36
  - **Malformed requests**: Invalid request URLs or Host headers return 400 without terminating the host. Validly formed requests still pass through the browser and Host checks above.
35
37
  - **Sensitive content in coordination data**: Project manifests, internal docs, meeting messages, and global documents are stored in plaintext in the host's local database. Do not store secrets, credentials, or customer data in these fields.
38
+ - **Destructive tools**: Destructive tools such as project deletion should be used through explicit review workflows.
36
39
  - **Project assets**: Binary assets uploaded via `upload_project_asset` are stored in the host's database in its data directory. Treat them as local files with the same access controls.
37
40
  - **API key for MCP**: The MCP adapter connects to the host over plain HTTP. If you expose the host to other machines, consider adding a reverse proxy with TLS in front of it.
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * `n2n-nexus doctor [claude|codex]`: check that the Node path and the entry
3
- * script in the hook commands and in the MCP server command that `install`
4
- * wrote still exist and run, that the host those commands talk to answers
3
+ * script in the hook commands, and the Node path and npm's npx script in the
4
+ * MCP server command (`"<node>" "<npx>" -y n2n-nexus@latest mcp`), that
5
+ * `install` wrote still exist and run, that the host those commands talk to answers
5
6
  * GET /health as n2n-nexus-host with this client's contract version, and say
6
7
  * how to fix what does not. Without a client both are checked; a client with
7
8
  * nothing installed is only reported when it was named. Exit code 1 when a
@@ -13,7 +14,7 @@ import { CONTRACT_VERSION, HOOK_CLIENTS, MCP_SERVER_NAME, isHookClient, noDaemon
13
14
  import { NexusClient, NexusToolError } from "../client/nexus-client.js";
14
15
  import { LOCAL_ENDPOINT, resolveEndpoint } from "../client/discovery.js";
15
16
  import { pkg } from "../config/index.js";
16
- import { VERSIONED_NODE_DIR, configFile, isOurs, parseVersion, readConfig, satisfiesEngines } from "./install.js";
17
+ import { MCP_PACKAGE, VERSIONED_NODE_DIR, configFile, isOurs, parseVersion, readConfig, satisfiesEngines } from "./install.js";
17
18
  import { mcpConfigFile, readMcpCommand } from "./mcp-config.js";
18
19
  const ok = (text) => ({ ok: true, text });
19
20
  const bad = (text) => ({ ok: false, text });
@@ -131,11 +132,11 @@ function doctorClient(client, endpoints, defaultEndpoint) {
131
132
  }
132
133
  else {
133
134
  report(`MCP Node ${mcp.command}`, checkNode(mcp.command));
134
- const entry = mcp.args[0];
135
- if (entry)
136
- report(`MCP entry ${entry}`, checkEntry(entry));
135
+ const [npx, ...rest] = mcp.args;
136
+ if (npx && rest.join(" ") === `-y ${MCP_PACKAGE} mcp`)
137
+ report(`MCP npx ${npx}`, checkEntry(npx));
137
138
  else
138
- report(`MCP command in ${mcpFile}`, bad("has no entry script argument"));
139
+ report(`MCP command in ${mcpFile}`, bad(`not in the form "<node>" "<npx>" -y ${MCP_PACKAGE} mcp`));
139
140
  }
140
141
  if (failed) {
141
142
  lines.push(` fix: run "n2n-nexus install ${client}" again (add --node <path> to use a stable Node path such as /usr/local/bin/node).`);
package/build/cli/hook.js CHANGED
@@ -2,15 +2,19 @@
2
2
  * `n2n-nexus hook <claude|codex> [--endpoint <url>] [--wait]`: forward one client
3
3
  * hook event to the daemon and print its output, or with `--wait` run the
4
4
  * background wait that wakes an idle conversation. Runs on every tool call, so
5
- * it loads only node built-ins (no MCP SDK, zod or SQLite), and every failure
5
+ * it loads only node built-ins (no MCP SDK or zod; node:sqlite only to read a
6
+ * Codex conversation's title, see title.ts), and every failure
6
7
  * is silent: exit 0 with nothing on stdout, so the client carries on unaffected.
7
8
  */
8
9
  import { spawn } from "node:child_process";
9
- import { BACKGROUND_WAIT_EVENT, HOOK_INPUT_FIELDS, HOOK_REQUEST_TIMEOUT_MS, HOOK_STOP_REQUEST_TIMEOUT_MS, WAIT_HOOK_TIMEOUT_SEC, noDaemonHint, isHookClient } from "../contract.js";
10
+ import { BACKGROUND_WAIT_EVENT, WAKE_RESULT_EVENT, HOOK_INPUT_FIELDS, HOOK_REQUEST_TIMEOUT_MS, HOOK_STOP_REQUEST_TIMEOUT_MS, SESSION_END_REQUEST_TIMEOUT_MS, WAIT_HOOK_TIMEOUT_SEC, WAKE_RETRY_FIRST_DELAY_MS, WAKE_RETRY_MAX_DELAY_MS, noDaemonHint, isHookClient } from "../contract.js";
10
11
  import { NexusClient, NexusTransportError, REQUEST_TIMEOUT_CODE } from "../client/nexus-client.js";
11
12
  import { DISCOVER_ENDPOINT, LOCAL_ENDPOINT, resolveEndpoint } from "../client/discovery.js";
13
+ import { readTitle } from "./title.js";
12
14
  /** Errors after which a discovered daemon is looked up again: it was never reached, so resending is safe. */
13
15
  const UNREACHABLE_CODES = new Set(["ECONNREFUSED", "EHOSTUNREACH", "ENETUNREACH", "ETIMEDOUT", "EADDRNOTAVAIL"]);
16
+ /** Events whose forwarded input carries the conversation's title. */
17
+ const TITLE_EVENTS = new Set(["SessionStart", "UserPromptSubmit", "Stop"]);
14
18
  function isPlainObject(value) {
15
19
  return typeof value === "object" && value !== null && !Array.isArray(value);
16
20
  }
@@ -38,7 +42,15 @@ async function forward(argv) {
38
42
  if (Object.prototype.hasOwnProperty.call(stdin, field))
39
43
  input[field] = stdin[field];
40
44
  }
41
- const timeoutMs = stdin.hook_event_name === "Stop" ? HOOK_STOP_REQUEST_TIMEOUT_MS : HOOK_REQUEST_TIMEOUT_MS;
45
+ // The title is read only where the host updates it, not on the frequent tool events.
46
+ if (typeof stdin.hook_event_name === "string" && TITLE_EVENTS.has(stdin.hook_event_name)) {
47
+ const title = await readTitle(client, stdin);
48
+ if (title !== undefined)
49
+ input.title = title;
50
+ }
51
+ // SessionEnd must finish within Codex's 3 s hook limit (discovery included): a short budget and a single attempt.
52
+ const sessionEnd = stdin.hook_event_name === "SessionEnd";
53
+ const timeoutMs = stdin.hook_event_name === "Stop" ? HOOK_STOP_REQUEST_TIMEOUT_MS : sessionEnd ? SESSION_END_REQUEST_TIMEOUT_MS : HOOK_REQUEST_TIMEOUT_MS;
42
54
  /** Send to `endpoint`; nothing listening there is reported with what to do about it. */
43
55
  const send = async (endpoint) => {
44
56
  try {
@@ -60,7 +72,7 @@ async function forward(argv) {
60
72
  // A timed-out Stop may have been waiting for messages on purpose, so only other events count a timeout.
61
73
  const code = error instanceof NexusTransportError ? error.code ?? "" : "";
62
74
  const unreachable = UNREACHABLE_CODES.has(code) || (code === REQUEST_TIMEOUT_CODE && stdin.hook_event_name !== "Stop");
63
- if (configured !== DISCOVER_ENDPOINT || !unreachable)
75
+ if (configured !== DISCOVER_ENDPOINT || !unreachable || sessionEnd)
64
76
  throw error;
65
77
  const found = await resolveEndpoint(configured, { fresh: true });
66
78
  if (found === endpoint)
@@ -79,8 +91,8 @@ async function forward(argv) {
79
91
  /** The background wait ends before the client's hook timeout would kill it during a request. */
80
92
  const WAIT_DEADLINE_MS = (WAIT_HOOK_TIMEOUT_SEC - 10) * 1000;
81
93
  /**
82
- * Ask the daemon until something arrives for the session (the notification
83
- * text), the session becomes active again or the deadline passes (null).
94
+ * Ask the daemon until something arrives for the session (the wake), the
95
+ * session becomes active again or the deadline passes (null).
84
96
  */
85
97
  export async function waitForWake(client, sessionId, endpoint, deadlineMs = WAIT_DEADLINE_MS) {
86
98
  const nexus = new NexusClient({ endpoint });
@@ -89,8 +101,8 @@ export async function waitForWake(client, sessionId, endpoint, deadlineMs = WAIT
89
101
  while (Date.now() < deadline) {
90
102
  const response = await nexus.hook({ client, input: { hook_event_name: BACKGROUND_WAIT_EVENT, session_id: sessionId } }, HOOK_STOP_REQUEST_TIMEOUT_MS);
91
103
  const output = isPlainObject(response) ? response.output : null;
92
- if (isPlainObject(output) && typeof output.wake === "string")
93
- return output.wake;
104
+ if (isPlainObject(output) && typeof output.wake === "string" && typeof output.dispatchId === "string")
105
+ return { text: output.wake, dispatchId: output.dispatchId };
94
106
  if (!(isPlainObject(output) && output.timeout === true))
95
107
  return null;
96
108
  }
@@ -101,7 +113,11 @@ export async function waitForWake(client, sessionId, endpoint, deadlineMs = WAIT
101
113
  * asyncRewake hook: exit code 2 with the notification on stderr wakes the
102
114
  * conversation. Codex has no such hook, so its Stop hook starts a detached copy
103
115
  * (`--session <id>`) and returns at once; the copy hands the notification to
104
- * `codex queue` (NEXUS_CODEX_COMMAND, default `codex`), which starts a turn.
116
+ * `codex queue` (NEXUS_CODEX_COMMAND, default `codex`), which starts a turn,
117
+ * and reports to the host whether it did (WAKE_RESULT_EVENT): after a failed
118
+ * queue the host undoes the wake and the copy waits again after a delay that
119
+ * doubles on each failure, so the same messages wake the session on a later
120
+ * try without retrying a broken `codex` in a loop.
105
121
  */
106
122
  async function runWait(argv) {
107
123
  const client = argv[0];
@@ -124,17 +140,29 @@ async function runWait(argv) {
124
140
  return 0;
125
141
  }
126
142
  }
127
- const text = await waitForWake(client, sessionId, await resolveEndpoint(configured));
128
- if (text === null)
129
- return 0;
130
- if (client === "codex") {
143
+ // Load what a wake needs before waiting: the wait lasts up to an hour, and an
144
+ // upgrade or a cleared npx cache may remove the package's files meanwhile.
145
+ const { spawnClientSync } = client === "codex" ? await import("./client-command.js") : { spawnClientSync: undefined };
146
+ const endpoint = await resolveEndpoint(configured);
147
+ const deadline = Date.now() + WAIT_DEADLINE_MS;
148
+ for (let delay = WAKE_RETRY_FIRST_DELAY_MS;; delay = Math.min(delay * 2, WAKE_RETRY_MAX_DELAY_MS)) {
149
+ const wake = await waitForWake(client, sessionId, endpoint, deadline - Date.now());
150
+ if (wake === null)
151
+ return 0;
152
+ if (!spawnClientSync) {
153
+ await new Promise((resolve) => process.stderr.write(wake.text + "\n", () => resolve()));
154
+ return 2;
155
+ }
131
156
  // Never through cmd.exe: the text comes from another session and spans several lines.
132
- const { spawnClientSync } = await import("./client-command.js");
133
- spawnClientSync(process.env.NEXUS_CODEX_COMMAND || "codex", ["queue", "--thread", sessionId, "--message", text], { stdio: "ignore", windowsHide: true });
134
- return 0;
157
+ const queued = spawnClientSync(process.env.NEXUS_CODEX_COMMAND || "codex", ["queue", "--thread", sessionId, "--message", wake.text], { stdio: "ignore", windowsHide: true });
158
+ const ok = !queued.error && queued.status === 0;
159
+ const input = { hook_event_name: WAKE_RESULT_EVENT, session_id: sessionId, dispatch_id: wake.dispatchId, ok };
160
+ const reported = await new NexusClient({ endpoint }).hook({ client, input }, HOOK_REQUEST_TIMEOUT_MS).then(() => true, () => false);
161
+ // Unknown result (no report): the host keeps the wake, and waiting again would deliver nothing.
162
+ if (ok || !reported || Date.now() + delay >= deadline)
163
+ return 0;
164
+ await new Promise((resolve) => setTimeout(resolve, delay));
135
165
  }
136
- await new Promise((resolve) => process.stderr.write(text + "\n", () => resolve()));
137
- return 2;
138
166
  }
139
167
  /** Run the hook command; resolves to exit code 0, or 2 when a background wait wakes a Claude conversation. */
140
168
  export async function runHook(argv) {