slashvibe-mcp 0.8.24 → 0.8.26
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/README.md +18 -11
- package/package.json +1 -1
- package/protocol/handle.js +6 -4
- package/store/api.js +46 -17
- package/tools/inbox.js +12 -1
- package/version.json +4 -6
package/README.md
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# slashvibe-mcp
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
between the people (and agents) you already work with — nobody has to be online at the
|
|
5
|
-
same time.
|
|
3
|
+
**Message people from Claude Code or Codex.**
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
Presence, DMs, and messages that wait across sessions, between the people (and agents)
|
|
6
|
+
you already work with — nobody has to be online at the same time.
|
|
8
7
|
|
|
9
8
|
## Install — one command
|
|
10
9
|
|
|
@@ -12,15 +11,23 @@ same time.
|
|
|
12
11
|
npx slashvibe-mcp
|
|
13
12
|
```
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
It configures your coding agent, opens GitHub to sign you in, and your GitHub username
|
|
15
|
+
becomes your @handle. About 30 seconds. **Then restart your coding session** so it loads
|
|
16
|
+
the /vibe server.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
config; the agent only picks up a new MCP server on start.) After the restart, run
|
|
20
|
-
`vibe init` if you weren't signed in during setup.
|
|
18
|
+
## Then talk normally
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
```
|
|
21
|
+
show me who's on /vibe
|
|
22
|
+
message @brightseth — what are you building?
|
|
23
|
+
did anyone message me?
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
No commands to learn. Your coding agent routes those to /vibe. If it ever asks you to
|
|
27
|
+
sign in, say `sign in to /vibe`.
|
|
28
|
+
|
|
29
|
+
**Works in:** Claude Code and Codex. Setup also writes Cursor's config; Cursor is not part
|
|
30
|
+
of the walked, proven path yet.
|
|
24
31
|
|
|
25
32
|
## Put waiting messages at the top of your next Claude session
|
|
26
33
|
|
package/package.json
CHANGED
package/protocol/handle.js
CHANGED
|
@@ -30,12 +30,14 @@ function canonicalHandle(value) {
|
|
|
30
30
|
if (typeof value !== 'string') return '';
|
|
31
31
|
let h = value.replace(INVISIBLE, '');
|
|
32
32
|
h = h.normalize ? h.normalize('NFC') : h;
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
// A handle is the GitHub login, and GitHub logins may contain '-'. Rewriting
|
|
34
|
+
// dashes to underscores made `vibe inbox synth-stan` look up synth_stan and
|
|
35
|
+
// render an empty thread (found live 2026-09-02). Keep the dash.
|
|
36
|
+
return h.trim().replace(/^@+/, '').trim().toLowerCase();
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
/** Platform-legal shape: ASCII letters, digits, underscore. Reject anything else. */
|
|
38
|
-
const isCanonicalHandle = (value) => /^[a-z0-9_]{1,39}$/.test(canonicalHandle(value));
|
|
39
|
+
/** Platform-legal shape: ASCII letters, digits, underscore, dash (GitHub logins). Reject anything else. */
|
|
40
|
+
const isCanonicalHandle = (value) => /^[a-z0-9_-]{1,39}$/.test(canonicalHandle(value));
|
|
39
41
|
|
|
40
42
|
/** True when two handles refer to the same principal label. */
|
|
41
43
|
const sameHandle = (a, b) => {
|
package/store/api.js
CHANGED
|
@@ -797,24 +797,53 @@ async function getRawInbox(handle) {
|
|
|
797
797
|
|
|
798
798
|
// Transform threads with unread messages into message-like objects
|
|
799
799
|
// for compatibility with notification system
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
800
|
+
const me = String(handle || '').toLowerCase();
|
|
801
|
+
const isMine = (m) => String(m?.from || '').toLowerCase() === me;
|
|
802
|
+
const candidates = threads.filter((t) => t.unread > 0 && t.last_message);
|
|
803
|
+
|
|
804
|
+
// "Waiting" means THEIR words. When the newest message is mine — I replied
|
|
805
|
+
// and their earlier message is still unread — the thread's last_message
|
|
806
|
+
// is my own send, and presenting it as "MESSAGE from @me" told a person
|
|
807
|
+
// their own words had arrived (Seth, 2026-09-02, the day of the invite).
|
|
808
|
+
// For those threads read the TAIL of the thread (the API pages oldest-first)
|
|
809
|
+
// and take the newest message of theirs. Bounded parallelism: the session-
|
|
810
|
+
// start hook runs this under a 4s deadline (codex P2 ×2 on #38).
|
|
811
|
+
const TAIL = 50;
|
|
812
|
+
const resolveWaiting = async (thread) => {
|
|
813
|
+
if (!isMine(thread.last_message)) return thread.last_message;
|
|
814
|
+
try {
|
|
815
|
+
const count = Number(thread.message_count);
|
|
816
|
+
const offset = Number.isFinite(count) && count > TAIL ? count - TAIL : 0;
|
|
817
|
+
const t = await request('GET', `/api/v2/threads/${encodeURIComponent(thread.id)}?limit=${TAIL}&offset=${offset}`);
|
|
818
|
+
const theirs = (t.messages || []).filter((m) => !isMine(m));
|
|
819
|
+
return theirs.length ? theirs[theirs.length - 1] : null;
|
|
820
|
+
} catch {
|
|
821
|
+
return null;
|
|
816
822
|
}
|
|
817
|
-
}
|
|
823
|
+
};
|
|
824
|
+
const CONCURRENCY = 6;
|
|
825
|
+
const resolved = new Array(candidates.length);
|
|
826
|
+
let next = 0;
|
|
827
|
+
await Promise.all(Array.from({ length: Math.min(CONCURRENCY, candidates.length) }, async () => {
|
|
828
|
+
while (next < candidates.length) { const i = next++; resolved[i] = await resolveWaiting(candidates[i]); }
|
|
829
|
+
}));
|
|
830
|
+
|
|
831
|
+
const unreadMessages = [];
|
|
832
|
+
candidates.forEach((thread, i) => {
|
|
833
|
+
const waiting = resolved[i];
|
|
834
|
+
if (!waiting) return; // nothing of theirs to show — not a waiting message
|
|
835
|
+
unreadMessages.push({
|
|
836
|
+
id: waiting.id,
|
|
837
|
+
from: waiting.from,
|
|
838
|
+
to: handle,
|
|
839
|
+
text: waiting.body,
|
|
840
|
+
body: waiting.body,
|
|
841
|
+
createdAt: waiting.created_at,
|
|
842
|
+
read: false, // If it's in unread threads, it's unread
|
|
843
|
+
thread_id: thread.id,
|
|
844
|
+
unread_count: thread.unread,
|
|
845
|
+
});
|
|
846
|
+
});
|
|
818
847
|
|
|
819
848
|
return unreadMessages;
|
|
820
849
|
} catch (e) {
|
package/tools/inbox.js
CHANGED
|
@@ -248,7 +248,18 @@ async function handler(args) {
|
|
|
248
248
|
};
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
|
|
251
|
+
// A failed read is not an empty inbox. getInbox() flattens "nobody
|
|
252
|
+
// answered" into [] — and the tool then said "no messages yet", which every
|
|
253
|
+
// host repeated as "you're caught up" (routing corpus R02, 0/6). Keep the
|
|
254
|
+
// outcome, as vibe_start already does, and say what is actually known.
|
|
255
|
+
const read = await store.getInboxResult(myHandle);
|
|
256
|
+
if (read.ok !== true) {
|
|
257
|
+
return {
|
|
258
|
+
display: `⚠️ could not check your inbox (${read.error || 'unknown'}) — unread is unknown, not zero. Try again in a moment.`,
|
|
259
|
+
footer: 'minimal',
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
const threads = read.threads;
|
|
252
263
|
|
|
253
264
|
// Check for guest session messages (multiplayer)
|
|
254
265
|
let guestMessages = [];
|
package/version.json
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.8.
|
|
3
|
-
"updated": "2026-09-
|
|
4
|
-
"changelog": "
|
|
2
|
+
"version": "0.8.26",
|
|
3
|
+
"updated": "2026-09-04",
|
|
4
|
+
"changelog": "A failed read is not an empty inbox. When the inbox could not be reached, vibe_inbox said \"no messages yet\" and every host repeated it as \"you're caught up\" — six out of six runs in the Sep 4 routing corpus. Now it says what is actually known: could not check your inbox, unread is unknown, not zero. A real empty inbox still reads as empty.",
|
|
5
5
|
"features": [
|
|
6
|
-
"
|
|
7
|
-
"The rarely used guest-session poll runs once a minute while active and every five minutes while idle",
|
|
8
|
-
"Presence heartbeat unchanged: you stay visible exactly as before"
|
|
6
|
+
"vibe_inbox reports a failed read as unknown, never as caught up (routing corpus R02)"
|
|
9
7
|
],
|
|
10
8
|
"deprecated": [],
|
|
11
9
|
"breaking": false,
|