n2n-nexus 0.11.0 → 0.12.1
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 +31 -0
- package/README.md +11 -5
- package/assets/n2n-nexus-logo.png +0 -0
- package/build/cli/machine.js +25 -4
- package/contract/errors.json +2 -2
- package/contract/hook.json +22 -3
- package/contract/tools.json +472 -34
- package/docs/ARCHITECTURE.md +14 -8
- package/docs/ARCHITECTURE_zh.md +13 -7
- package/docs/ASSISTANT_GUIDE.md +31 -3
- package/docs/ASSISTANT_GUIDE_zh.md +31 -3
- package/docs/CHANGELOG_zh.md +31 -0
- package/docs/README_zh.md +11 -5
- package/docs/TOOLS_REFERENCE.md +37 -17
- package/docs/TOOLS_REFERENCE_zh.md +37 -17
- package/docs/USER_GUIDE.md +3 -1
- package/docs/USER_GUIDE_zh.md +3 -1
- package/package.json +1 -1
- package/assets/n2n-nexus-banner.jpg +0 -0
- package/assets/n2n-nexus-icon.png +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,37 @@
|
|
|
4
4
|
|
|
5
5
|
All notable changes to this project will be documented in this file.
|
|
6
6
|
|
|
7
|
+
## [0.12.1] - 2026-10-29
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- New tray icon, also the README logo: a small network on a charcoal tile with a grey edge, a red node in the middle joined by light blue links to four white nodes. It replaces the teal disc in the tray and the hexagon logo; the old icon and banner files are gone from `assets`. On macOS the menu bar shows the network alone as a template image, black or white to suit the menu bar.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- A message the host posts because of your own call (the seat taken, the AI consensus notice, the seat's decision or veto) counts as read for you: the seat drafts the next point right after taking the seat or deciding one, and the member whose stance completed the AI consensus posts right after it, without a refusal for that message.
|
|
16
|
+
- Meeting news no longer repeats what `read_messages` returned in unread mode: reading a meeting also marks those messages announced, so the next hook notification shows only what came after.
|
|
17
|
+
- In a meeting with the user's seat, the message about a changed approver list no longer names the seat, which is in none of the approver lists.
|
|
18
|
+
|
|
19
|
+
## [0.12.0] - 2026-10-29
|
|
20
|
+
|
|
21
|
+
Upgrade the host and the client on every machine together (contract version 4). The database is upgraded in place: keep `nexus.db`.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- Meetings with the user's seat: the conversation the user sits at takes the seat with `seat: "user"` on `start_meeting` or `join_meeting`; taking it moves it from whoever held it. The seat's `endorse` of the current draft makes the host post the `DECISION` (with the objections still open) and the meeting goes on to the next point; its `object` vetoes the draft. The others' consensus decides nothing there and is only reported when it is reached or lost. Only the seat ends such a meeting, with a summary; anyone else's `end_meeting` is posted as a proposal to end it. `list_meetings` returns `seat`, and `approvals` the seat's stance.
|
|
26
|
+
- Cleanup by age, at startup and every hour, with two `settings.json` fields: `meetingRetentionDays` (default 30) and `retentionDays` (default 7). Read or expired direct messages, delivered notices, events, finished tasks, sessions without activity (unless a direct message still references them) and adapter instances not seen go after `retentionDays`; an active meeting without a message for `meetingRetentionDays` closes with `endReason: expired`, and a closed or archived meeting goes `meetingRetentionDays` after it ended. Each cleanup that changed something writes a `maintenance.cleanup` event.
|
|
27
|
+
- `host_maintenance` actions `cleanup` (with `dryRun` to see what would go) and `export` (a meeting as markdown).
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- Read before posting: `send_message` to a meeting, and `endorse` / `object`, are refused while messages of others are unread; the result attaches them (texts cut at `notifyTextChars`) and they count as read. The next post after a refusal goes through with a note of what was unread; a draft or a `DECISION` only once everything is read. `withdraw` goes through with the unread messages attached.
|
|
32
|
+
- Meeting notifications show each message with its text, like direct messages; what a wake, a Stop or a prompt shows whole counts as read.
|
|
33
|
+
- An objection, and a stance that changes your last one, need grounds in the note (`#n`, a file path, a URL, or a sentence starting with 实测:/文档:/数据: or Measured:/Docs:/Data:); a stance on a proposal can change at most twice.
|
|
34
|
+
- Meeting messages, global chat and meeting members keep the sender's name and machine as they were when written; `read_messages` returns `senderBackfilled` and members `machine` and `backfilled` for the rows of older databases, filled in at the upgrade.
|
|
35
|
+
- Sessions report their environment (the WSL distribution, shown after the platform: `Vector · WSL Ubuntu_26`); on macOS the hostname is the computer name set in System Settings. A session is named `<client>@<dir>#<the last 4 letters and digits of its id>`, with more of the id while another recorded session has that name, so names are not reused.
|
|
36
|
+
- The tray menu's first line says only whether the host runs (`Running`, `Running · background service` or `Not running`); the address is on its own line, and the error takes that line when no host answers. The `(LAN)` suffix is gone: the **Allow LAN access** item shows the same.
|
|
37
|
+
|
|
7
38
|
## [0.11.0] - 2026-10-29
|
|
8
39
|
|
|
9
40
|
Upgrade the host and the client on every machine together, and delete `nexus.db` from the host's data directory before the new host starts: this release starts a new database and contract version, and an older host or client no longer works with it.
|
package/README.md
CHANGED
|
@@ -11,7 +11,6 @@ Local-first MCP coordination hub from N2NS Lab for multi-AI assistant collaborat
|
|
|
11
11
|
[](https://github.com/n2ns/n2n-nexus/blob/main/LICENSE)
|
|
12
12
|
[](https://modelcontextprotocol.io)
|
|
13
13
|
[](https://nodejs.org)
|
|
14
|
-
[](https://datafrog.io)
|
|
15
14
|
|
|
16
15
|
[中文版](./docs/README_zh.md)
|
|
17
16
|
|
|
@@ -32,6 +31,7 @@ n2n-nexus is an open-source Model Context Protocol (MCP) coordination server for
|
|
|
32
31
|
- [Real-world example](#-real-world-example)
|
|
33
32
|
- [FAQ](#-faq)
|
|
34
33
|
- [Documentation](#-documentation)
|
|
34
|
+
- [License](#-license)
|
|
35
35
|
|
|
36
36
|
## 🔎 Looking for
|
|
37
37
|
|
|
@@ -62,8 +62,10 @@ n2n-nexus gives AI assistants a shared coordination workspace. A long-running lo
|
|
|
62
62
|
## 🎯 Why use it?
|
|
63
63
|
|
|
64
64
|
- Coordinate multiple AI assistants working on the same project.
|
|
65
|
+
- Let Claude Code and Codex conversations message each other directly: a message arrives whole through the client's hooks, wakes a waiting conversation, and is answered with `reply`; each session shows the machine and environment it runs on.
|
|
66
|
+
- Hold meetings among assistants and keep their decisions. When you take part, your conversation holds the user's seat: you decide each draft and end the meeting, and the assistants' consensus is only reported.
|
|
65
67
|
- Share project manifests, decisions, and assets across IDEs and machines.
|
|
66
|
-
- Keep decisions, proposals, and updates in a local meeting and message log.
|
|
68
|
+
- Keep decisions, proposals, and updates in a local meeting and message log; old data is cleaned up after the retention you set.
|
|
67
69
|
- Run the host once; connect adapters from Windows, WSL, SSH hosts, or multiple editors.
|
|
68
70
|
- Avoid giving each assistant broad filesystem or backend access.
|
|
69
71
|
|
|
@@ -85,7 +87,7 @@ n2n-nexus gives AI assistants a shared coordination workspace. A long-running lo
|
|
|
85
87
|
|
|
86
88
|
- **The host is the source of truth**: install it once and keep it running, as a tray app or as a background service.
|
|
87
89
|
- **MCP adapters are stateless**: each IDE starts an adapter (`n2n-nexus mcp`), which forwards tool calls to the host.
|
|
88
|
-
- **Every Claude Code and Codex conversation has its own identity**: client hooks register the conversation with the host and
|
|
90
|
+
- **Every Claude Code and Codex conversation has its own identity**: client hooks register the conversation with the host and deliver new direct messages and meeting messages to it; each tool call names its conversation through the request metadata the client attaches, never through model-supplied arguments.
|
|
89
91
|
- **Cross-environment by design**: point `NEXUS_ENDPOINT` at the same host from different machines or shells. The host listens on `127.0.0.1` by default; turn on LAN access only on a trusted network.
|
|
90
92
|
|
|
91
93
|
## 🚀 Quick start
|
|
@@ -107,9 +109,9 @@ The host exposes 36 tools, grouped by purpose:
|
|
|
107
109
|
| **Global docs** | `list_global_docs`, `get_global_doc`, `sync_global_doc`, `get_global_strategy`, `update_global_strategy` | List, read, create and update shared global documents and the master strategy document. |
|
|
108
110
|
| **Messaging** | `send_message`, `read_messages`, `list_events` | Post meeting or global messages; read them and the activity log. |
|
|
109
111
|
| **Direct messages** | `peers`, `send`, `read`, `reply`, `listen`, `set_profile` | Find other Claude Code / Codex conversations, message them, read and answer what arrives, wait for messages, and name yourself. |
|
|
110
|
-
| **Meetings** | `list_meetings`, `start_meeting`, `join_meeting`, `leave_meeting`, `stance`, `end_meeting`, `archive_meeting`, `reopen_meeting` | List and open meetings; join (woken by new messages) or leave them; endorse or object to the current draft, which closes the meeting by consensus; close, archive, and reopen them. |
|
|
112
|
+
| **Meetings** | `list_meetings`, `start_meeting`, `join_meeting`, `leave_meeting`, `stance`, `end_meeting`, `archive_meeting`, `reopen_meeting` | List and open meetings; join (woken by new messages) or leave them; endorse or object to the current draft, which closes the meeting by consensus or, with the user's seat, is decided by the user; close, archive, and reopen them. |
|
|
111
113
|
| **Tasks** | `create_task`, `get_task`, `list_tasks`, `update_task`, `cancel_task` | Create, update and track tasks that assistants manage themselves. |
|
|
112
|
-
| **Maintenance** | `host_maintenance`, `host_delete_project` | Prune or clear the global chat stream; delete a project. |
|
|
114
|
+
| **Maintenance** | `host_maintenance`, `host_delete_project` | Prune or clear the global chat stream; run the retention cleanup now; export a meeting as markdown; delete a project. |
|
|
113
115
|
|
|
114
116
|
See [Tools reference](./docs/TOOLS_REFERENCE.md) for parameter schemas and call examples.
|
|
115
117
|
|
|
@@ -152,6 +154,10 @@ Yes. Run the host in one environment and point every adapter at it with `NEXUS_E
|
|
|
152
154
|
|
|
153
155
|
No cloud service is needed. Data stays in the configured local directory. Exposing the host to remote machines is your own network configuration.
|
|
154
156
|
|
|
157
|
+
### How long does it keep data?
|
|
158
|
+
|
|
159
|
+
Meetings for 30 days after they end, everything else (direct messages, events, finished tasks, inactive sessions) for 7 days; set `meetingRetentionDays` and `retentionDays` in the host's `settings.json`. `host_maintenance` exports a meeting as markdown before it goes ([User guide](docs/USER_GUIDE.md#settingsjson)).
|
|
160
|
+
|
|
155
161
|
### Is it a vector database or a source code indexer?
|
|
156
162
|
|
|
157
163
|
No. n2n-nexus coordinates project metadata, messages, meetings, docs, tasks and assets; it does no semantic code search.
|
|
Binary file
|
package/build/cli/machine.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Where this conversation runs, which the hook command adds to the input of
|
|
3
3
|
* every event it forwards as `machine`, so that other sessions can tell the machines apart
|
|
4
|
-
* (
|
|
4
|
+
* (sessions on different machines or WSL distributions often share a working directory).
|
|
5
5
|
* Node built-ins only: the hook command imports this on client events.
|
|
6
6
|
*/
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
7
8
|
import fs from "node:fs";
|
|
8
9
|
import os from "node:os";
|
|
9
10
|
/** darwin, linux, win32 as Node reports them; wsl for a Linux that runs under Windows. */
|
|
@@ -17,11 +18,31 @@ export function platformOf(platform = process.platform, readProcVersion = () =>
|
|
|
17
18
|
return "linux";
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
|
-
/**
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* The machine's name. On macOS the name set in System Settings (ComputerName):
|
|
23
|
+
* the host name there is a model name such as MacBook-Air that two Macs share,
|
|
24
|
+
* and it changes with the network. Elsewhere, and when that cannot be read, the
|
|
25
|
+
* host name without the `.local` suffix that macOS adds.
|
|
26
|
+
*/
|
|
27
|
+
export function hostnameOf(platform = process.platform, hostname = os.hostname(), readComputerName = () => execFileSync("scutil", ["--get", "ComputerName"], { encoding: "utf8", timeout: 1000, stdio: ["ignore", "pipe", "ignore"] })) {
|
|
28
|
+
if (platform === "darwin") {
|
|
29
|
+
try {
|
|
30
|
+
const name = readComputerName().trim();
|
|
31
|
+
if (name)
|
|
32
|
+
return name;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// fall back to the host name
|
|
36
|
+
}
|
|
37
|
+
}
|
|
22
38
|
return hostname.replace(/\.local$/i, "");
|
|
23
39
|
}
|
|
40
|
+
/** What tells environments that share the host name apart: the WSL distribution, else "". */
|
|
41
|
+
export function environmentOf(platform, env = process.env) {
|
|
42
|
+
return platform === "wsl" ? env.WSL_DISTRO_NAME ?? "" : "";
|
|
43
|
+
}
|
|
24
44
|
export function machineInfo(now = new Date()) {
|
|
45
|
+
const platform = platformOf();
|
|
25
46
|
// 0 - x, not -x: at UTC -x is -0, which JSON writes as 0.
|
|
26
|
-
return { hostname: hostnameOf(), platform:
|
|
47
|
+
return { hostname: hostnameOf(), platform, environment: environmentOf(platform), utcOffset: 0 - now.getTimezoneOffset() };
|
|
27
48
|
}
|
package/contract/errors.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"contractVersion":
|
|
2
|
+
"contractVersion": 4,
|
|
3
3
|
"errorBody": {
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"ok": true,
|
|
22
22
|
"service": "n2n-nexus",
|
|
23
23
|
"version": "<host version>",
|
|
24
|
-
"contractVersion":
|
|
24
|
+
"contractVersion": 4,
|
|
25
25
|
"sessions": {
|
|
26
26
|
"online": "<number of agent sessions not offline>"
|
|
27
27
|
}
|
package/contract/hook.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"contractVersion":
|
|
2
|
+
"contractVersion": 4,
|
|
3
3
|
"clients": [
|
|
4
4
|
"claude",
|
|
5
5
|
"codex"
|
|
@@ -78,12 +78,26 @@
|
|
|
78
78
|
"replyTool": "The reply tool is %s.",
|
|
79
79
|
"noReplyMark": "(no reply needed)",
|
|
80
80
|
"earlierUnread": "%d earlier direct message(s) still unread; read them with read.",
|
|
81
|
-
"meetingNews": "Meeting \"%s\" (%s): %d new message(s)
|
|
82
|
-
"
|
|
81
|
+
"meetingNews": "Meeting \"%s\" (%s): %d new message(s), #%d–#%d:",
|
|
82
|
+
"meetingMessageHeader": "--- Meeting message #%d from %s at %s%s ---",
|
|
83
|
+
"meetingMessageEnd": "--- End of meeting message #%d ---",
|
|
84
|
+
"meetingNotShown": "- %d more message(s) not shown (#%d–#%d).",
|
|
85
|
+
"meetingReadRest": "read_messages(meetingId) returns the meeting messages that are cut or not shown here.",
|
|
86
|
+
"meetingClosing": "Post with send_message(meetingId) only when you have something to add.",
|
|
87
|
+
"sentWithUnread": "[n2n-nexus: sent while %s were unread by the sender.]",
|
|
83
88
|
"meetingEnded": "Meeting \"%s\" (%s) has ended%s. No reply needed.",
|
|
84
89
|
"stanceNotice": "%s %s draft #%d of meeting \"%s\" (%s)%s. No reply needed.",
|
|
85
90
|
"approversChanged": "Required approvers of draft #%d changed (version %d): %s.",
|
|
86
91
|
"consensusDecision": "Decision by consensus on draft #%d: every required approver (%s) endorsed it.\n\n%s",
|
|
92
|
+
"seatSender": "user's seat (via %s)",
|
|
93
|
+
"seatTaken": "The user's seat is taken by %s: its stances on the current draft and its end_meeting are the user's decisions.",
|
|
94
|
+
"seatMoved": "The user's seat moved from %s to %s.",
|
|
95
|
+
"seatDecision": "Decision on draft #%d, approved by the user's seat (%s)%s.%s\n\n%s",
|
|
96
|
+
"seatOpenObjections": " Objections still open when it was approved: %s.",
|
|
97
|
+
"seatVeto": "Draft #%d was vetoed by the user's seat (%s): %s",
|
|
98
|
+
"aiConsensus": "AI consensus on draft #%d: every required approver besides the user's seat (%s) endorsed it. Only the user's seat decides it.",
|
|
99
|
+
"aiConsensusLost": "AI consensus on draft #%d is lost.",
|
|
100
|
+
"proposedEnd": "Proposed end of the meeting (only the user's seat ends it): %s",
|
|
87
101
|
"stanceReminder": "Meeting \"%s\" (%s) waits for your stance on draft #%d: call stance with meetingId and messageId %d, endorse or object (with a reason). No other reply needed.",
|
|
88
102
|
"stanceWaiting": "Meeting \"%s\" (%s): draft #%d still waits for the stance of %s. No reply needed.",
|
|
89
103
|
"noAgentSessionError": "This call carries no agent session, so direct messaging cannot tell who you are. It needs the n2n-nexus hooks (n2n-nexus install <claude|codex>, then a new conversation) and the MCP server registered as 'n2n-nexus'. If both are in place, call the tool again: the identity from the hook may have expired."
|
|
@@ -196,6 +210,10 @@
|
|
|
196
210
|
"win32"
|
|
197
211
|
]
|
|
198
212
|
},
|
|
213
|
+
"environment": {
|
|
214
|
+
"type": "string",
|
|
215
|
+
"description": "The WSL distribution, \"\" otherwise"
|
|
216
|
+
},
|
|
199
217
|
"utcOffset": {
|
|
200
218
|
"type": "integer",
|
|
201
219
|
"minimum": -1440,
|
|
@@ -205,6 +223,7 @@
|
|
|
205
223
|
"required": [
|
|
206
224
|
"hostname",
|
|
207
225
|
"platform",
|
|
226
|
+
"environment",
|
|
208
227
|
"utcOffset"
|
|
209
228
|
]
|
|
210
229
|
}
|