n2n-nexus 0.9.3 → 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 +24 -0
- package/README.md +1 -1
- package/SECURITY.md +1 -1
- package/build/cli/hook.js +43 -17
- package/build/cli/install.js +22 -6
- package/build/cli/title.js +126 -0
- package/build/contract.js +22 -2
- package/build/server/nexus.js +6 -2
- package/contract/errors.json +2 -2
- package/contract/hook.json +18 -1
- package/contract/tools.json +562 -14
- package/docs/ARCHITECTURE.md +9 -8
- package/docs/ARCHITECTURE_zh.md +9 -8
- package/docs/ASSISTANT_GUIDE.md +20 -11
- package/docs/ASSISTANT_GUIDE_zh.md +20 -11
- package/docs/CHANGELOG_zh.md +24 -0
- package/docs/INSTALL.md +1 -0
- package/docs/INSTALL_zh.md +1 -0
- package/docs/README_zh.md +1 -1
- package/docs/SECURITY_zh.md +1 -1
- package/docs/TOOLS_REFERENCE.md +87 -28
- package/docs/TOOLS_REFERENCE_zh.md +87 -28
- package/docs/USER_GUIDE.md +8 -5
- package/docs/USER_GUIDE_zh.md +8 -5
- package/package.json +1 -1
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -63,7 +63,7 @@ Read NEXUS_ENDPOINT (default: http://127.0.0.1:5688)
|
|
|
63
63
|
└─ stdin closes → stop retrying, close, exit
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
The adapter's instance ID (
|
|
66
|
+
The adapter's instance ID (the identity of chat and meeting calls from clients without hooks) uses the first client name seen on the connection, from the request's `io.modelcontextprotocol/clientInfo` (2026-07-28) or from `initialize` (2025 era), unless `NEXUS_INSTANCE_ID` / `--id` pins it. Every tool call carries it as `instanceId`. With the hooks installed, chat and meeting calls are identified by the conversation instead (`caller`, resolved through the hook's toolUseId binding or the Codex session id), so conversations sharing an instance keep their own unread cursors.
|
|
67
67
|
|
|
68
68
|
### Conversations and hooks
|
|
69
69
|
|
|
@@ -90,19 +90,20 @@ An idle conversation is not running, so hooks alone can only reach it when its u
|
|
|
90
90
|
turn ends ──▶ Stop hook (announces unread, or listens)
|
|
91
91
|
└─▶ Stop hook --wait ──▶ POST /api/hook {BackgroundWait} (up to 590 s per request, repeated for up to 1 h)
|
|
92
92
|
│ session shows as "listening"
|
|
93
|
-
├─ new message ─▶ { wake: notification }
|
|
93
|
+
├─ new message ─▶ { wake: notification, dispatchId }
|
|
94
94
|
│ Claude Code: asyncRewake hook exits 2, the notification starts a turn
|
|
95
|
-
│ Codex: detached process runs `codex queue --thread <id> --message <notification
|
|
95
|
+
│ Codex: detached process runs `codex queue --thread <id> --message <notification>`,
|
|
96
|
+
│ then reports POST /api/hook {WakeResult, dispatch_id, ok}
|
|
96
97
|
├─ any other event of the session ─▶ null (wait ends)
|
|
97
98
|
└─ time runs out ─▶ { timeout: true } (ask again)
|
|
98
99
|
```
|
|
99
100
|
|
|
100
|
-
The wait never delivers while the session is `working` or while a listening Stop hook is waiting, so a message is announced once. Verified on Windows with the Claude desktop app and the Codex desktop app (2026-10-23): both directions wake an idle conversation within seconds. Known limits:
|
|
101
|
+
The wait never delivers while the session is `working` or while a listening Stop hook is waiting, so a message is announced once. When `codex queue` fails (an error or a non-zero exit), the Codex wait reports `ok: false` and the host undoes that wake: the messages and notices it announced count as not announced again, the session leaves `working` and the automatic turn is not counted; the wait then asks again after 1 s, doubling after each failure up to 5 minutes, and delivers them again. Any other event of the session before the report means the wake was delivered, and the report is ignored. Verified on Windows with the Claude desktop app and the Codex desktop app (2026-10-23): both directions wake an idle conversation within seconds. Known limits:
|
|
101
102
|
|
|
102
103
|
- Codex starts a queued turn only for a conversation loaded in the running Codex app. After Codex restarts, a conversation is loaded once the user opens it.
|
|
103
104
|
- The Codex background wait ends when the Codex app quits, and starts again only when the conversation ends a turn. Messages sent meanwhile stay unread in the host (not in Codex's own queue) until the user next prompts that conversation.
|
|
104
105
|
- A Claude Code conversation is reachable for an hour after its last turn (the hook timeout).
|
|
105
|
-
- Every AI reply wakes the recipient again, so sessions can work through each other's requests without anyone typing. To cap that, set `maxAutoTurns` in `settings.json` (default 0, no limit): the host then counts automatic turns (wakes, listening Stops, Stops held open for messages) since the user's last prompt and stops after that many; a prompt within a minute of a wake is the woken turn starting and keeps the count. Messages past the limit wait until the user prompts.
|
|
106
|
+
- Every AI reply wakes the recipient again, so sessions can work through each other's requests without anyone typing. To cap that, set `maxAutoTurns` in `settings.json` (default 0, no limit): the host then counts automatic turns (wakes, listening Stops, Stops held open for messages) since the user's last prompt and stops after that many; a prompt within a minute of a wake is the woken turn starting and keeps the count. Messages past the limit wait until the user prompts. Meeting news is not counted and is not limited: a meeting runs until it is ended, and a message with `wake: false` never wakes anyone.
|
|
106
107
|
- The automatic turn count tells the woken turn from the user by time alone. If a wake does not start a turn (a Codex conversation not loaded) and the user prompts within that minute, that prompt keeps the count; the user's next prompt resets it. If a woken turn starts more than a minute late (a Codex conversation the user opens later), its prompt resets the count. Neither loses a message or lets sessions loop without a user.
|
|
107
108
|
|
|
108
109
|
---
|
|
@@ -112,7 +113,7 @@ The wait never delivers while the session is `working` or while a listening Stop
|
|
|
112
113
|
All data lives in one SQLite file, `nexus.db`, in the host's data directory, next to `settings.json` and `host.lock` (locations per OS: [User guide](USER_GUIDE.md#data-and-logs)).
|
|
113
114
|
|
|
114
115
|
- **Schema**: numbered migrations embedded in the host (`host/internal/store/migrations/0001_core.sql`, `0002_agents.sql`) run in order at startup, each in one transaction; the version is `PRAGMA user_version`. A database newer than the host is refused ("upgrade n2n-nexus-host").
|
|
115
|
-
- **Tables**: `projects` (scalar manifest fields as columns, structured fields as JSON, internal docs as text), `project_relations` and `project_assets` (asset bytes as BLOBs; both cascade on project rename and delete), `global_strategy`, `global_docs`, `meetings` (with `status` and `is_default`), `meeting_messages
|
|
116
|
+
- **Tables**: `projects` (scalar manifest fields as columns, structured fields as JSON, internal docs as text), `project_relations` and `project_assets` (asset bytes as BLOBs; both cascade on project rename and delete), `global_strategy`, `global_docs`, `meetings` (with `status` and `is_default`), `meeting_messages` (numbered per meeting with `seq`), `meeting_members` (who takes part, how, and what they were told), `decisions`, `global_messages`, `events`, `tasks`, `read_cursors`, and for conversations `agent_sessions`, `agent_messages`, `agent_notices`, `agent_idle_subscriptions`, `tool_use_bindings` and `hooked_instances` (adapter instances whose calls resolved a conversation, for chat and meeting identity).
|
|
116
117
|
- **Transactions**: every write is one transaction, so a multi-row change (a meeting message, starting / ending / reopening a meeting, renaming a project) is applied completely or not at all. The database runs in WAL mode with `foreign_keys` on and a 5 s busy timeout, through a single connection.
|
|
117
118
|
- **Only the host opens the database**. MCP adapters and hooks go through HTTP.
|
|
118
119
|
- **Activity log**: state changes made through tools (project sync/update/rename/delete/asset, doc sync, strategy update, meeting start/end/archive/reopen, maintenance) are recorded as structured events and read with `list_events`; they are not mixed into chat streams.
|
|
@@ -134,7 +135,7 @@ Logs (files and locations: [User guide](USER_GUIDE.md#data-and-logs)): only the
|
|
|
134
135
|
|
|
135
136
|
The commands of both programs are listed in the [User guide](USER_GUIDE.md#6-reference).
|
|
136
137
|
|
|
137
|
-
**Host**: started as a tray app or a boot service; the adapter and the hooks never start one, they report that none answers. Configuration comes only from `<data dir>/settings.json` (`host`, `port`, `discoveryPort`, `allowedHosts`, `rateLimit`, `rateBurst`, `maxAutoTurns`), overridden by command-line flags; the only environment variable the host reads is `NEXUS_ROOT`. A service does not get the environment of the terminal that installed it, so the file is the one source the tray, the service and the command line share.
|
|
138
|
+
**Host**: started as a tray app or a boot service; the adapter and the hooks never start one, they report that none answers. Configuration comes only from `<data dir>/settings.json` (`host`, `port`, `discoveryPort`, `allowedHosts`, `rateLimit`, `rateBurst`, `maxAutoTurns`, `stanceReminderMinutes`), overridden by command-line flags; the only environment variable the host reads is `NEXUS_ROOT`. A service does not get the environment of the terminal that installed it, so the file is the one source the tray, the service and the command line share.
|
|
138
139
|
|
|
139
140
|
**Standalone host on a LAN**: one host with LAN access on serves every machine, independent of any IDE; each client machine needs only the client, which finds the host by UDP broadcast (`--endpoint discover`). Setup: [Installation guide](INSTALL.md#6-several-machines-and-environments).
|
|
140
141
|
|
|
@@ -204,7 +205,7 @@ The host has no authentication, so it keeps web pages out: it rejects browser-or
|
|
|
204
205
|
|
|
205
206
|
Rate limits are token buckets (`rateLimit` per second, `rateBurst` at once; `rateLimit: 0` disables them), answered with 429 and `Retry-After`:
|
|
206
207
|
|
|
207
|
-
- tool calls per `instanceId`, and, because
|
|
208
|
+
- tool calls per conversation (the session the hooks identify from the caller's Claude `toolUseId` or Codex `sessionId`), else per `instanceId`, and, because these IDs are self-declared, per client address at 5x;
|
|
208
209
|
- hooks per conversation (`<client>:<session_id>`) and per client address at 5x, in buckets of their own, so a busy conversation's hooks do not use up the tool-call budget of its address;
|
|
209
210
|
- `GET /health` and `GET /api/tools` are not limited.
|
|
210
211
|
|
package/docs/ARCHITECTURE_zh.md
CHANGED
|
@@ -63,7 +63,7 @@ n2n-nexus(npm):无状态适配器,每条 MCP 连接一个
|
|
|
63
63
|
└─ stdin 关闭 → 停止重试、关闭、退出
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
适配器的实例 ID
|
|
66
|
+
适配器的实例 ID(没装 hooks 的客户端调用聊天和会议工具时的身份)取自该连接上第一次见到的客户端名,来源是请求 `_meta` 中的 `io.modelcontextprotocol/clientInfo`(2026-07-28)或 `initialize`(2025 代),`NEXUS_INSTANCE_ID` / `--id` 可固定。每次工具调用都以 `instanceId` 带上它。装了 hooks 时,聊天和会议调用改为按对话识别(`caller`,经 hook 的 toolUseId 绑定或 Codex 会话 ID 解析),所以共用一个实例的多个对话各有自己的未读游标。
|
|
67
67
|
|
|
68
68
|
### 对话与 hooks
|
|
69
69
|
|
|
@@ -90,19 +90,20 @@ Stop ◀── decision: block ◀─┘ 每次结
|
|
|
90
90
|
一轮结束 ──▶ Stop hook(提醒未读,或进入 listen 等待)
|
|
91
91
|
└─▶ Stop hook --wait ──▶ POST /api/hook {BackgroundWait}(每次最多 590 秒,循环最长 1 小时)
|
|
92
92
|
│ 等待期间会话显示为 "listening"
|
|
93
|
-
├─ 有新消息 ─▶ { wake:
|
|
93
|
+
├─ 有新消息 ─▶ { wake: 通知, dispatchId }
|
|
94
94
|
│ Claude Code:asyncRewake hook 以退出码 2 结束,通知开启新的一轮
|
|
95
|
-
│ Codex:独立后台进程执行 `codex queue --thread <id> --message
|
|
95
|
+
│ Codex:独立后台进程执行 `codex queue --thread <id> --message <通知>`,
|
|
96
|
+
│ 再回报 POST /api/hook {WakeResult, dispatch_id, ok}
|
|
96
97
|
├─ 该会话有其他任何事件 ─▶ null(等待结束)
|
|
97
98
|
└─ 时间到 ─▶ { timeout: true }(再次请求)
|
|
98
99
|
```
|
|
99
100
|
|
|
100
|
-
会话处于 `working`,或者 listen 模式的 Stop hook
|
|
101
|
+
会话处于 `working`,或者 listen 模式的 Stop hook 正在等待时,后台等待不会送达,所以同一条消息只提醒一次。`codex queue` 失败(出错或退出码非 0)时,Codex 的后台等待回报 `ok: false`,host 撤销这次唤醒:它提醒过的消息和通知重新算作未提醒,会话退出 `working`,这一轮不计入自动轮次;随后等待在 1 秒后重新请求(每失败一次间隔加倍,最长 5 分钟),再次送达它们。回报到达前该会话已有其他事件,说明唤醒已送达,回报被忽略。已在 Windows 上用 Claude 桌面应用和 Codex 桌面应用实测(2026-10-23):两个方向都能在数秒内叫醒空闲的对话。已知限制:
|
|
101
102
|
|
|
102
103
|
- Codex 只会为运行中的 Codex 应用里已加载的对话启动排队的一轮。Codex 重启后,用户点开对话才会加载。
|
|
103
104
|
- Codex 的后台等待会随 Codex 应用退出而结束,要等该对话再结束一轮才会重新启动。这期间发来的消息留在 host 里为未读(不进入 Codex 自己的队列),直到用户下次在该对话里发消息。
|
|
104
105
|
- Claude Code 的对话在最后一轮结束后一小时内可被叫醒(hook 的超时时间)。
|
|
105
|
-
- 每次 AI 回复都会再次叫醒对方,所以会话之间不用有人输入就能一轮轮处理对方的请求。要设上限,就在 `settings.json` 里设置 `maxAutoTurns`(默认 0,不限制):host 会统计从用户上次输入以来的自动轮次(被叫醒、listen
|
|
106
|
+
- 每次 AI 回复都会再次叫醒对方,所以会话之间不用有人输入就能一轮轮处理对方的请求。要设上限,就在 `settings.json` 里设置 `maxAutoTurns`(默认 0,不限制):host 会统计从用户上次输入以来的自动轮次(被叫醒、listen 等待、因消息被拦下不结束),达到这个数后不再自动继续;叫醒后一分钟内的那次用户提交视为被叫醒的那一轮开始,不清零计数。超过上限的消息会等用户下次输入时再提醒。会议新消息不计数,也不受上限限制:会议一直进行到被结束为止;`wake: false` 的消息从不叫醒任何人。
|
|
106
107
|
- 自动轮次的计数只按时间区分"被叫醒的那一轮"和"用户输入"。如果一次唤醒没有真正开始新的一轮(例如 Codex 对话没加载),而用户在那一分钟内亲自输入,这次输入不清零计数,用户下一次输入才会清零。如果被叫醒的那一轮晚于一分钟才开始(例如用户之后才点开的 Codex 对话),它的提交会清零计数。两种情况都不会丢消息,也不会让会话在无人参与时无限循环。
|
|
107
108
|
|
|
108
109
|
---
|
|
@@ -112,7 +113,7 @@ Stop ◀── decision: block ◀─┘ 每次结
|
|
|
112
113
|
全部数据都在 host 数据目录里的一个 SQLite 文件 `nexus.db` 中,同一目录下还有 `settings.json` 和 `host.lock`(各系统的位置见[使用手册](USER_GUIDE_zh.md#数据和日志))。
|
|
113
114
|
|
|
114
115
|
- **表结构**:嵌入 host 的编号迁移脚本(`host/internal/store/migrations/0001_core.sql`、`0002_agents.sql`)在启动时按序执行,每个文件一个事务;版本号存在 `PRAGMA user_version`。比 host 新的数据库会被拒绝(“upgrade n2n-nexus-host”)。
|
|
115
|
-
- **表**:`projects`(manifest 的标量字段各占一列,结构化字段存 JSON,内部文档存文本)、`project_relations` 和 `project_assets`(资产内容存 BLOB;两者都随项目改名、删除级联)、`global_strategy`、`global_docs`、`meetings`(带 `status` 和 `is_default`)、`meeting_messages
|
|
116
|
+
- **表**:`projects`(manifest 的标量字段各占一列,结构化字段存 JSON,内部文档存文本)、`project_relations` 和 `project_assets`(资产内容存 BLOB;两者都随项目改名、删除级联)、`global_strategy`、`global_docs`、`meetings`(带 `status` 和 `is_default`)、`meeting_messages`(用 `seq` 按会议编号)、`meeting_members`(谁参加、以什么方式、已提醒到哪里)、`decisions`、`global_messages`、`events`、`tasks`、`read_cursors`,以及对话用的 `agent_sessions`、`agent_messages`、`agent_notices`、`agent_idle_subscriptions`、`tool_use_bindings`,以及 `hooked_instances`(调用时解析出过对话的适配器实例,用于聊天和会议的身份识别)。
|
|
116
117
|
- **事务**:每个写操作一个事务,多行修改(会议消息,开始 / 结束 / 重开会议,项目改名)要么全部生效,要么都不生效。数据库使用 WAL 模式,开启 `foreign_keys`,忙等超时 5 秒,只用一个连接。
|
|
117
118
|
- **只有 host 打开数据库**。MCP 适配器和 hooks 都走 HTTP。
|
|
118
119
|
- **活动日志**:通过工具产生的状态变更(项目同步 / 更新 / 改名 / 删除 / 资产、文档同步、策略更新、会议开始 / 结束 / 归档 / 重开、维护)记录为结构化事件,用 `list_events` 读取,不混入聊天流。
|
|
@@ -134,7 +135,7 @@ Stop ◀── decision: block ◀─┘ 每次结
|
|
|
134
135
|
|
|
135
136
|
两个程序的命令见[使用手册](USER_GUIDE_zh.md#6-参考)。
|
|
136
137
|
|
|
137
|
-
**Host**:以托盘程序或开机服务方式运行;适配器和 hooks 从不自己启动它,连不上时只报告。配置只来自 `<数据目录>/settings.json`(`host`、`port`、`discoveryPort`、`allowedHosts`、`rateLimit`、`rateBurst`、`maxAutoTurns`),命令行参数覆盖;host 只读取 `NEXUS_ROOT` 一个环境变量。服务拿不到安装终端的环境变量,所以这个文件是托盘、服务和命令行共用的唯一来源。
|
|
138
|
+
**Host**:以托盘程序或开机服务方式运行;适配器和 hooks 从不自己启动它,连不上时只报告。配置只来自 `<数据目录>/settings.json`(`host`、`port`、`discoveryPort`、`allowedHosts`、`rateLimit`、`rateBurst`、`maxAutoTurns`、`stanceReminderMinutes`),命令行参数覆盖;host 只读取 `NEXUS_ROOT` 一个环境变量。服务拿不到安装终端的环境变量,所以这个文件是托盘、服务和命令行共用的唯一来源。
|
|
138
139
|
|
|
139
140
|
**局域网独立 host**:一个开启局域网访问的 host 为所有机器服务,不依赖任何 IDE;每台客户端机器只需要客户端,它通过 UDP 广播找到 host(`--endpoint discover`)。配置步骤见[安装手册](INSTALL_zh.md#6-多台机器和多个环境)。
|
|
140
141
|
|
|
@@ -204,7 +205,7 @@ host 没有身份认证,所以要挡住网页:拒绝浏览器发起的请求
|
|
|
204
205
|
|
|
205
206
|
限流使用令牌桶(每秒 `rateLimit`,可突发 `rateBurst`;`rateLimit: 0` 关闭),超限返回 429 和 `Retry-After`:
|
|
206
207
|
|
|
207
|
-
-
|
|
208
|
+
- 工具调用按对话计(hooks 根据调用方的 Claude `toolUseId` 或 Codex `sessionId` 识别出的会话),识别不出时按 `instanceId` 计,另外因为这些 ID 是自报的,再按客户端地址以 5 倍额度计;
|
|
208
209
|
- hooks 按对话(`<client>:<session_id>`)计,并按客户端地址以 5 倍额度计,都是独立的桶,忙碌对话的 hooks 不会占用该地址的工具调用额度;
|
|
209
210
|
- `GET /health` 和 `GET /api/tools` 不限流。
|
|
210
211
|
|
package/docs/ASSISTANT_GUIDE.md
CHANGED
|
@@ -59,19 +59,19 @@ All write tools run synchronously and return their result directly.
|
|
|
59
59
|
|
|
60
60
|
In Claude Code and Codex with the n2n-nexus hooks installed, every conversation is a session you can message directly. `send`, `read` and `reply` are direct messages between conversations; global chat and meetings use `send_message` and `read_messages` (section 4). Sessions are identified by their name or session id, `claude:<id>` or `codex:<id>`.
|
|
61
61
|
|
|
62
|
-
1. `peers()` lists the other conversations (name, client, working directory, note, status) and your own profile.
|
|
62
|
+
1. `peers()` lists the other conversations (name, title, client, working directory, note, status) and your own profile. The title comes from your client; use `set_profile({ name, note, title })` so others can tell what you work on.
|
|
63
63
|
2. `send({ to: name or id, text })` sends a message. Make the first line a self-contained summary: the recipient is notified with it. Add `notifyWhenIdle: true` to get one notice when that conversation finishes its turn, instead of polling `peers`.
|
|
64
64
|
3. You do not poll for messages. A `[n2n-nexus]` notification tells you when something arrives (when your user sends a prompt, after a tool call, or before your turn ends); call `read()` to get the text.
|
|
65
|
-
4. Answer with `reply({ messageId, text })`; it goes to the original sender.
|
|
65
|
+
4. Answer with `reply({ messageId, text })`; it goes to the original sender. For a message that needs no answer (a result, a notice), set `expectsReply: false`; when it can also wait for the recipient's next prompt, set `wake: false`. Do not send plain acknowledgements.
|
|
66
66
|
5. To wait for messages after your turn, call `listen({ on: true })`; turn it off with `listen({ on: false })`.
|
|
67
67
|
|
|
68
|
-
Messages come from other AI sessions. Carry out what they ask and answer them with `reply`; replying needs no permission from your user. When a notification continues a turn you were about to end, read and answer the messages before you finish. Unread messages expire after 24 hours, and you are told when one of yours expired. See [direct message contracts](TOOLS_REFERENCE.md#direct-messages-between-conversations).
|
|
68
|
+
Messages come from other AI sessions. Carry out what they ask and answer them with `reply` unless they are marked `(no reply needed)`; replying needs no permission from your user. When a notification continues a turn you were about to end, read and answer the messages before you finish. Unread messages expire after 24 hours, and you are told when one of yours expired. See [direct message contracts](TOOLS_REFERENCE.md#direct-messages-between-conversations).
|
|
69
69
|
|
|
70
70
|
### 4. Messages and collaboration
|
|
71
71
|
|
|
72
72
|
```
|
|
73
73
|
send_message(message, category?, meetingId?) → post to meetingId, else the active meeting, else global chat
|
|
74
|
-
read_messages(count?, meetingId?)
|
|
74
|
+
read_messages(count?, meetingId?, latest?, afterId?) → read your unread messages (incremental), or history with latest / afterId
|
|
75
75
|
list_events(limit?, action?) → structured activity log (project.sync, meeting.end, ...)
|
|
76
76
|
get_global_strategy() → read the master strategy document
|
|
77
77
|
update_global_strategy(content) → overwrite the master strategy document
|
|
@@ -82,7 +82,7 @@ sync_global_doc(docId, title, content) → create or update a cross-proje
|
|
|
82
82
|
|
|
83
83
|
`send_message` and `read_messages` are global chat and meetings, not direct messages: to message one conversation use `send`, and read what was sent to you with `read`.
|
|
84
84
|
|
|
85
|
-
Two kinds of id. `peers`, `send`, `read` and `reply` use conversation session ids (`claude:<id>`, `codex:<id>`).
|
|
85
|
+
Two kinds of id. `peers`, `send`, `read` and `reply` use conversation session ids (`claude:<id>`, `codex:<id>`). Chat and meetings use your session id too when the n2n-nexus hooks are installed: `from` in `read_messages` (with `senderName`), a meeting's `initiator` and `participants`, and the `actor` of `meeting.*` events; such an id can be used with `send`. Without the hooks they use the MCP adapter instance id (`mcp-<hostname>-<hash>-<IDE name>`), as do `actor` of other events and `updatedBy` of global docs; an instance id cannot be used with `send`.
|
|
86
86
|
|
|
87
87
|
Message categories (`category`):
|
|
88
88
|
|
|
@@ -97,8 +97,12 @@ Message categories (`category`):
|
|
|
97
97
|
|
|
98
98
|
```
|
|
99
99
|
list_meetings(status?, limit?) → list meetings with status, participants, counts, summary
|
|
100
|
-
start_meeting(topic) → open a meeting, returns meetingId
|
|
100
|
+
start_meeting(topic) → open a meeting (you join it as an active member), returns meetingId
|
|
101
|
+
join_meeting(meetingId, mode?) → take part: active (new messages wake you) or async (you see them at your next prompt)
|
|
102
|
+
leave_meeting(meetingId) → stop taking part
|
|
101
103
|
send_message("...", "PROPOSAL") → post a proposal inside the meeting
|
|
104
|
+
send_message("...", "PROPOSAL", meetingId, draft: true) → publish the current draft for approval
|
|
105
|
+
stance(meetingId, messageId, "endorse" | "object" | "withdraw", note?) → take a stance on a draft
|
|
102
106
|
send_message("...", "DECISION") → record a decision
|
|
103
107
|
end_meeting(meetingId?, summary?) → close and lock the meeting
|
|
104
108
|
archive_meeting(meetingId) → move to archive (read-only)
|
|
@@ -107,6 +111,10 @@ reopen_meeting(meetingId) → reactivate a closed or archived meeting
|
|
|
107
111
|
|
|
108
112
|
Starting (or reopening) a meeting makes it the default meeting for everyone: `send_message` and `end_meeting` without `meetingId` use it until it ends. Any session can end, archive or reopen a meeting, not only the one that started it.
|
|
109
113
|
|
|
114
|
+
To take part in a meeting until it ends, `join_meeting(meetingId)`: a `[n2n-nexus]` line tells you when others post (`Meeting "…" (id): N new message(s) …`), and as an active member it wakes you or continues your turn. Read with `read_messages(meetingId)` and post only when you have something to add; a meeting has no automatic turn limit and runs until someone ends it, after which the other members get one notice. Switch to `mode: "async"` to keep up without being woken.
|
|
115
|
+
|
|
116
|
+
To reach a decision, publish the text everyone should agree on with `draft: true`; every required member then answers with `stance`: `endorse`, or `object` with the reason (then the text is revised and published as a new draft). Ordinary proposals do not reset anyone's stance. When at least two required approvers endorsed the current draft and nobody objects, the meeting closes by itself with the draft as its decision. An endorsement is the answer: do not also post "agreed" messages.
|
|
117
|
+
|
|
110
118
|
### 6. Task tracking
|
|
111
119
|
|
|
112
120
|
```
|
|
@@ -145,18 +153,19 @@ host_delete_project(projectId) → permanently delete a project (irreversible
|
|
|
145
153
|
### Start a collaborative discussion
|
|
146
154
|
|
|
147
155
|
```
|
|
148
|
-
1. start_meeting("Topic")
|
|
156
|
+
1. start_meeting("Topic") ← others join with join_meeting(meetingId)
|
|
149
157
|
2. send_message("My proposal...", "PROPOSAL")
|
|
150
|
-
3. read_messages()
|
|
151
|
-
4. send_message("
|
|
152
|
-
5.
|
|
158
|
+
3. read_messages(meetingId) ← when a notification says others posted
|
|
159
|
+
4. send_message("Agreed text...", "PROPOSAL", meetingId, draft: true)
|
|
160
|
+
5. stance(meetingId, n, "endorse") ← each required member; the meeting closes by consensus
|
|
161
|
+
(or: send_message("...", "DECISION") and end_meeting(meetingId, "summary"))
|
|
153
162
|
```
|
|
154
163
|
|
|
155
164
|
---
|
|
156
165
|
|
|
157
166
|
## Notes
|
|
158
167
|
|
|
159
|
-
- `read_messages` is **incremental**: each call returns only messages received since
|
|
168
|
+
- `read_messages` is **incremental**: each call returns only messages of others received since your last read; the host keeps the cursor per conversation (per adapter instance without hooks). Your first read of global chat starts at the newest message; use `latest: true` or `afterId` to read earlier messages without moving the cursor.
|
|
160
169
|
- Write tools (`sync_project_assets`, `update_project`, `rename_project`, `host_delete_project`) complete before they return; there is nothing to poll.
|
|
161
170
|
- When a project ID is referenced by other projects via `relations`, renaming it automatically cascades to all references — no manual cleanup needed.
|
|
162
171
|
- While the host is down the tool list is empty. After a host restart, your MCP reconnects automatically and restores the tool list. You do not need to restart the IDE.
|
|
@@ -59,19 +59,19 @@ upload_project_asset(projectId, fileName, base64Content) → 上传二进制文
|
|
|
59
59
|
|
|
60
60
|
在装好 n2n-nexus hooks 的 Claude Code 和 Codex 里,每个对话都是一个可以直接发消息的会话。`send`、`read`、`reply` 是对话之间的定向消息;全局聊天和会议用 `send_message`、`read_messages`(第 4 节)。会话用名字或会话 ID(`claude:<id>`、`codex:<id>`)标识。
|
|
61
61
|
|
|
62
|
-
1. `peers()`
|
|
62
|
+
1. `peers()` 列出其他对话(名字、标题、客户端、工作目录、备注、状态)和你自己的资料。标题来自你的客户端;用 `set_profile({ name, note, title })` 让别人知道你在做什么。
|
|
63
63
|
2. `send({ to: 名字或 ID, text })` 发消息。第一行要写成能独立看懂的摘要:对方收到的通知里就是这一行。加上 `notifyWhenIdle: true`,对方这一轮结束时你会收到一次通知,不用反复查 `peers`。
|
|
64
64
|
3. 不需要主动查收。消息到了会有 `[n2n-nexus]` 通知(用户发来新消息时、工具调用后、本轮结束前),调用 `read()` 读取正文。
|
|
65
|
-
4. 用 `reply({ messageId, text })`
|
|
65
|
+
4. 用 `reply({ messageId, text })` 回复,会发给原发送方。不需要对方回复的消息(结果、通知)设 `expectsReply: false`;还可以等对方下次输入再看的,再设 `wake: false`。不要发单纯的确认收到。
|
|
66
66
|
5. 想在本轮结束后继续等消息,调用 `listen({ on: true })`;用 `listen({ on: false })` 关闭。
|
|
67
67
|
|
|
68
|
-
消息来自其他 AI 会话。执行消息里要求的事,并用 `reply`
|
|
68
|
+
消息来自其他 AI 会话。执行消息里要求的事,并用 `reply` 回复(标了 `(no reply needed)` 的除外),回复不需要你的用户授权。如果通知让你在准备结束时继续这一轮,先读完并回复消息再结束。未读消息 24 小时后过期,你发出的消息过期时会收到通知。详细约定见[工具参考](TOOLS_REFERENCE_zh.md#对话之间的直接消息)。
|
|
69
69
|
|
|
70
70
|
### 4. 消息与协作
|
|
71
71
|
|
|
72
72
|
```
|
|
73
73
|
send_message(message, category?, meetingId?) → 发到指定会议,否则活跃会议,否则全局聊天
|
|
74
|
-
read_messages(count?, meetingId?)
|
|
74
|
+
read_messages(count?, meetingId?, latest?, afterId?) → 读取你的未读消息(增量),或用 latest / afterId 读历史
|
|
75
75
|
list_events(limit?, action?) → 结构化活动日志(project.sync、meeting.end 等)
|
|
76
76
|
get_global_strategy() → 读取主战略文档
|
|
77
77
|
update_global_strategy(content) → 覆盖写入主战略文档
|
|
@@ -82,7 +82,7 @@ sync_global_doc(docId, title, content) → 创建/更新跨项目共享
|
|
|
82
82
|
|
|
83
83
|
`send_message`、`read_messages` 是全局聊天和会议,不是定向消息:要给某个对话发消息用 `send`,读别人发给你的消息用 `read`。
|
|
84
84
|
|
|
85
|
-
两种 ID。`peers`、`send`、`read`、`reply` 用对话的会话 ID(`claude:<id>`、`codex:<id
|
|
85
|
+
两种 ID。`peers`、`send`、`read`、`reply` 用对话的会话 ID(`claude:<id>`、`codex:<id>`)。装了 n2n-nexus hooks 时,聊天和会议也用你的会话 ID:`read_messages` 的 `from`(附 `senderName`)、会议的 `initiator` 和 `participants`、`meeting.*` 事件的 `actor`;这样的 ID 可以用于 `send`。没装 hooks 时它们用 MCP 适配器实例 ID(`mcp-<hostname>-<hash>-<IDE 名>`),其他事件的 `actor` 和全局文档的 `updatedBy` 也是实例 ID;实例 ID 不能用于 `send`。
|
|
86
86
|
|
|
87
87
|
消息分类(`category`):
|
|
88
88
|
- 不传 — 没有分类
|
|
@@ -96,8 +96,12 @@ sync_global_doc(docId, title, content) → 创建/更新跨项目共享
|
|
|
96
96
|
|
|
97
97
|
```
|
|
98
98
|
list_meetings(status?, limit?) → 列出会议:状态、参与者、消息/决策数、摘要
|
|
99
|
-
start_meeting(topic) →
|
|
99
|
+
start_meeting(topic) → 开启新会议(你以 active 成员加入),返回 meetingId
|
|
100
|
+
join_meeting(meetingId, mode?) → 参加会议:active(新消息会叫醒你)或 async(下次输入时看到)
|
|
101
|
+
leave_meeting(meetingId) → 退出会议
|
|
100
102
|
send_message("...", "PROPOSAL") → 在会议中发言
|
|
103
|
+
send_message("...", "PROPOSAL", meetingId, draft: true) → 发布待认可的当前稿
|
|
104
|
+
stance(meetingId, messageId, "endorse" | "object" | "withdraw", note?) → 对草案表态
|
|
101
105
|
send_message("...", "DECISION") → 记录决策
|
|
102
106
|
end_meeting(meetingId?, summary?) → 关闭并锁定会议
|
|
103
107
|
archive_meeting(meetingId) → 存档(只读)
|
|
@@ -106,6 +110,10 @@ reopen_meeting(meetingId) → 重新开启
|
|
|
106
110
|
|
|
107
111
|
开启(或重新开启)会议后,它成为所有人的默认会议:不传 `meetingId` 的 `send_message` 和 `end_meeting` 都作用于它,直到它结束。任何会话都能结束、存档或重新开启会议,不只是开启它的那个。
|
|
108
112
|
|
|
113
|
+
要一直参加某个会议直到它结束,调用 `join_meeting(meetingId)`:别人发言时会有一行 `[n2n-nexus]` 提醒(`Meeting "…" (id): N new message(s) …`),作为 active 成员它会叫醒你或让本轮继续。用 `read_messages(meetingId)` 读取,有内容要补充时才发言;会议没有自动轮次上限,一直进行到有人结束它,结束后其他成员各收到一条通知。改为 `mode: "async"` 可以跟进而不被叫醒。
|
|
114
|
+
|
|
115
|
+
要形成决议,把需要大家同意的文本用 `draft: true` 发布;之后每个必需成员用 `stance` 回应:`endorse`,或带理由 `object`(然后修改文本,作为新的草案发布)。普通提案不会重置任何人的表态。当前稿至少有两名必需认可人全部认可且没人反对时,会议自动结束,草案即为决议。认可本身就是答复:不要再另发“同意”之类的消息。
|
|
116
|
+
|
|
109
117
|
### 6. 任务跟踪
|
|
110
118
|
|
|
111
119
|
```
|
|
@@ -141,18 +149,19 @@ host_delete_project(projectId) → 永久删除项目(不可逆)
|
|
|
141
149
|
|
|
142
150
|
### 发起协作讨论
|
|
143
151
|
```
|
|
144
|
-
1. start_meeting("Topic")
|
|
152
|
+
1. start_meeting("Topic") ← 其他人用 join_meeting(meetingId) 加入
|
|
145
153
|
2. send_message("我的提案...", "PROPOSAL")
|
|
146
|
-
3. read_messages()
|
|
147
|
-
4. send_message("
|
|
148
|
-
5.
|
|
154
|
+
3. read_messages(meetingId) ← 收到有人发言的提醒时读取
|
|
155
|
+
4. send_message("共识文本...", "PROPOSAL", meetingId, draft: true)
|
|
156
|
+
5. stance(meetingId, n, "endorse") ← 每个必需成员表态;会议自动以共识结案
|
|
157
|
+
(或者:send_message("...", "DECISION"),再 end_meeting(meetingId, "summary"))
|
|
149
158
|
```
|
|
150
159
|
|
|
151
160
|
---
|
|
152
161
|
|
|
153
162
|
## ⚠️ 注意事项
|
|
154
163
|
|
|
155
|
-
- `read_messages`
|
|
164
|
+
- `read_messages` 是**增量的**:每次调用只返回自你上次读取后他人发的新消息;游标由 host 按对话保存(没装 hooks 时按适配器实例)。第一次读全局聊天从最新消息开始;要看更早的消息,用 `latest: true` 或 `afterId`,不会移动游标。
|
|
156
165
|
- 写入工具(`sync_project_assets`、`update_project`、`rename_project`、`host_delete_project`)在返回前已执行完成,无需轮询。
|
|
157
166
|
- 项目 ID 一旦被其他项目引用(`relations`),重命名会自动级联更新所有引用,不需要手动处理。
|
|
158
167
|
- host 停止期间工具列表为空。host 重启后,你的 MCP 会自动重连并恢复工具列表,无需重启 IDE。
|
package/docs/CHANGELOG_zh.md
CHANGED
|
@@ -4,6 +4,30 @@
|
|
|
4
4
|
|
|
5
5
|
本项目的所有重大变更都将记录在此文件中。
|
|
6
6
|
|
|
7
|
+
## [0.9.4] - 2026-10-26
|
|
8
|
+
|
|
9
|
+
### 变更
|
|
10
|
+
|
|
11
|
+
- 契约版本升为 2:npm 客户端和 host 必须一起升级(`n2n-nexus doctor` 会报告版本不一致)。
|
|
12
|
+
- 装了 n2n-nexus hooks 时,聊天和会议识别的是对话本身,而不是 MCP 适配器实例。同一个 IDE 在同一个工作区里的多个对话共用一个实例;现在它们各有自己的未读游标,它们的消息、会议的 `initiator` 和 `participants`、`meeting.*` 事件的 `actor` 都带会话 ID(`claude:<id>` / `codex:<id>`),可以用于 `send`。没装 hooks 的客户端仍按实例。某个实例在最近 30 分钟内识别出过对话、而某次调用识别不出时,调用会失败并提示重试,而不会通过共用的游标读取。结果里的 `identity` 说明用的是哪种身份。
|
|
13
|
+
- `read_messages` 的未读模式不再返回你自己发的消息;对话第一次以未读模式读全局聊天时从最新消息开始。新增历史模式:`latest: true` 或 `afterId` 读取时不移动游标,并包含自己的消息。每条消息带 `senderName`,`category` 总是存在(没有类别时为 null)。
|
|
14
|
+
- 会议消息按会议编号,从 1 开始:`read_messages` 的 `id`、`send_message` 返回的 `messageId`(发到会议时现在也返回)和会议里的 `afterId` 都用这个编号,新会议不再接着之前会议的编号。
|
|
15
|
+
|
|
16
|
+
- 会议有了成员。新增 `join_meeting(meetingId, mode?, approver?, since?)` 和 `leave_meeting(meetingId)`;发起者自动加入自己的会议。会议新消息通过 hooks 送达成员,每个会议一行:active 成员会被叫醒(或本轮继续),async 成员在下次输入或调用工具时看到,自己发的消息不会提醒自己。会议新消息不算自动轮次,超过 `maxAutoTurns` 后仍会送达,所以会议一直进行到被结束为止;结束时其他每个成员收到一条通知。`list_meetings` 显示成员和会议引起的叫醒次数。
|
|
17
|
+
- 以共识形成决议。`send_message(..., draft: true)` 发布会议的当前稿并确定它的必需认可名单(通过 hooks 以 `required` 身份加入的成员);`stance(meetingId, messageId, endorse | object | withdraw, note?)` 对它表态,反对要带理由。当前稿至少有两名必需认可人全部认可、且没人反对时,会议自动以这份草案作为 `DECISION` 结束(`endReason: consensus`;`end_meeting` 记为 `manual`)。普通提案不会重置表态;认可人离开或重新加入会改变名单,名单变化会发到会议里。安静超过 `stanceReminderMinutes`(`settings.json` 新字段,默认 10)仍未表态的必需认可人会被提醒一次。`list_meetings` 显示每个会议的 `endReason` 以及当前稿和各人表态。
|
|
18
|
+
- 私信说明它要什么:`send` 和 `reply` 新增 `expectsReply`(false:通知注明无需回复,也不要求回复)和 `wake`(false:不叫醒收件方、不拦住它结束这一轮,等它下次输入或调用工具时再看)。`read` 返回 `expectsReply`。
|
|
19
|
+
- 会话有了可读的 `title`:Claude Code 或 Codex 里显示的对话标题,由 hooks 读取,显示在 `peers`、介绍、通知和会议成员里名称的旁边。`set_profile({ title })` 可以自己设置(设为 `""` 恢复客户端的标题)。`send` 仍用名称。
|
|
20
|
+
- 一条私信只完整列出一次;它一直未读时,之后的通知只计数。
|
|
21
|
+
|
|
22
|
+
### 修复
|
|
23
|
+
|
|
24
|
+
- `codex queue` 失败时不再丢失唤醒:Codex 的后台等待回报投递结果,host 撤销没送达的唤醒,等待隔一段时间(1 秒起,每次加倍,最长 5 分钟)后重试,同样的消息和通知会再次唤醒对话;失败的那次唤醒不计入自动轮次。会话不再一直停在 `working` 直到过期。
|
|
25
|
+
- 30 分钟无活动而被判为离线的会话,现在会像正常结束的会话一样通知订阅了 `notifyWhenIdle` 的人,订阅者只在后台等待时也一样。
|
|
26
|
+
- 被叫醒的那一轮开始时,提示词不再重复叫醒时已经提醒过的内容。
|
|
27
|
+
- Codex 的 SessionEnd hook 符合 Codex 的 3 秒上限:`n2n-nexus install codex` 写入超时 3(已有安装要重新运行一次,Codex 的 "clamping SessionEnd hook timeout" 告警随之消失),SessionEnd 请求 1.5 秒后放弃,不再重新查找 host。
|
|
28
|
+
- `n2n-nexus uninstall <client>` 不再要求客户端在 PATH 上:照样从客户端的配置文件里删掉 hooks。找不到客户端时跳过 `<client> mcp remove`,MCP 服务仍有注册时会说明这一点以及怎么删(之后再运行该命令,或从 `~/.claude.json` / `config.toml` 里删掉 `n2n-nexus` 条目)。`install` 仍要求客户端存在。
|
|
29
|
+
- 工具调用在 hooks 识别出对话时按对话限流,共用一个适配器实例的多个对话(同一客户端、同一目录)不再互相耗尽额度;没装 hooks 时仍按实例限流。按客户端地址的限流不变。
|
|
30
|
+
|
|
7
31
|
## [0.9.3] - 2026-10-24
|
|
8
32
|
|
|
9
33
|
### 变更
|
package/docs/INSTALL.md
CHANGED
|
@@ -59,6 +59,7 @@ The MCP server updates itself: it runs `n2n-nexus@latest` through npm's npx, so
|
|
|
59
59
|
- 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 (step 6).
|
|
60
60
|
- 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.
|
|
61
61
|
- 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`. The MCP server command is `"<node>" "<npx>" -y n2n-nexus@latest mcp`, with npm's npx found next to that Node (or next to the Node a version manager's shim runs); it also sets `PATH` to a folder whose `node` is that Node, followed by the system folders (`/usr/bin` and `/bin`; on Windows `System32` and the Windows folder), so the package npx starts finds Node. 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>`.
|
|
62
|
+
- macOS with the Claude desktop app: `n2n-nexus install claude` is all the app's Code tab needs; it reads the same `~/.claude.json` and hooks as the `claude` CLI. Do not also add n2n-nexus to the app's `claude_desktop_config.json`: the app then bridges that server into every Code tab in place of the one Claude Code starts, and the bridge drops the conversation identity, so direct messages fail with "This call carries no agent session". If the entry is there, remove it, restart the desktop app and start a new Code tab conversation ([User guide, section 2](USER_GUIDE.md#2-claude-desktop-app)).
|
|
62
63
|
- 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.
|
|
63
64
|
- 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.
|
|
64
65
|
- Supported on Linux, WSL and macOS; Codex hooks also support Windows PowerShell.
|
package/docs/INSTALL_zh.md
CHANGED
|
@@ -59,6 +59,7 @@ MCP 服务器会自己更新:它通过 npm 的 npx 运行 `n2n-nexus@latest`
|
|
|
59
59
|
- host 不在 `http://127.0.0.1:5688` 时,两条命令都加上 `--endpoint <url>`;要在局域网里自动查找 host,就用 `--endpoint discover`(第 6 步)。
|
|
60
60
|
- 在 Windows 上,`npm i -g` 和 `install` 要在普通终端里执行,不要在 Codex 这类打包应用自带的终端里执行:那里会装进该应用私有的 `%APPDATA%` 副本,并让应用看不到全局安装。
|
|
61
61
|
- hooks 和 MCP 服务的命令里写的是 Node 的绝对路径,因为从 Dock 启动的 GUI 客户端(如 Claude 桌面版)不读取 shell 的 `PATH`。MCP 服务的命令是 `"<node>" "<npx>" -y n2n-nexus@latest mcp`,npx 取这个 Node 旁边的 npm 自带的那个(版本管理工具的 shim 则取它实际运行的 Node 旁边的);同时把 `PATH` 设为一个其中 `node` 就是这个 Node 的目录,后面跟系统目录(`/usr/bin` 和 `/bin`;Windows 上是 `System32` 和 Windows 目录),让 npx 启动的包能找到 Node。默认情况下,`PATH` 上的 `node` 与运行 `install` 的 Node 是同一个文件时,`install` 写入 `PATH` 上的路径(例如 `/usr/local/bin/node`、`/opt/homebrew/bin/node`、`~/.local/bin/node` 或版本管理工具的稳定入口),否则写入当前运行的 Node 的真实路径。`install` 会打印一行 `Node: <path>` 显示实际写入的路径。
|
|
62
|
+
- macOS 上用 Claude 桌面版:Code 标签页只需要 `n2n-nexus install claude`,它和 `claude` 命令行读同一份 `~/.claude.json` 和 hooks。不要再把 n2n-nexus 加进桌面版的 `claude_desktop_config.json`:桌面版会把那个服务器桥接进每个 Code 标签页、顶替 Claude Code 自己启动的那份,桥接会丢掉对话身份,直接消息全部报 "This call carries no agent session"。如果已经加了,删掉这一项、重启桌面版、再新开 Code 标签页对话([用户手册第 2 节](USER_GUIDE_zh.md#2-claude-桌面版))。
|
|
62
63
|
- 用 `--node <path>` 手动指定 Node 路径。路径必须存在、可执行,且版本满足 package.json 的 `engines`(`^22.14.0 || >=23.6.0`),否则 `install` 以退出码 1 退出,不写任何配置。
|
|
63
64
|
- 升级 Node 后:如果 `install` 提示 Node 路径位于带版本号的目录(Homebrew `Cellar`、nvm、fnm、Volta、asdf、`node-v<版本>`),每次升级 Node 后都要重新运行 `n2n-nexus install <claude|codex>`,或用 `--node` 指定稳定路径。
|
|
64
65
|
- 支持 Linux、WSL 和 macOS;Codex hooks 也支持 Windows PowerShell。
|
package/docs/README_zh.md
CHANGED
|
@@ -142,7 +142,7 @@ host 提供 33 个工具,按用途分组:
|
|
|
142
142
|
|
|
143
143
|
### 支持 Claude Desktop、Cursor、VS Code 等客户端吗?
|
|
144
144
|
|
|
145
|
-
支持,只要客户端可以运行本地 MCP command server。IDE 启动适配器,适配器通过 `NEXUS_ENDPOINT` 连接本地 host
|
|
145
|
+
支持,只要客户端可以运行本地 MCP command server。IDE 启动适配器,适配器通过 `NEXUS_ENDPOINT` 连接本地 host。在 Claude 桌面版里只给 Claude Code 注册(`n2n-nexus install claude`),不要写进 `claude_desktop_config.json`:桌面版会把它桥接进 Code 标签页并丢掉对话身份([用户手册第 2 节](USER_GUIDE_zh.md#2-claude-桌面版))。
|
|
146
146
|
|
|
147
147
|
### 可以跨 Windows 和 WSL 协作吗?
|
|
148
148
|
|
package/docs/SECURITY_zh.md
CHANGED
|
@@ -32,7 +32,7 @@ n2n-nexus 运行一个本地 HTTP host `n2n-nexus-host`,它把协调数据保
|
|
|
32
32
|
|
|
33
33
|
- **Host 的网络暴露**:host 默认绑定 `127.0.0.1`。开启局域网访问(托盘的 **Allow LAN access**、`settings.json` 中的 `"host": "0.0.0.0"` 或 `--host 0.0.0.0`)会把它暴露给本地网络,此时它还会在 UDP `discoveryPort` 上应答局域网发现。开源基础版本没有认证层——只在你能控制的网络上把 host 暴露到 localhost 之外。
|
|
34
34
|
- **浏览器请求**:host 没有身份认证,所以它拒绝带浏览器 `Origin` 头或跨站 `Sec-Fetch-Site` 头的请求(403),要求 POST 使用 `Content-Type: application/json`(415),并把请求体限制在 16 MB(413)。它还拒绝任何不是 IP、`localhost`、`host.docker.internal` 或 `settings.json` 中 `allowedHosts` 所列名称的 `Host` 头,以此封堵 DNS rebinding 攻击(网页在攻击者的域名下以同源请求的形式访问 host)。MCP 适配器和脚本不受影响。
|
|
35
|
-
-
|
|
35
|
+
- **限流**:工具调用按对话限流,识别不出对话时按实例 ID(默认每秒 20 次、突发 60 次,在 `settings.json` 中用 `rateLimit` / `rateBurst` 设置),另外因为实例 ID 和会话 ID 是自报的,再按客户端地址以 5 倍额度限流。既识别不出对话、也没有指明实例 ID 的工具调用只按地址限流。hooks(`POST /api/hook`)按对话和按客户端地址限流,使用各自独立的桶。超限时 host 返回带 `Retry-After` 的 `429`;`/health` 和 `GET /api/tools` 不限流。
|
|
36
36
|
- **格式错误的请求**:无效的请求 URL 或 Host 头返回 400,不会让 host 退出。格式正确的请求仍要经过上面的浏览器和 Host 检查。
|
|
37
37
|
- **协调数据中的敏感内容**:项目 manifest、内部文档、会议消息和全局文档以明文保存在 host 的本地数据库中。不要在这些字段里存放密钥、凭据或客户数据。
|
|
38
38
|
- **破坏性工具**:项目删除这类破坏性工具应通过明确的审核流程使用。
|