slashvibe-mcp 0.2.9 → 0.3.13

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.
Files changed (205) hide show
  1. package/README.md +1 -0
  2. package/analytics.js +107 -0
  3. package/auth-store.js +148 -0
  4. package/auto-update.js +130 -0
  5. package/bridges/bridge-monitor.js +388 -0
  6. package/bridges/discord-bot.js +431 -0
  7. package/bridges/farcaster.js +299 -0
  8. package/bridges/telegram.js +261 -0
  9. package/bridges/webhook-health.js +420 -0
  10. package/bridges/webhook-server.js +437 -0
  11. package/bridges/whatsapp.js +441 -0
  12. package/bridges/x-webhook.js +423 -0
  13. package/config.js +154 -5
  14. package/crypto.js +3 -8
  15. package/games/arcade.js +406 -0
  16. package/games/chess.js +451 -0
  17. package/games/colorguess.js +343 -0
  18. package/games/crossword-words.js +171 -0
  19. package/games/crossword.js +461 -0
  20. package/games/drawing.js +347 -0
  21. package/games/gameroulette.js +300 -0
  22. package/games/gamerouter.js +336 -0
  23. package/games/gamestatus.js +337 -0
  24. package/games/guessnumber.js +209 -0
  25. package/games/hangman.js +279 -0
  26. package/games/memory.js +338 -0
  27. package/games/multiplayer-tictactoe.js +389 -0
  28. package/games/pixelart.js +399 -0
  29. package/games/quickduel.js +354 -0
  30. package/games/riddle.js +371 -0
  31. package/games/rockpaperscissors.js +291 -0
  32. package/games/snake.js +406 -0
  33. package/games/storybuilder.js +343 -0
  34. package/games/tictactoe.js +345 -0
  35. package/games/twentyquestions.js +286 -0
  36. package/games/twotruths.js +207 -0
  37. package/games/werewolf.js +508 -0
  38. package/games/wordassociation.js +247 -0
  39. package/games/wordchain.js +135 -0
  40. package/index.js +105 -216
  41. package/intelligence/index.js +45 -0
  42. package/intelligence/infer.js +316 -0
  43. package/intelligence/interests.js +369 -0
  44. package/intelligence/patterns.js +651 -0
  45. package/intelligence/proactive.js +358 -0
  46. package/intelligence/serendipity.js +306 -0
  47. package/memory.js +166 -0
  48. package/notification-emitter.js +77 -0
  49. package/notify.js +102 -1
  50. package/package.json +21 -7
  51. package/prompts.js +1 -1
  52. package/protocol/index.js +161 -1
  53. package/setup.js +402 -0
  54. package/store/api.js +528 -82
  55. package/store/profiles.js +160 -12
  56. package/tools/_actions.js +463 -16
  57. package/tools/_deprecated/auto-suggest-connections.js +304 -0
  58. package/tools/_deprecated/bootstrap-skills.js +231 -0
  59. package/tools/_deprecated/bridge-dashboard.js +342 -0
  60. package/tools/_deprecated/bridge-health.js +400 -0
  61. package/tools/_deprecated/bridge-live.js +384 -0
  62. package/tools/_deprecated/bridges.js +383 -0
  63. package/tools/_deprecated/colorguess.js +281 -0
  64. package/tools/_deprecated/discover-insights.js +379 -0
  65. package/tools/_deprecated/discover-momentum.js +256 -0
  66. package/tools/_deprecated/discovery-analytics.js +345 -0
  67. package/tools/_deprecated/discovery-auto-suggest.js +275 -0
  68. package/tools/_deprecated/discovery-bootstrap.js +267 -0
  69. package/tools/_deprecated/discovery-daily.js +375 -0
  70. package/tools/_deprecated/discovery-dashboard.js +385 -0
  71. package/tools/_deprecated/discovery-digest.js +314 -0
  72. package/tools/_deprecated/discovery-hub.js +357 -0
  73. package/tools/_deprecated/discovery-insights.js +384 -0
  74. package/tools/_deprecated/discovery-momentum.js +281 -0
  75. package/tools/_deprecated/discovery-monitor.js +319 -0
  76. package/tools/_deprecated/discovery-proactive.js +300 -0
  77. package/tools/_deprecated/draw.js +317 -0
  78. package/tools/_deprecated/farcaster.js +307 -0
  79. package/tools/_deprecated/forget.js +119 -0
  80. package/tools/_deprecated/games-catalog.js +376 -0
  81. package/tools/_deprecated/games.js +313 -0
  82. package/tools/_deprecated/guessnumber.js +194 -0
  83. package/tools/_deprecated/hangman.js +129 -0
  84. package/tools/_deprecated/multiplayer-tictactoe.js +303 -0
  85. package/tools/_deprecated/recall.js +147 -0
  86. package/tools/_deprecated/remember.js +86 -0
  87. package/tools/_deprecated/riddle.js +240 -0
  88. package/tools/_deprecated/run-bootstrap.js +69 -0
  89. package/tools/_deprecated/skills-analytics.js +349 -0
  90. package/tools/_deprecated/skills-bootstrap.js +301 -0
  91. package/tools/_deprecated/skills-dashboard.js +268 -0
  92. package/tools/_deprecated/skills.js +380 -0
  93. package/tools/_deprecated/smart-intro.js +353 -0
  94. package/tools/_deprecated/storybuilder.js +331 -0
  95. package/tools/_deprecated/telegram-bot.js +183 -0
  96. package/tools/_deprecated/telegram-setup.js +214 -0
  97. package/tools/_deprecated/twentyquestions.js +143 -0
  98. package/tools/_discovery-enhanced.js +290 -0
  99. package/tools/_discovery.js +439 -0
  100. package/tools/_proactive-discovery.js +301 -0
  101. package/tools/_shared/index.js +64 -0
  102. package/tools/_shared.js +234 -0
  103. package/tools/_work-context.js +338 -0
  104. package/tools/_work-context.manual-test.js +199 -0
  105. package/tools/_work-context.test.js +260 -0
  106. package/tools/activity.js +220 -0
  107. package/tools/admin-inbox.js +218 -0
  108. package/tools/agent-treasury.js +288 -0
  109. package/tools/analytics.js +191 -0
  110. package/tools/approve.js +197 -0
  111. package/tools/arcade.js +173 -0
  112. package/tools/artifact-create.js +17 -11
  113. package/tools/artifact-view.js +31 -1
  114. package/tools/artifacts-price.js +47 -43
  115. package/tools/ask-expert.js +160 -0
  116. package/tools/available.js +120 -0
  117. package/tools/become-expert.js +150 -0
  118. package/tools/broadcast.js +286 -0
  119. package/tools/chat.js +202 -0
  120. package/tools/collaborative-drawing.js +286 -0
  121. package/tools/connection-status.js +178 -0
  122. package/tools/crossword.js +17 -1
  123. package/tools/discover.js +350 -34
  124. package/tools/dm.js +115 -73
  125. package/tools/drawing.js +1 -1
  126. package/tools/earnings.js +126 -0
  127. package/tools/echo.js +16 -0
  128. package/tools/feed.js +51 -7
  129. package/tools/follow.js +224 -0
  130. package/tools/friends.js +207 -0
  131. package/tools/game.js +2 -2
  132. package/tools/genesis.js +233 -0
  133. package/tools/gig-browse.js +206 -0
  134. package/tools/gig-complete.js +144 -0
  135. package/tools/handoff.js +7 -1
  136. package/tools/help.js +4 -4
  137. package/tools/idea.js +9 -2
  138. package/tools/inbox.js +334 -28
  139. package/tools/init.js +727 -36
  140. package/tools/invite.js +15 -4
  141. package/tools/l2-bridge.js +272 -0
  142. package/tools/l2-status.js +217 -0
  143. package/tools/l2.js +206 -0
  144. package/tools/migrate.js +156 -0
  145. package/tools/mint.js +377 -0
  146. package/tools/multiplayer-game.js +1 -1
  147. package/tools/notifications.js +415 -0
  148. package/tools/observe.js +200 -0
  149. package/tools/onboarding.js +147 -0
  150. package/tools/open.js +143 -12
  151. package/tools/party-game.js +2 -2
  152. package/tools/plan.js +225 -0
  153. package/tools/presence-agent.js +7 -0
  154. package/tools/proof-of-work.js +100 -104
  155. package/tools/pulse.js +218 -0
  156. package/tools/reply.js +166 -0
  157. package/tools/report.js +2 -2
  158. package/tools/reputation.js +175 -0
  159. package/tools/request.js +17 -3
  160. package/tools/schedule.js +367 -0
  161. package/tools/search-messages.js +123 -0
  162. package/tools/session.js +420 -0
  163. package/tools/session_price.js +128 -0
  164. package/tools/settings.js +126 -3
  165. package/tools/ship.js +31 -8
  166. package/tools/shipback.js +326 -0
  167. package/tools/smart-check.js +201 -0
  168. package/tools/social-processor.js +445 -0
  169. package/tools/solo-game.js +1 -1
  170. package/tools/start.js +335 -93
  171. package/tools/status.js +53 -6
  172. package/tools/stuck.js +297 -0
  173. package/tools/subscribe.js +148 -0
  174. package/tools/subscriptions.js +134 -0
  175. package/tools/suggest-tags.js +6 -8
  176. package/tools/tag-suggestions.js +257 -0
  177. package/tools/tip.js +193 -0
  178. package/tools/token.js +103 -0
  179. package/tools/update.js +1 -1
  180. package/tools/wallet.js +239 -186
  181. package/tools/watch.js +157 -0
  182. package/tools/webhook-test.js +388 -0
  183. package/tools/who.js +54 -3
  184. package/tools/withdraw.js +145 -0
  185. package/tools/work-summary.js +96 -0
  186. package/tools/workshop.js +327 -0
  187. package/tools/x-mentions.js +1 -1
  188. package/version.json +14 -3
  189. package/tools/artifacts-buy.js +0 -111
  190. package/tools/connect.js +0 -284
  191. package/tools/gigs-apply.js +0 -99
  192. package/tools/gigs-browse.js +0 -114
  193. package/tools/gigs-complete.js +0 -128
  194. package/tools/gigs-post.js +0 -140
  195. package/tools/live-off.js +0 -109
  196. package/tools/live-watch.js +0 -176
  197. package/tools/live.js +0 -128
  198. package/tools/sessions-browse.js +0 -105
  199. package/tools/whats-happening.js +0 -125
  200. /package/tools/{away.js → _deprecated/away.js} +0 -0
  201. /package/tools/{back.js → _deprecated/back.js} +0 -0
  202. /package/tools/{mute.js → _deprecated/mute.js} +0 -0
  203. /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
  204. /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
  205. /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
