slashvibe-mcp 0.3.21 → 0.3.22
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/LICENSE +21 -0
- package/README.md +280 -47
- package/config.js +36 -31
- package/crypto.js +1 -6
- package/discord.js +19 -19
- package/index.js +217 -207
- package/intelligence/index.js +2 -9
- package/intelligence/infer.js +10 -16
- package/intelligence/patterns.js +23 -18
- package/intelligence/proactive.js +16 -15
- package/intelligence/serendipity.js +57 -20
- package/memory.js +13 -8
- package/notify.js +39 -14
- package/package.json +27 -20
- package/presence.js +2 -2
- package/prompts.js +5 -9
- package/protocol/index.js +123 -87
- package/protocol/telegram-commands.js +36 -37
- package/store/api.js +358 -529
- package/store/local.js +9 -10
- package/store/profiles.js +48 -192
- package/store/reservations.js +2 -9
- package/store/skills.js +69 -71
- package/store/sqlite.js +355 -0
- package/tools/_actions.js +48 -387
- package/tools/_connection-queue.js +45 -56
- package/tools/_discovery-enhanced.js +52 -57
- package/tools/_discovery.js +87 -185
- package/tools/{l2-status.js → _experimental/l2-status.js} +68 -70
- package/tools/{shipback.js → _experimental/shipback.js} +4 -3
- package/tools/_proactive-discovery.js +60 -73
- package/tools/_shared/index.js +41 -64
- package/tools/admin-inbox.js +10 -15
- package/tools/agents.js +1 -1
- package/tools/artifact-create.js +13 -23
- package/tools/artifact-view.js +4 -4
- package/tools/{_deprecated/back.js → back.js} +1 -1
- package/tools/bye.js +3 -5
- package/tools/consent.js +2 -2
- package/tools/context.js +9 -10
- package/tools/crossword.js +3 -2
- package/tools/discover.js +94 -356
- package/tools/dm.js +27 -86
- package/tools/doctor.js +12 -41
- package/tools/drawing.js +34 -20
- package/tools/echo.js +11 -11
- package/tools/feed.js +30 -58
- package/tools/follow.js +64 -187
- package/tools/{_deprecated/forget.js → forget.js} +4 -7
- package/tools/game.js +144 -48
- package/tools/handoff.js +6 -8
- package/tools/help.js +3 -3
- package/tools/idea.js +15 -27
- package/tools/inbox.js +121 -293
- package/tools/init.js +54 -151
- package/tools/invite.js +8 -21
- package/tools/migrate.js +27 -24
- package/tools/multiplayer-game.js +50 -40
- package/tools/{_deprecated/mute.js → mute.js} +4 -3
- package/tools/notifications.js +58 -48
- package/tools/observe.js +12 -15
- package/tools/onboarding.js +8 -11
- package/tools/open.js +13 -144
- package/tools/party-game.js +23 -12
- package/tools/patterns.js +2 -1
- package/tools/ping.js +5 -7
- package/tools/react.js +28 -30
- package/tools/{_deprecated/recall.js → recall.js} +5 -10
- package/tools/release.js +4 -2
- package/tools/{_deprecated/remember.js → remember.js} +4 -6
- package/tools/report.js +2 -2
- package/tools/request.js +6 -26
- package/tools/reserve.js +1 -1
- package/tools/session-fork.js +97 -0
- package/tools/session-save.js +109 -0
- package/tools/settings.js +30 -99
- package/tools/ship.js +74 -56
- package/tools/{_deprecated/skills-exchange.js → skills-exchange.js} +38 -39
- package/tools/social-inbox.js +22 -28
- package/tools/social-post.js +24 -27
- package/tools/solo-game.js +54 -46
- package/tools/start.js +14 -148
- package/tools/status.js +21 -68
- package/tools/submit.js +4 -2
- package/tools/suggest-tags.js +36 -33
- package/tools/summarize.js +19 -16
- package/tools/tag-suggestions.js +72 -73
- package/tools/test.js +1 -1
- package/tools/{_deprecated/tictactoe.js → tictactoe.js} +26 -26
- package/tools/token.js +4 -4
- package/tools/update.js +1 -2
- package/tools/watch.js +132 -112
- package/tools/who.js +20 -40
- package/tools/{_deprecated/wordassociation.js → wordassociation.js} +23 -20
- package/tools/workshop-buddy.js +52 -53
- package/tools/x-mentions.js +0 -1
- package/tools/x-reply.js +0 -1
- package/twitter.js +14 -20
- package/version.json +8 -10
- package/analytics.js +0 -107
- package/auth-store.js +0 -148
- package/auto-update.js +0 -130
- package/bridges/bridge-monitor.js +0 -388
- package/bridges/discord-bot.js +0 -431
- package/bridges/farcaster.js +0 -299
- package/bridges/telegram.js +0 -261
- package/bridges/webhook-health.js +0 -420
- package/bridges/webhook-server.js +0 -437
- package/bridges/whatsapp.js +0 -441
- package/bridges/x-webhook.js +0 -423
- package/games/arcade.js +0 -406
- package/games/chess.js +0 -451
- package/games/colorguess.js +0 -343
- package/games/crossword-words.js +0 -171
- package/games/crossword.js +0 -461
- package/games/drawing.js +0 -347
- package/games/gameroulette.js +0 -300
- package/games/gamerouter.js +0 -336
- package/games/gamestatus.js +0 -337
- package/games/guessnumber.js +0 -209
- package/games/hangman.js +0 -279
- package/games/memory.js +0 -338
- package/games/multiplayer-tictactoe.js +0 -389
- package/games/pixelart.js +0 -399
- package/games/quickduel.js +0 -354
- package/games/riddle.js +0 -371
- package/games/rockpaperscissors.js +0 -291
- package/games/snake.js +0 -406
- package/games/storybuilder.js +0 -343
- package/games/tictactoe.js +0 -345
- package/games/twentyquestions.js +0 -286
- package/games/twotruths.js +0 -207
- package/games/werewolf.js +0 -508
- package/games/wordassociation.js +0 -247
- package/games/wordchain.js +0 -135
- package/intelligence/interests.js +0 -369
- package/notification-emitter.js +0 -77
- package/setup.js +0 -480
- package/smart-inbox.js +0 -276
- package/tools/_deprecated/auto-suggest-connections.js +0 -304
- package/tools/_deprecated/bootstrap-skills.js +0 -231
- package/tools/_deprecated/bridge-dashboard.js +0 -342
- package/tools/_deprecated/bridge-health.js +0 -400
- package/tools/_deprecated/bridge-live.js +0 -384
- package/tools/_deprecated/bridges.js +0 -383
- package/tools/_deprecated/colorguess.js +0 -281
- package/tools/_deprecated/discover-insights.js +0 -379
- package/tools/_deprecated/discover-momentum.js +0 -256
- package/tools/_deprecated/discovery-analytics.js +0 -345
- package/tools/_deprecated/discovery-auto-suggest.js +0 -275
- package/tools/_deprecated/discovery-bootstrap.js +0 -267
- package/tools/_deprecated/discovery-daily.js +0 -375
- package/tools/_deprecated/discovery-dashboard.js +0 -385
- package/tools/_deprecated/discovery-digest.js +0 -314
- package/tools/_deprecated/discovery-hub.js +0 -357
- package/tools/_deprecated/discovery-insights.js +0 -384
- package/tools/_deprecated/discovery-momentum.js +0 -281
- package/tools/_deprecated/discovery-monitor.js +0 -319
- package/tools/_deprecated/discovery-proactive.js +0 -300
- package/tools/_deprecated/draw.js +0 -317
- package/tools/_deprecated/farcaster.js +0 -307
- package/tools/_deprecated/games-catalog.js +0 -376
- package/tools/_deprecated/games.js +0 -313
- package/tools/_deprecated/guessnumber.js +0 -194
- package/tools/_deprecated/hangman.js +0 -129
- package/tools/_deprecated/multiplayer-tictactoe.js +0 -303
- package/tools/_deprecated/riddle.js +0 -240
- package/tools/_deprecated/run-bootstrap.js +0 -69
- package/tools/_deprecated/skills-analytics.js +0 -349
- package/tools/_deprecated/skills-bootstrap.js +0 -301
- package/tools/_deprecated/skills-dashboard.js +0 -268
- package/tools/_deprecated/skills.js +0 -380
- package/tools/_deprecated/smart-intro.js +0 -353
- package/tools/_deprecated/storybuilder.js +0 -331
- package/tools/_deprecated/telegram-bot.js +0 -183
- package/tools/_deprecated/telegram-setup.js +0 -214
- package/tools/_deprecated/twentyquestions.js +0 -143
- package/tools/_shared.js +0 -234
- package/tools/_work-context.js +0 -338
- package/tools/_work-context.manual-test.js +0 -199
- package/tools/_work-context.test.js +0 -260
- package/tools/activity.js +0 -220
- package/tools/agent-treasury.js +0 -288
- package/tools/analytics.js +0 -191
- package/tools/approve.js +0 -197
- package/tools/arcade.js +0 -173
- package/tools/artifacts-price.js +0 -107
- package/tools/ask-expert.js +0 -160
- package/tools/available.js +0 -120
- package/tools/become-expert.js +0 -150
- package/tools/broadcast.js +0 -325
- package/tools/chat.js +0 -202
- package/tools/collaborative-drawing.js +0 -286
- package/tools/connection-status.js +0 -178
- package/tools/earnings.js +0 -126
- package/tools/friends.js +0 -207
- package/tools/genesis.js +0 -233
- package/tools/gig-browse.js +0 -206
- package/tools/gig-complete.js +0 -144
- package/tools/health.js +0 -87
- package/tools/leaderboard.js +0 -117
- package/tools/lib/git-apply.js +0 -206
- package/tools/lib/git-bundle.js +0 -407
- package/tools/mint.js +0 -377
- package/tools/plan.js +0 -225
- package/tools/profile.js +0 -219
- package/tools/proof-of-work.js +0 -144
- package/tools/pulse.js +0 -218
- package/tools/reply.js +0 -166
- package/tools/reputation.js +0 -175
- package/tools/schedule.js +0 -367
- package/tools/search-messages.js +0 -123
- package/tools/session.js +0 -467
- package/tools/session_price.js +0 -128
- package/tools/smart-check.js +0 -201
- package/tools/social-processor.js +0 -445
- package/tools/streak.js +0 -147
- package/tools/stuck.js +0 -297
- package/tools/subscribe.js +0 -148
- package/tools/subscriptions.js +0 -134
- package/tools/tip.js +0 -193
- package/tools/wallet.js +0 -269
- package/tools/webhook-test.js +0 -388
- package/tools/withdraw.js +0 -145
- package/tools/work-summary.js +0 -96
- package/tools/workshop.js +0 -327
- /package/tools/{l2-bridge.js → _experimental/l2-bridge.js} +0 -0
- /package/tools/{l2.js → _experimental/l2.js} +0 -0
- /package/tools/{_deprecated/away.js → away.js} +0 -0
package/tools/inbox.js
CHANGED
|
@@ -5,118 +5,59 @@
|
|
|
5
5
|
const config = require('../config');
|
|
6
6
|
const store = require('../store');
|
|
7
7
|
const notify = require('../notify');
|
|
8
|
-
const
|
|
9
|
-
const {
|
|
10
|
-
const { requireInit, header, emptyState, formatTimeAgo, truncate, divider, fetchRelevantUsers } = require('./_shared');
|
|
8
|
+
const analytics = require('../analytics');
|
|
9
|
+
const { requireInit, header, emptyState, formatTimeAgo, truncate, divider } = require('./_shared');
|
|
11
10
|
const { actions, formatActions } = require('./_actions');
|
|
12
11
|
|
|
13
|
-
//
|
|
14
|
-
function
|
|
15
|
-
if (!text || text.length <= maxLen) return text;
|
|
16
|
-
const truncated = text.slice(0, maxLen);
|
|
17
|
-
const lastSpace = truncated.lastIndexOf(' ');
|
|
18
|
-
return (lastSpace > maxLen * 0.7 ? truncated.slice(0, lastSpace) : truncated) + '...';
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Get activity heat icon for a user (borrowed from who.js logic)
|
|
22
|
-
function getStatusIcon(user) {
|
|
23
|
-
if (!user) return '●';
|
|
24
|
-
|
|
25
|
-
// Check mood/status
|
|
26
|
-
if (user.mood === '🔥' || user.mood === '🚀' || user.builderMode === 'shipping') return '🔥';
|
|
27
|
-
if (user.mood === '🧠' || user.builderMode === 'deep-focus') return '🧠';
|
|
28
|
-
if (user.mood === '🐛') return '🐛';
|
|
29
|
-
if (user.mood === '🌙') return '🌙';
|
|
30
|
-
|
|
31
|
-
// Check recency
|
|
32
|
-
const lastSeenMs = user.lastSeen || Date.now();
|
|
33
|
-
const minutesAgo = (Date.now() - lastSeenMs) / 60000;
|
|
34
|
-
if (minutesAgo < 5) return '⚡';
|
|
35
|
-
if (minutesAgo < 30) return '●';
|
|
36
|
-
return '○';
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Get status label for a user
|
|
40
|
-
function getStatusLabel(user) {
|
|
41
|
-
if (!user) return null;
|
|
42
|
-
|
|
43
|
-
if (user.mood === '🔥' || user.mood === '🚀' || user.builderMode === 'shipping') return 'shipping';
|
|
44
|
-
if (user.mood === '🧠' || user.builderMode === 'deep-focus') return 'deep focus';
|
|
45
|
-
if (user.mood === '🐛') return 'debugging';
|
|
46
|
-
if (user.mood === '🌙') return 'late night';
|
|
47
|
-
if (user.mood === '💭') return 'thinking';
|
|
48
|
-
|
|
49
|
-
const lastSeenMs = user.lastSeen || Date.now();
|
|
50
|
-
const minutesAgo = (Date.now() - lastSeenMs) / 60000;
|
|
51
|
-
if (minutesAgo < 5) return 'active';
|
|
52
|
-
if (minutesAgo < 30) return 'online';
|
|
53
|
-
return 'away';
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Build recommended connections display for empty/caught-up inbox
|
|
57
|
-
async function buildRecommendationsDisplay(myHandle) {
|
|
58
|
-
// Fetch recommendations from relevancy API
|
|
59
|
-
const relevancy = await fetchRelevantUsers(myHandle, 'dm_suggest', 5);
|
|
60
|
-
|
|
61
|
-
if (!relevancy || !relevancy.matches || relevancy.matches.length === 0) {
|
|
62
|
-
return null; // Fallback to old behavior
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const matches = relevancy.matches;
|
|
66
|
-
|
|
67
|
-
// Get presence info for status display
|
|
68
|
-
const presenceMap = new Map();
|
|
12
|
+
// Helper: Fetch recent ships for social proof (FOMO)
|
|
13
|
+
async function getRecentShips(limit = 2) {
|
|
69
14
|
try {
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
15
|
+
const apiUrl = config.getApiUrl();
|
|
16
|
+
const response = await fetch(`${apiUrl}/api/board?limit=${limit}&category=shipped`);
|
|
17
|
+
const data = await response.json();
|
|
18
|
+
return (data.entries || []).map(e => ({
|
|
19
|
+
author: e.author,
|
|
20
|
+
content: e.content?.slice(0, 50)
|
|
21
|
+
}));
|
|
75
22
|
} catch (e) {
|
|
76
|
-
|
|
23
|
+
return [];
|
|
77
24
|
}
|
|
25
|
+
}
|
|
78
26
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
27
|
+
// Helper: Get next incomplete onboarding task
|
|
28
|
+
async function getNextOnboardingTask(handle) {
|
|
29
|
+
try {
|
|
30
|
+
const checklist = await store.getChecklistStatus(handle);
|
|
31
|
+
if (checklist.success && checklist.tasks) {
|
|
32
|
+
const nextTask = checklist.tasks.find(t => !t.done);
|
|
33
|
+
if (nextTask) {
|
|
34
|
+
// Map task IDs to user-friendly actions
|
|
35
|
+
const taskActions = {
|
|
36
|
+
read_welcome: {
|
|
37
|
+
shortLabel: 'Read welcome',
|
|
38
|
+
command: 'check my messages',
|
|
39
|
+
description: 'See your welcome message'
|
|
40
|
+
},
|
|
41
|
+
reply_seth: { shortLabel: 'Reply to @vibe', command: 'message @vibe', description: 'Say hi back!' },
|
|
42
|
+
message_builder: {
|
|
43
|
+
shortLabel: 'Message a builder',
|
|
44
|
+
command: 'discover suggest',
|
|
45
|
+
description: 'Find someone to connect with'
|
|
46
|
+
},
|
|
47
|
+
post_ship: {
|
|
48
|
+
shortLabel: 'Ship something',
|
|
49
|
+
command: 'ship what I built',
|
|
50
|
+
description: "Share what you're building"
|
|
51
|
+
},
|
|
52
|
+
leave_feedback: { shortLabel: 'Give feedback', command: 'talk to @echo', description: 'Help improve /vibe' }
|
|
53
|
+
};
|
|
54
|
+
return taskActions[nextTask.id] || null;
|
|
55
|
+
}
|
|
100
56
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// Enrich matches with status info for action descriptions
|
|
106
|
-
const enrichedMatches = matches.map(match => {
|
|
107
|
-
const presenceUser = presenceMap.get(match.handle?.toLowerCase());
|
|
108
|
-
return {
|
|
109
|
-
...match,
|
|
110
|
-
statusIcon: getStatusIcon(presenceUser),
|
|
111
|
-
statusLabel: getStatusLabel(presenceUser)
|
|
112
|
-
};
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
display,
|
|
117
|
-
matches: enrichedMatches,
|
|
118
|
-
actions: formatActions(actions.recommendedConnections(enrichedMatches))
|
|
119
|
-
};
|
|
57
|
+
return null;
|
|
58
|
+
} catch (e) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
120
61
|
}
|
|
121
62
|
|
|
122
63
|
const definition = {
|
|
@@ -139,72 +80,48 @@ async function handler(args) {
|
|
|
139
80
|
notify.checkAll(store);
|
|
140
81
|
|
|
141
82
|
if (!threads || threads.length === 0) {
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
'No messages yet.'
|
|
150
|
-
);
|
|
151
|
-
return {
|
|
152
|
-
display,
|
|
153
|
-
actions: recommendations.actions
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
} catch (e) {
|
|
157
|
-
console.log('[inbox] recommendations error:', e.message);
|
|
83
|
+
// Fetch context for retention-optimized actions (parallel for speed)
|
|
84
|
+
const [recentShips, onboardingTask] = await Promise.all([getRecentShips(2), getNextOnboardingTask(myHandle)]);
|
|
85
|
+
|
|
86
|
+
// Build social proof line
|
|
87
|
+
let socialProof = '';
|
|
88
|
+
if (recentShips.length > 0) {
|
|
89
|
+
socialProof = `\n 💫 @${recentShips[0].author} just shipped`;
|
|
158
90
|
}
|
|
159
91
|
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
92
|
+
// Build CTA based on onboarding state
|
|
93
|
+
const cta = onboardingTask
|
|
94
|
+
? `→ ${onboardingTask.shortLabel}: "${onboardingTask.command}"`
|
|
95
|
+
: 'Say "dm @someone" to start';
|
|
96
|
+
|
|
97
|
+
// Track empty inbox state for retention analytics
|
|
98
|
+
analytics.trackEmptyInbox('none', {
|
|
99
|
+
recentThreads: [],
|
|
100
|
+
recentShips,
|
|
101
|
+
onboardingTask,
|
|
102
|
+
state: 'no_messages'
|
|
103
|
+
});
|
|
171
104
|
|
|
172
105
|
return {
|
|
173
|
-
display:
|
|
174
|
-
|
|
106
|
+
display: `── 📭 Inbox ──────────────────────────
|
|
107
|
+
No messages yet${socialProof}
|
|
108
|
+
${cta}
|
|
109
|
+
──────────────────────────────────────`,
|
|
110
|
+
hint: 'suggest_compose',
|
|
111
|
+
actions: formatActions(
|
|
112
|
+
actions.emptyInbox({
|
|
113
|
+
recentThreads: [],
|
|
114
|
+
recentShips,
|
|
115
|
+
onboardingTask
|
|
116
|
+
})
|
|
117
|
+
)
|
|
175
118
|
};
|
|
176
119
|
}
|
|
177
120
|
|
|
178
|
-
//
|
|
179
|
-
// Messages from relevant users appear first within unread threads
|
|
180
|
-
const relevantHandles = new Map(); // handle -> relevancy score (position in matches)
|
|
181
|
-
try {
|
|
182
|
-
const relevancy = await fetchRelevantUsers(myHandle, 'notification', 20);
|
|
183
|
-
if (relevancy && relevancy.matches) {
|
|
184
|
-
relevancy.matches.forEach((m, idx) => {
|
|
185
|
-
// Higher score = more relevant (inverse of position)
|
|
186
|
-
relevantHandles.set(m.handle.toLowerCase(), relevancy.matches.length - idx);
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
} catch (e) {
|
|
190
|
-
// Don't fail inbox if relevancy fails
|
|
191
|
-
console.log('[inbox] relevancy fetch error:', e.message);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Sort: unread first, then by relevancy within unread, then by most recent
|
|
121
|
+
// Sort: unread first, then by most recent
|
|
195
122
|
const sorted = threads.sort((a, b) => {
|
|
196
|
-
// Primary: unread vs read
|
|
197
123
|
if (a.unread > 0 && b.unread === 0) return -1;
|
|
198
124
|
if (b.unread > 0 && a.unread === 0) return 1;
|
|
199
|
-
|
|
200
|
-
// Secondary (within same read/unread status): relevancy
|
|
201
|
-
const aRelevancy = relevantHandles.get(a.handle.toLowerCase()) || 0;
|
|
202
|
-
const bRelevancy = relevantHandles.get(b.handle.toLowerCase()) || 0;
|
|
203
|
-
if (aRelevancy !== bRelevancy) {
|
|
204
|
-
return bRelevancy - aRelevancy; // Higher relevancy first
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Tertiary: timestamp (most recent first)
|
|
208
125
|
return (b.lastTimestamp || 0) - (a.lastTimestamp || 0);
|
|
209
126
|
});
|
|
210
127
|
|
|
@@ -213,141 +130,50 @@ async function handler(args) {
|
|
|
213
130
|
|
|
214
131
|
// Handle case where all messages are read (no unread)
|
|
215
132
|
if (totalUnread === 0) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const recommendations = await buildRecommendationsDisplay(myHandle);
|
|
219
|
-
if (recommendations) {
|
|
220
|
-
return {
|
|
221
|
-
display: recommendations.display,
|
|
222
|
-
actions: recommendations.actions
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
} catch (e) {
|
|
226
|
-
console.log('[inbox] recommendations error:', e.message);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Fallback: show recent threads
|
|
230
|
-
const recentHandles = sorted.slice(0, 3).map(t => `@${t.handle}`).join(', ');
|
|
231
|
-
return {
|
|
232
|
-
display: `📭 All caught up! Recent: ${recentHandles}`,
|
|
233
|
-
actions: formatActions(actions.recommendedConnections([]))
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Auto-open single unread thread inline (no second tool call needed)
|
|
238
|
-
if (totalUnread === 1 && unreadSenders.length === 1) {
|
|
239
|
-
const them = unreadSenders[0].handle;
|
|
240
|
-
|
|
241
|
-
// Fetch full thread and mark as read
|
|
242
|
-
const thread = await store.getThread(myHandle, them);
|
|
243
|
-
await store.markThreadRead(myHandle, them);
|
|
244
|
-
|
|
245
|
-
// Auto-track readWelcomeAt if viewing welcome from @seth
|
|
246
|
-
const isWelcomeThread = them.toLowerCase() === 'seth';
|
|
247
|
-
if (isWelcomeThread) {
|
|
248
|
-
try {
|
|
249
|
-
await store.trackChecklistCompletion(myHandle, 'read_welcome', {
|
|
250
|
-
source: 'inbox_auto_open',
|
|
251
|
-
timestamp: Date.now()
|
|
252
|
-
});
|
|
253
|
-
console.log('[inbox] Auto-tracked readWelcomeAt for', myHandle);
|
|
254
|
-
} catch (e) {
|
|
255
|
-
console.warn('[inbox] Failed to track readWelcomeAt:', e.message);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Log received messages for patterns
|
|
260
|
-
const theirMessages = thread.filter(m => m.from === them);
|
|
261
|
-
if (theirMessages.length > 0) {
|
|
262
|
-
patterns.logMessageReceived(them);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Check if they're typing
|
|
266
|
-
let typingNotice = '';
|
|
267
|
-
try {
|
|
268
|
-
const typingUsers = await store.getTypingUsers(myHandle);
|
|
269
|
-
if (typingUsers.includes(them)) {
|
|
270
|
-
typingNotice = `\n_@${them} is typing..._\n`;
|
|
271
|
-
}
|
|
272
|
-
} catch (e) {}
|
|
273
|
-
|
|
274
|
-
// Build thread display - summary first, newest-first thread
|
|
275
|
-
const latestFromThem = theirMessages.length > 0
|
|
276
|
-
? theirMessages.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0))[0]
|
|
277
|
-
: null;
|
|
133
|
+
const recentHandles = sorted.slice(0, 3).map(t => t.handle);
|
|
134
|
+
const recentDisplay = recentHandles.map(h => `@${h}`).join(', ');
|
|
278
135
|
|
|
279
|
-
|
|
136
|
+
// Fetch context for retention-optimized actions (parallel for speed)
|
|
137
|
+
const [recentShips, onboardingTask] = await Promise.all([getRecentShips(2), getNextOnboardingTask(myHandle)]);
|
|
280
138
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
? summarizeMessage(latestFromThem.body)
|
|
286
|
-
: (latestFromThem.payload ? '[attachment]' : '');
|
|
287
|
-
|
|
288
|
-
display = `💬 @${them}${agentBadge} (${time}): "${preview}"\n\n`;
|
|
289
|
-
} else {
|
|
290
|
-
display = `💬 @${them}: _Waiting for reply..._\n\n`;
|
|
139
|
+
// Build social proof line
|
|
140
|
+
let socialProof = '';
|
|
141
|
+
if (recentShips.length > 0) {
|
|
142
|
+
socialProof = `\n 💫 @${recentShips[0].author} just shipped`;
|
|
291
143
|
}
|
|
292
144
|
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const isMe = m.from === myHandle;
|
|
300
|
-
const agentBadge = m.isAgent && !isMe ? '🤖 ' : '';
|
|
301
|
-
const sender = isMe ? 'you' : `@${m.from}`;
|
|
302
|
-
const time = store.formatTimeAgo(m.timestamp);
|
|
303
|
-
|
|
304
|
-
display += `${agentBadge}**${sender}** — _${time}_\n`;
|
|
305
|
-
|
|
306
|
-
if (m.body) {
|
|
307
|
-
display += `${m.body}\n`;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if (m.payload) {
|
|
311
|
-
display += `${formatPayload(m.payload)}\n`;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
display += '\n';
|
|
145
|
+
// Track empty inbox state for retention analytics
|
|
146
|
+
analytics.trackEmptyInbox('none', {
|
|
147
|
+
recentThreads: recentHandles,
|
|
148
|
+
recentShips,
|
|
149
|
+
onboardingTask,
|
|
150
|
+
state: 'all_caught_up'
|
|
315
151
|
});
|
|
316
152
|
|
|
317
|
-
|
|
318
|
-
display
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
? `Building: "${truncate(user.workingOn, 40)}"`
|
|
332
|
-
: 'Recommended for you';
|
|
333
|
-
return {
|
|
334
|
-
handle: user.handle,
|
|
335
|
-
building: user.workingOn,
|
|
336
|
-
reasons: ['Matched during your welcome']
|
|
337
|
-
};
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
return {
|
|
341
|
-
display,
|
|
342
|
-
actions: formatActions(actions.recommendedConnections(recommendedActions))
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
} catch (e) {
|
|
346
|
-
console.warn('[inbox] Failed to fetch recommended builders:', e.message);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
153
|
+
return {
|
|
154
|
+
display: `── 📭 Inbox ──────────────────────────
|
|
155
|
+
All caught up! Recent: ${recentDisplay}${socialProof}
|
|
156
|
+
──────────────────────────────────────`,
|
|
157
|
+
hint: 'suggest_compose',
|
|
158
|
+
actions: formatActions(
|
|
159
|
+
actions.emptyInbox({
|
|
160
|
+
recentThreads: recentHandles,
|
|
161
|
+
recentShips,
|
|
162
|
+
onboardingTask
|
|
163
|
+
})
|
|
164
|
+
)
|
|
165
|
+
};
|
|
166
|
+
}
|
|
349
167
|
|
|
350
|
-
|
|
168
|
+
// Auto-open single unread message (skip inbox view to reduce friction)
|
|
169
|
+
if (totalUnread === 1 && unreadSenders.length === 1) {
|
|
170
|
+
const singleSender = unreadSenders[0];
|
|
171
|
+
return {
|
|
172
|
+
hint: 'auto_open_single_thread',
|
|
173
|
+
handle: singleSender.handle,
|
|
174
|
+
preview: truncate(singleSender.lastMessage || '', 60),
|
|
175
|
+
display: `📬 Opening thread with @${singleSender.handle}...`
|
|
176
|
+
};
|
|
351
177
|
}
|
|
352
178
|
|
|
353
179
|
// Build compact display (3 lines above the fold)
|
|
@@ -363,10 +189,12 @@ async function handler(args) {
|
|
|
363
189
|
display += '───────────────────────────────────\n';
|
|
364
190
|
|
|
365
191
|
// Line 4+: Expanded list with counts and badges
|
|
366
|
-
const expanded = unreadSenders
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
192
|
+
const expanded = unreadSenders
|
|
193
|
+
.map(t => {
|
|
194
|
+
const agent = t.isAgent ? ' 🤖' : '';
|
|
195
|
+
return `@${t.handle} (${t.unread})${agent}`;
|
|
196
|
+
})
|
|
197
|
+
.join(' • ');
|
|
370
198
|
display += expanded;
|
|
371
199
|
|
|
372
200
|
// Build response with optional hints for structured flows
|