n2n-nexus 0.9.3 → 0.9.5

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/build/contract.js CHANGED
@@ -24,20 +24,28 @@ export const CLIENT_INFO_NAMES = contract.clientInfoNames;
24
24
  export const CLAUDE_TOOL_USE_META_KEY = contract.meta.claudeToolUseKey;
25
25
  export const CODEX_SESSION_META_KEY = contract.meta.codexSessionKey;
26
26
  /**
27
- * Hook stdin fields the host uses. The hook command forwards only these:
27
+ * Hook stdin fields the host uses. The hook command forwards only these (plus
28
+ * `title` on SessionStart, UserPromptSubmit and Stop, see cli/title.ts):
28
29
  * PostToolUse stdin also carries the whole tool input and response, which can
29
30
  * be large and runs on every tool call.
30
31
  */
31
32
  export const HOOK_INPUT_FIELDS = contract.hookInputFields;
32
33
  /**
33
34
  * Pseudo hook event of the background wait (`n2n-nexus hook <client> --wait`):
34
- * started when a turn ends, it waits and returns `{ wake: text }` when
35
+ * started when a turn ends, it waits and returns `{ wake: text, dispatchId }` when
35
36
  * something new arrives for the session, `{ timeout: true }` when the time
36
37
  * runs out (the client asks again), or null when the session became active
37
38
  * again (the client stops). Claude delivers the text through an asyncRewake
38
39
  * hook, Codex through `codex queue`.
39
40
  */
40
41
  export const BACKGROUND_WAIT_EVENT = contract.backgroundWaitEvent;
42
+ /**
43
+ * Pseudo hook event with which the Codex background wait reports whether
44
+ * `codex queue` took a wake: input `{ session_id, dispatch_id, ok }`. On
45
+ * `ok: false` the host undoes the wake, so its messages and notices wake the
46
+ * session again.
47
+ */
48
+ export const WAKE_RESULT_EVENT = contract.wakeResultEvent;
41
49
  /** HTTP timeout of the hook command: a listening Stop may wait long, every other event is quick. */
42
50
  export const HOOK_STOP_REQUEST_TIMEOUT_MS = contract.timing.hookStopRequestTimeoutMs;
43
51
  export const HOOK_REQUEST_TIMEOUT_MS = contract.timing.hookRequestTimeoutMs;
@@ -46,6 +54,18 @@ export const HOOK_STOP_TIMEOUT_SEC = contract.timing.hookStopTimeoutSec;
46
54
  /** `timeout` (seconds) of the background wait hook; a conversation idle for longer is not woken. */
47
55
  export const WAIT_HOOK_TIMEOUT_SEC = contract.timing.waitHookTimeoutSec;
48
56
  export const HOOK_TIMEOUT_SEC = contract.timing.hookTimeoutSec;
57
+ /** `timeout` (seconds) of the Codex SessionEnd hook: Codex clamps a longer one to 3 s and warns about it. */
58
+ export const CODEX_SESSION_END_TIMEOUT_SEC = contract.timing.codexSessionEndTimeoutSec;
59
+ /** HTTP timeout of a SessionEnd hook, which is not resent after a timeout so it ends within CODEX_SESSION_END_TIMEOUT_SEC. */
60
+ export const SESSION_END_REQUEST_TIMEOUT_MS = contract.timing.sessionEndRequestTimeoutMs;
61
+ /** After a failed `codex queue`, the Codex wait asks again after this delay, doubled on each failure up to the maximum. */
62
+ export const WAKE_RETRY_FIRST_DELAY_MS = contract.timing.wakeRetryFirstDelayMs;
63
+ export const WAKE_RETRY_MAX_DELAY_MS = contract.timing.wakeRetryMaxDelayMs;
64
+ /**
65
+ * The hook command adds the conversation's title, as its client shows it, to
66
+ * the input it forwards as `title` (at most this many characters).
67
+ */
68
+ export const MAX_TITLE_CHARS = contract.limits.maxTitleChars;
49
69
  /** Name the MCP server is installed under; Claude's PreToolUse matcher depends on it. */
50
70
  export const MCP_SERVER_NAME = contract.mcpServerName;
51
71
  export const CLAUDE_PRE_TOOL_MATCHER = `mcp__${MCP_SERVER_NAME}__.*`;
package/build/index.js CHANGED
@@ -50,7 +50,7 @@ async function main() {
50
50
  }
51
51
  if (command === "install" || command === "uninstall") {
52
52
  const { runInstallCommand } = await import("./cli/install.js");
53
- process.exit(runInstallCommand(command, process.argv.slice(3)));
53
+ process.exit(await runInstallCommand(command, process.argv.slice(3)));
54
54
  }