package/tools/inbox.js CHANGED
@@ -5,9 +5,120 @@
5
5
  const config = require('../config');
6
6
  const store = require('../store');
7
7
  const notify = require('../notify');
8
- const { requireInit, header, emptyState, formatTimeAgo, truncate, divider } = require('./_shared');
8
+ const patterns = require('../intelligence/patterns');
9
+ const { formatPayload } = require('../protocol');
10
+ const { requireInit, header, emptyState, formatTimeAgo, truncate, divider, fetchRelevantUsers } = require('./_shared');
9
11
  const { actions, formatActions } = require('./_actions');
10
12
 
13
+ // Truncate message for preview (first 100 chars, clean break at word)
14
+ function summarizeMessage(text, maxLen = 100) {
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();
69
+ try {
70
+ const presence = await store.getPresence();
71
+ const allUsers = [...(presence.active || []), ...(presence.away || [])];
72
+ allUsers.forEach(u => {
73
+ presenceMap.set(u.handle?.toLowerCase(), u);
74
+ });
75
+ } catch (e) {
76
+ // Continue without presence info
77
+ }
78
+
79
+ // Build preview line (stays visible when collapsed)
80
+ const top3Handles = matches.slice(0, 3).map(m => `@${m.handle}`).join(', ');
81
+ let display = `šŸ“­ All caught up! Connect with: ${top3Handles}\n\n`;
82
+
83
+ // Build ranked list with statuses
84
+ display += `---\n`;
85
+
86
+ matches.forEach(match => {
87
+ const presenceUser = presenceMap.get(match.handle?.toLowerCase());
88
+ const icon = getStatusIcon(presenceUser);
89
+ const statusLabel = getStatusLabel(presenceUser);
90
+ const statusText = statusLabel ? ` — ${statusLabel}` : '';
91
+
92
+ display += `${icon} **@${match.handle}**${statusText}\n`;
93
+
94
+ // Show building + first reason
95
+ if (match.building) {
96
+ const reason = match.reasons?.[0] ? ` • ${match.reasons[0]}` : '';
97
+ display += ` "${summarizeMessage(match.building, 50)}"${reason}\n`;
98
+ } else if (match.reasons?.[0]) {
99
+ display += ` ${match.reasons[0]}\n`;
100
+ }
101
+
102
+ display += '\n';
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
+ };
120
+ }
121
+
11
122
  const definition = {
12
123
  name: 'vibe_inbox',
13
124
  description: 'See your unread messages and recent threads.',
@@ -28,39 +139,235 @@ async function handler(args) {
28
139
  notify.checkAll(store);
29
140
 
30
141
  if (!threads || threads.length === 0) {
142
+ // Try to show personalized recommendations
143
+ try {
144
+ const recommendations = await buildRecommendationsDisplay(myHandle);
145
+ if (recommendations) {
146
+ // Change preview line for empty inbox (no messages yet)
147
+ const display = recommendations.display.replace(
148
+ 'All caught up!',
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);
158
+ }
159
+
160
+ // Fallback: Check onboarding status to customize CTA
161
+ let cta = 'Say "dm @someone" to start';
162
+ try {
163
+ const checklist = await store.getChecklistStatus(myHandle);
164
+ if (checklist.success && checklist.tasks) {
165
+ const repliedToSeth = checklist.tasks.find(t => t.id === 'reply_seth')?.done;
166
+ if (!repliedToSeth) {
167
+ cta = 'Reply to @vibe to get started!';
168
+ }
169
+ }
170
+ } catch (e) {}
171
+
31
172
  return {
32
- display: `${header('Inbox')}\n\n${emptyState('No messages yet.', 'Say "message someone" to start a conversation')}`
173
+ display: `šŸ“­ No messages yet. ${cta}`,
174
+ actions: formatActions(actions.recommendedConnections([]))
33
175
  };
34
176
  }
35
177
 
36
- // Sort: unread first, then by most recent
178
+ // Fetch relevant users to prioritize notifications
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
37
195
  const sorted = threads.sort((a, b) => {
196
+ // Primary: unread vs read
38
197
  if (a.unread > 0 && b.unread === 0) return -1;
39
198
  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)
40
208
  return (b.lastTimestamp || 0) - (a.lastTimestamp || 0);
41
209
  });
42
210
 
43
211
  const totalUnread = sorted.reduce((sum, t) => sum + (t.unread || 0), 0);
44
- let display = header(`Inbox${totalUnread > 0 ? ` — ${totalUnread} UNREAD` : ''}`);
45
- display += '\n\n';
46
-
47
- sorted.forEach(thread => {
48
- const unreadBadge = thread.unread > 0 ? ` šŸ“¬ NEW MESSAGE` : '';
49
- const agentBadge = thread.isAgent ? 'šŸ¤– ' : '';
50
- const preview = truncate(thread.lastMessage || '', 60);
51
- const timeAgo = formatTimeAgo(thread.lastTimestamp);
52
-
53
- display += `${agentBadge}**@${thread.handle}**${unreadBadge}\n`;
54
- if (preview) {
55
- display += ` "${preview}"\n`;
212
+ const unreadSenders = sorted.filter(t => t.unread > 0);
213
+
214
+ // Handle case where all messages are read (no unread)
215
+ if (totalUnread === 0) {
216
+ // Try to show personalized recommendations
217
+ try {
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);
56
227
  }
57
- if (timeAgo) {
58
- display += ` _${timeAgo}_\n`;
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;
278
+
279
+ let display = '';
280
+
281
+ if (latestFromThem) {
282
+ const agentBadge = latestFromThem.isAgent ? ' šŸ¤–' : '';
283
+ const time = store.formatTimeAgo(latestFromThem.timestamp);
284
+ const preview = latestFromThem.body
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`;
59
291
  }
60
- display += '\n';
61
- });
62
292
 
63
- display += `${divider()}Say "open thread with @handle" to read more`;
293
+ // Thread section - sorted newest first
294
+ display += `---\nšŸ“œ Thread\n\n`;
295
+
296
+ const sortedThread = [...thread].sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
297
+
298
+ sortedThread.forEach(m => {
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';
315
+ });
316
+
317
+ if (typingNotice) {
318
+ display += typingNotice + '\n';
319
+ }
320
+
321
+ display += `---\nJust type your reply to send it`;
322
+
323
+ // For @seth welcome thread, fetch recommended builders and add actions
324
+ if (isWelcomeThread) {
325
+ try {
326
+ const onboardingData = await store.getOnboardingData(myHandle);
327
+ if (onboardingData.success && onboardingData.recommendedUsers?.length > 0) {
328
+ // Build action options to message recommended builders
329
+ const recommendedActions = onboardingData.recommendedUsers.slice(0, 3).map(user => {
330
+ const description = user.workingOn
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
+ }
349
+
350
+ return { display };
351
+ }
352
+
353
+ // Build compact display (3 lines above the fold)
354
+ // Line 1: Total count
355
+ let display = `šŸ“¬ ${totalUnread} unread message${totalUnread > 1 ? 's' : ''}\n`;
356
+
357
+ // Line 2: Top 3 senders + overflow
358
+ const top3Names = unreadSenders.slice(0, 3).map(t => `@${t.handle}`);
359
+ const overflow = unreadSenders.length > 3 ? ` (+${unreadSenders.length - 3} more)` : '';
360
+ display += `from ${top3Names.join(', ')}${overflow}\n`;
361
+
362
+ // Line 3: Divider
363
+ display += '───────────────────────────────────\n';
364
+
365
+ // Line 4+: Expanded list with counts and badges
366
+ const expanded = unreadSenders.map(t => {
367
+ const agent = t.isAgent ? ' šŸ¤–' : '';
368
+ return `@${t.handle} (${t.unread})${agent}`;
369
+ }).join(' • ');
370
+ display += expanded;
64
371
 
65
372
  // Build response with optional hints for structured flows
66
373
  const response = { display };
@@ -69,20 +376,19 @@ async function handler(args) {
69
376
  if (totalUnread >= 5) {
70
377
  response.hint = 'structured_triage_recommended';
71
378
  response.unread_count = totalUnread;
72
- response.threads = sorted.filter(t => t.unread > 0).map(t => ({
379
+ response.threads = unreadSenders.map(t => ({
73
380
  handle: t.handle,
74
381
  unread: t.unread,
75
382
  preview: truncate(t.lastMessage || '', 40)
76
383
  }));
77
384
  }
78
- // Suggest compose when inbox is empty or fully read
79
- else if (threads.length === 0 || totalUnread === 0) {
80
- response.hint = 'suggest_compose';
81
- }
82
385
 
83
- // Add guided mode actions
84
- const threadSummaries = sorted.slice(0, 4).map(t => ({ handle: t.handle, unread: t.unread || 0 }));
85
- response.actions = formatActions(actions.afterInbox(threadSummaries));
386
+ // Add guided mode actions with compact format
387
+ const senderSummaries = unreadSenders.map(t => ({
388
+ handle: t.handle,
389
+ unread: t.unread || 0
390
+ }));
391
+ response.actions = formatActions(actions.afterInboxCompact(senderSummaries));
86
392
 
87
393
  return response;
88
394
  }