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.
- package/README.md +1 -0
- package/analytics.js +107 -0
- package/auth-store.js +148 -0
- package/auto-update.js +130 -0
- package/bridges/bridge-monitor.js +388 -0
- package/bridges/discord-bot.js +431 -0
- package/bridges/farcaster.js +299 -0
- package/bridges/telegram.js +261 -0
- package/bridges/webhook-health.js +420 -0
- package/bridges/webhook-server.js +437 -0
- package/bridges/whatsapp.js +441 -0
- package/bridges/x-webhook.js +423 -0
- package/config.js +154 -5
- package/crypto.js +3 -8
- package/games/arcade.js +406 -0
- package/games/chess.js +451 -0
- package/games/colorguess.js +343 -0
- package/games/crossword-words.js +171 -0
- package/games/crossword.js +461 -0
- package/games/drawing.js +347 -0
- package/games/gameroulette.js +300 -0
- package/games/gamerouter.js +336 -0
- package/games/gamestatus.js +337 -0
- package/games/guessnumber.js +209 -0
- package/games/hangman.js +279 -0
- package/games/memory.js +338 -0
- package/games/multiplayer-tictactoe.js +389 -0
- package/games/pixelart.js +399 -0
- package/games/quickduel.js +354 -0
- package/games/riddle.js +371 -0
- package/games/rockpaperscissors.js +291 -0
- package/games/snake.js +406 -0
- package/games/storybuilder.js +343 -0
- package/games/tictactoe.js +345 -0
- package/games/twentyquestions.js +286 -0
- package/games/twotruths.js +207 -0
- package/games/werewolf.js +508 -0
- package/games/wordassociation.js +247 -0
- package/games/wordchain.js +135 -0
- package/index.js +105 -216
- package/intelligence/index.js +45 -0
- package/intelligence/infer.js +316 -0
- package/intelligence/interests.js +369 -0
- package/intelligence/patterns.js +651 -0
- package/intelligence/proactive.js +358 -0
- package/intelligence/serendipity.js +306 -0
- package/memory.js +166 -0
- package/notification-emitter.js +77 -0
- package/notify.js +102 -1
- package/package.json +21 -7
- package/prompts.js +1 -1
- package/protocol/index.js +161 -1
- package/setup.js +402 -0
- package/store/api.js +528 -82
- package/store/profiles.js +160 -12
- package/tools/_actions.js +463 -16
- package/tools/_deprecated/auto-suggest-connections.js +304 -0
- package/tools/_deprecated/bootstrap-skills.js +231 -0
- package/tools/_deprecated/bridge-dashboard.js +342 -0
- package/tools/_deprecated/bridge-health.js +400 -0
- package/tools/_deprecated/bridge-live.js +384 -0
- package/tools/_deprecated/bridges.js +383 -0
- package/tools/_deprecated/colorguess.js +281 -0
- package/tools/_deprecated/discover-insights.js +379 -0
- package/tools/_deprecated/discover-momentum.js +256 -0
- package/tools/_deprecated/discovery-analytics.js +345 -0
- package/tools/_deprecated/discovery-auto-suggest.js +275 -0
- package/tools/_deprecated/discovery-bootstrap.js +267 -0
- package/tools/_deprecated/discovery-daily.js +375 -0
- package/tools/_deprecated/discovery-dashboard.js +385 -0
- package/tools/_deprecated/discovery-digest.js +314 -0
- package/tools/_deprecated/discovery-hub.js +357 -0
- package/tools/_deprecated/discovery-insights.js +384 -0
- package/tools/_deprecated/discovery-momentum.js +281 -0
- package/tools/_deprecated/discovery-monitor.js +319 -0
- package/tools/_deprecated/discovery-proactive.js +300 -0
- package/tools/_deprecated/draw.js +317 -0
- package/tools/_deprecated/farcaster.js +307 -0
- package/tools/_deprecated/forget.js +119 -0
- package/tools/_deprecated/games-catalog.js +376 -0
- package/tools/_deprecated/games.js +313 -0
- package/tools/_deprecated/guessnumber.js +194 -0
- package/tools/_deprecated/hangman.js +129 -0
- package/tools/_deprecated/multiplayer-tictactoe.js +303 -0
- package/tools/_deprecated/recall.js +147 -0
- package/tools/_deprecated/remember.js +86 -0
- package/tools/_deprecated/riddle.js +240 -0
- package/tools/_deprecated/run-bootstrap.js +69 -0
- package/tools/_deprecated/skills-analytics.js +349 -0
- package/tools/_deprecated/skills-bootstrap.js +301 -0
- package/tools/_deprecated/skills-dashboard.js +268 -0
- package/tools/_deprecated/skills.js +380 -0
- package/tools/_deprecated/smart-intro.js +353 -0
- package/tools/_deprecated/storybuilder.js +331 -0
- package/tools/_deprecated/telegram-bot.js +183 -0
- package/tools/_deprecated/telegram-setup.js +214 -0
- package/tools/_deprecated/twentyquestions.js +143 -0
- package/tools/_discovery-enhanced.js +290 -0
- package/tools/_discovery.js +439 -0
- package/tools/_proactive-discovery.js +301 -0
- package/tools/_shared/index.js +64 -0
- package/tools/_shared.js +234 -0
- package/tools/_work-context.js +338 -0
- package/tools/_work-context.manual-test.js +199 -0
- package/tools/_work-context.test.js +260 -0
- package/tools/activity.js +220 -0
- package/tools/admin-inbox.js +218 -0
- package/tools/agent-treasury.js +288 -0
- package/tools/analytics.js +191 -0
- package/tools/approve.js +197 -0
- package/tools/arcade.js +173 -0
- package/tools/artifact-create.js +17 -11
- package/tools/artifact-view.js +31 -1
- package/tools/artifacts-price.js +47 -43
- package/tools/ask-expert.js +160 -0
- package/tools/available.js +120 -0
- package/tools/become-expert.js +150 -0
- package/tools/broadcast.js +286 -0
- package/tools/chat.js +202 -0
- package/tools/collaborative-drawing.js +286 -0
- package/tools/connection-status.js +178 -0
- package/tools/crossword.js +17 -1
- package/tools/discover.js +350 -34
- package/tools/dm.js +115 -73
- package/tools/drawing.js +1 -1
- package/tools/earnings.js +126 -0
- package/tools/echo.js +16 -0
- package/tools/feed.js +51 -7
- package/tools/follow.js +224 -0
- package/tools/friends.js +207 -0
- package/tools/game.js +2 -2
- package/tools/genesis.js +233 -0
- package/tools/gig-browse.js +206 -0
- package/tools/gig-complete.js +144 -0
- package/tools/handoff.js +7 -1
- package/tools/help.js +4 -4
- package/tools/idea.js +9 -2
- package/tools/inbox.js +334 -28
- package/tools/init.js +727 -36
- package/tools/invite.js +15 -4
- package/tools/l2-bridge.js +272 -0
- package/tools/l2-status.js +217 -0
- package/tools/l2.js +206 -0
- package/tools/migrate.js +156 -0
- package/tools/mint.js +377 -0
- package/tools/multiplayer-game.js +1 -1
- package/tools/notifications.js +415 -0
- package/tools/observe.js +200 -0
- package/tools/onboarding.js +147 -0
- package/tools/open.js +143 -12
- package/tools/party-game.js +2 -2
- package/tools/plan.js +225 -0
- package/tools/presence-agent.js +7 -0
- package/tools/proof-of-work.js +100 -104
- package/tools/pulse.js +218 -0
- package/tools/reply.js +166 -0
- package/tools/report.js +2 -2
- package/tools/reputation.js +175 -0
- package/tools/request.js +17 -3
- package/tools/schedule.js +367 -0
- package/tools/search-messages.js +123 -0
- package/tools/session.js +420 -0
- package/tools/session_price.js +128 -0
- package/tools/settings.js +126 -3
- package/tools/ship.js +31 -8
- package/tools/shipback.js +326 -0
- package/tools/smart-check.js +201 -0
- package/tools/social-processor.js +445 -0
- package/tools/solo-game.js +1 -1
- package/tools/start.js +335 -93
- package/tools/status.js +53 -6
- package/tools/stuck.js +297 -0
- package/tools/subscribe.js +148 -0
- package/tools/subscriptions.js +134 -0
- package/tools/suggest-tags.js +6 -8
- package/tools/tag-suggestions.js +257 -0
- package/tools/tip.js +193 -0
- package/tools/token.js +103 -0
- package/tools/update.js +1 -1
- package/tools/wallet.js +239 -186
- package/tools/watch.js +157 -0
- package/tools/webhook-test.js +388 -0
- package/tools/who.js +54 -3
- package/tools/withdraw.js +145 -0
- package/tools/work-summary.js +96 -0
- package/tools/workshop.js +327 -0
- package/tools/x-mentions.js +1 -1
- package/version.json +14 -3
- package/tools/artifacts-buy.js +0 -111
- package/tools/connect.js +0 -284
- package/tools/gigs-apply.js +0 -99
- package/tools/gigs-browse.js +0 -114
- package/tools/gigs-complete.js +0 -128
- package/tools/gigs-post.js +0 -140
- package/tools/live-off.js +0 -109
- package/tools/live-watch.js +0 -176
- package/tools/live.js +0 -128
- package/tools/sessions-browse.js +0 -105
- package/tools/whats-happening.js +0 -125
- /package/tools/{away.js ā _deprecated/away.js} +0 -0
- /package/tools/{back.js ā _deprecated/back.js} +0 -0
- /package/tools/{mute.js ā _deprecated/mute.js} +0 -0
- /package/tools/{skills-exchange.js ā _deprecated/skills-exchange.js} +0 -0
- /package/tools/{tictactoe.js ā _deprecated/tictactoe.js} +0 -0
- /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
|
|
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:
|
|
173
|
+
display: `š No messages yet. ${cta}`,
|
|
174
|
+
actions: formatActions(actions.recommendedConnections([]))
|
|
33
175
|
};
|
|
34
176
|
}
|
|
35
177
|
|
|
36
|
-
//
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
85
|
-
|
|
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
|
}
|