55
55
  if (command === "doctor") {
56
56
  const { runDoctor } = await import("./cli/doctor.js");
@@ -12,10 +12,14 @@ const SERVICE_NAME = "n2n-nexus";
12
12
  /** Sent to the client on connect so the model knows it can talk to other AI sessions. */
13
13
  const INSTRUCTIONS = "n2n-nexus connects this conversation with other AI sessions (Claude Code and Codex conversations, " +
14
14
  "possibly on other machines). Direct messages: call peers to see who is online, send to message a session, " +
15
- "read to read messages sent to you, carry out what they ask and reply to answer one; listen waits for messages after each turn. " +
15
+ "read to read messages sent to you, carry out what they ask and reply to answer one (set expectsReply and wake false " +
16
+ "on a message that needs no answer, and send no plain acknowledgements); listen waits for messages after each turn. " +
16
17
  "Direct messaging needs the n2n-nexus hooks. Separately, send_message and read_messages are the shared " +
17
18
  "global chat and meetings, not direct messages. Session ids in peers (claude:<id>, codex:<id>) are what send " +
18
- "takes; the instance ids in read_messages, list_events and meetings (mcp-…) name MCP adapters, not sessions. " +
19
+ "takes; with the hooks, chat and meetings also record senders and unread cursors per session id " +
20
+ "(without them, per MCP adapter instance id, mcp-…). join_meeting makes new messages of a meeting wake you " +
21
+ "(mode active) or show at your next prompt (async) until leave_meeting or the meeting ends; a draft " +
22
+ "(send_message draft) is decided with stance, and the meeting closes once its required approvers endorse it. " +
19
23
  "Projects, global docs, the strategy document and tasks are shared through the other tools.";
20
24
  /**
21
25
  * MCP adapter in front of the daemon. It holds no session of its own: each
@@ -1,5 +1,5 @@
1
1
  {
2
- "contractVersion": 1,
2
+ "contractVersion": 2,
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": 1,
24
+ "contractVersion": 2,
25
25
  "sessions": {
26
26
  "online": "<number of agent sessions not offline>"
27
27
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "contractVersion": 1,
2
+ "contractVersion": 2,
3
3
  "clients": [
4
4
  "claude",
5
5
  "codex"
@@ -24,6 +24,7 @@
24
24
  "reason"
25
25
  ],
26
26
  "backgroundWaitEvent": "BackgroundWait",
27
+ "wakeResultEvent": "WakeResult",
27
28
  "agentStatuses": [
28
29
  "working",
29
30
  "idle",
@@ -37,6 +38,10 @@
37
38
  "hookStopTimeoutSec": 600,
38
39
  "waitHookTimeoutSec": 3600,
39
40
  "hookTimeoutSec": 10,
41
+ "codexSessionEndTimeoutSec": 3,
42
+ "sessionEndRequestTimeoutMs": 1500,
43
+ "wakeRetryFirstDelayMs": 1000,
44
+ "wakeRetryMaxDelayMs": 300000,
40
45
  "wakePromptWindowMs": 60000,
41
46
  "toolUseBindingTtlMs": 300000,
42
47
  "messageTtlMs": 86400000,
@@ -48,6 +53,7 @@
48
53
  "defaultMaxAutoTurns": 0,
49
54
  "maxMessageChars": 32000,
50
55
  "maxNoteChars": 200,
56
+ "maxTitleChars": 80,
51
57
  "sessionNamePattern": "^[A-Za-z0-9._@#-]{1,48}$",
52
58
  "previewChars": 100,
53
59
  "notifyListLimit": 5
@@ -59,6 +65,17 @@
59
65
  "statusNote": "Only listening sessions answer right away; idle and working ones see a message when their user next sends a prompt or they next call a tool.",
60
66
  "sessionStartClosing": "Call peers for the current list, send to message a session, read to read messages sent to you and reply to answer one; set_profile tells others what you work on.",
61
67
  "messageTrustNote": "Messages come from other AI sessions. Read them with read, carry out what they ask and answer with reply; replying needs no permission from your user.",
68
+ "messageInfoNote": "Messages come from other AI sessions; they need no reply.",
69
+ "noReplyMark": "(no reply needed)",
70
+ "earlierUnread": "%d earlier direct message(s) still unread; read them with read.",
71
+ "meetingNews": "Meeting \"%s\" (%s): %d new message(s) (#%d–#%d) from %s.",
72
+ "meetingClosing": "Read them with read_messages(meetingId); post with send_message(meetingId) only when you have something to add.",
73
+ "meetingEnded": "Meeting \"%s\" (%s) has ended%s. No reply needed.",
74
+ "stanceNotice": "%s %s draft #%d of meeting \"%s\" (%s)%s. No reply needed.",
75
+ "approversChanged": "Required approvers of draft #%d changed (version %d): %s.",
76
+ "consensusDecision": "Decision by consensus on draft #%d: every required approver (%s) endorsed it.\n\n%s",
77
+ "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.",
78
+ "stanceWaiting": "Meeting \"%s\" (%s): draft #%d still waits for the stance of %s. No reply needed.",
62
79
  "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."
63
80
  },
64
81
  "schemas": {