slashvibe-mcp 0.3.20 → 0.3.21
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 +47 -252
- 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 +27 -15
- 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 +116 -159
- package/intelligence/index.js +9 -2
- package/intelligence/interests.js +369 -0
- package/notification-emitter.js +77 -0
- package/notify.js +5 -1
- package/package.json +21 -16
- package/prompts.js +1 -1
- package/protocol/index.js +73 -0
- package/setup.js +480 -0
- package/smart-inbox.js +276 -0
- package/store/api.js +536 -215
- package/store/profiles.js +160 -12
- package/tools/_actions.js +362 -21
- package/tools/_discovery.js +119 -26
- 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/analytics.js +191 -0
- package/tools/approve.js +197 -0
- package/tools/artifact-create.js +14 -3
- package/tools/artifacts-price.js +107 -0
- package/tools/available.js +120 -0
- package/tools/broadcast.js +325 -0
- package/tools/chat.js +202 -0
- package/tools/collaborative-drawing.js +1 -1
- package/tools/connection-status.js +178 -0
- package/tools/discover.js +350 -34
- package/tools/dm.js +80 -8
- package/tools/earnings.js +126 -0
- package/tools/feed.js +35 -4
- package/tools/follow.js +224 -0
- package/tools/friends.js +207 -0
- package/tools/gig-browse.js +206 -0
- package/tools/gig-complete.js +144 -0
- package/tools/health.js +87 -0
- package/tools/help.js +3 -3
- package/tools/idea.js +9 -2
- package/tools/inbox.js +289 -105
- package/tools/init.js +131 -34
- package/tools/invite.js +15 -4
- package/tools/leaderboard.js +117 -0
- package/tools/lib/git-apply.js +206 -0
- package/tools/lib/git-bundle.js +407 -0
- package/tools/migrate.js +3 -3
- package/tools/multiplayer-game.js +1 -1
- package/tools/onboarding.js +7 -7
- package/tools/open.js +143 -12
- package/tools/party-game.js +1 -1
- package/tools/plan.js +225 -0
- package/tools/proof-of-work.js +144 -0
- package/tools/reply.js +166 -0
- package/tools/report.js +1 -1
- package/tools/request.js +17 -3
- package/tools/schedule.js +367 -0
- package/tools/search-messages.js +123 -0
- package/tools/session.js +467 -0
- package/tools/session_price.js +128 -0
- package/tools/settings.js +90 -2
- package/tools/ship.js +30 -7
- package/tools/smart-check.js +201 -0
- package/tools/start.js +147 -12
- package/tools/status.js +53 -6
- package/tools/streak.js +147 -0
- 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 +1 -1
- package/tools/tip.js +150 -77
- package/tools/token.js +4 -4
- package/tools/update.js +1 -1
- package/tools/wallet.js +221 -79
- package/tools/watch.js +157 -0
- package/tools/who.js +30 -1
- package/tools/withdraw.js +145 -0
- package/tools/work-summary.js +96 -0
- package/version.json +10 -8
- package/LICENSE +0 -21
- package/store/sqlite.js +0 -347
- /package/tools/{auto-suggest-connections.js → _deprecated/auto-suggest-connections.js} +0 -0
- /package/tools/{away.js → _deprecated/away.js} +0 -0
- /package/tools/{back.js → _deprecated/back.js} +0 -0
- /package/tools/{bootstrap-skills.js → _deprecated/bootstrap-skills.js} +0 -0
- /package/tools/{bridge-dashboard.js → _deprecated/bridge-dashboard.js} +0 -0
- /package/tools/{bridge-health.js → _deprecated/bridge-health.js} +0 -0
- /package/tools/{bridge-live.js → _deprecated/bridge-live.js} +0 -0
- /package/tools/{bridges.js → _deprecated/bridges.js} +0 -0
- /package/tools/{colorguess.js → _deprecated/colorguess.js} +0 -0
- /package/tools/{discover-insights.js → _deprecated/discover-insights.js} +0 -0
- /package/tools/{discover-momentum.js → _deprecated/discover-momentum.js} +0 -0
- /package/tools/{discovery-analytics.js → _deprecated/discovery-analytics.js} +0 -0
- /package/tools/{discovery-auto-suggest.js → _deprecated/discovery-auto-suggest.js} +0 -0
- /package/tools/{discovery-bootstrap.js → _deprecated/discovery-bootstrap.js} +0 -0
- /package/tools/{discovery-daily.js → _deprecated/discovery-daily.js} +0 -0
- /package/tools/{discovery-dashboard.js → _deprecated/discovery-dashboard.js} +0 -0
- /package/tools/{discovery-digest.js → _deprecated/discovery-digest.js} +0 -0
- /package/tools/{discovery-hub.js → _deprecated/discovery-hub.js} +0 -0
- /package/tools/{discovery-insights.js → _deprecated/discovery-insights.js} +0 -0
- /package/tools/{discovery-momentum.js → _deprecated/discovery-momentum.js} +0 -0
- /package/tools/{discovery-monitor.js → _deprecated/discovery-monitor.js} +0 -0
- /package/tools/{discovery-proactive.js → _deprecated/discovery-proactive.js} +0 -0
- /package/tools/{draw.js → _deprecated/draw.js} +0 -0
- /package/tools/{farcaster.js → _deprecated/farcaster.js} +0 -0
- /package/tools/{forget.js → _deprecated/forget.js} +0 -0
- /package/tools/{games-catalog.js → _deprecated/games-catalog.js} +0 -0
- /package/tools/{games.js → _deprecated/games.js} +0 -0
- /package/tools/{guessnumber.js → _deprecated/guessnumber.js} +0 -0
- /package/tools/{hangman.js → _deprecated/hangman.js} +0 -0
- /package/tools/{multiplayer-tictactoe.js → _deprecated/multiplayer-tictactoe.js} +0 -0
- /package/tools/{mute.js → _deprecated/mute.js} +0 -0
- /package/tools/{recall.js → _deprecated/recall.js} +0 -0
- /package/tools/{remember.js → _deprecated/remember.js} +0 -0
- /package/tools/{riddle.js → _deprecated/riddle.js} +0 -0
- /package/tools/{run-bootstrap.js → _deprecated/run-bootstrap.js} +0 -0
- /package/tools/{skills-analytics.js → _deprecated/skills-analytics.js} +0 -0
- /package/tools/{skills-bootstrap.js → _deprecated/skills-bootstrap.js} +0 -0
- /package/tools/{skills-dashboard.js → _deprecated/skills-dashboard.js} +0 -0
- /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
- /package/tools/{skills.js → _deprecated/skills.js} +0 -0
- /package/tools/{smart-intro.js → _deprecated/smart-intro.js} +0 -0
- /package/tools/{storybuilder.js → _deprecated/storybuilder.js} +0 -0
- /package/tools/{telegram-bot.js → _deprecated/telegram-bot.js} +0 -0
- /package/tools/{telegram-setup.js → _deprecated/telegram-setup.js} +0 -0
- /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
- /package/tools/{twentyquestions.js → _deprecated/twentyquestions.js} +0 -0
- /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
package/tools/inbox.js
CHANGED
|
@@ -5,47 +5,118 @@
|
|
|
5
5
|
const config = require('../config');
|
|
6
6
|
const store = require('../store');
|
|
7
7
|
const notify = require('../notify');
|
|
8
|
-
const
|
|
9
|
-
const {
|
|
8
|
+
const patterns = require('../intelligence/patterns');
|
|
9
|
+
const { formatPayload } = require('../protocol');
|
|
10
|
+
const { requireInit, header, emptyState, formatTimeAgo, truncate, divider, fetchRelevantUsers } = require('./_shared');
|
|
10
11
|
const { actions, formatActions } = require('./_actions');
|
|
11
12
|
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return (data.entries || []).map(e => ({
|
|
19
|
-
author: e.author,
|
|
20
|
-
content: e.content?.slice(0, 50)
|
|
21
|
-
}));
|
|
22
|
-
} catch (e) {
|
|
23
|
-
return [];
|
|
24
|
-
}
|
|
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) + '...';
|
|
25
19
|
}
|
|
26
20
|
|
|
27
|
-
//
|
|
28
|
-
|
|
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();
|
|
29
69
|
try {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const taskActions = {
|
|
36
|
-
'read_welcome': { shortLabel: 'Read welcome', command: 'check my messages', description: 'See your welcome message' },
|
|
37
|
-
'reply_seth': { shortLabel: 'Reply to @vibe', command: 'message @vibe', description: 'Say hi back!' },
|
|
38
|
-
'message_builder': { shortLabel: 'Message a builder', command: 'discover suggest', description: 'Find someone to connect with' },
|
|
39
|
-
'post_ship': { shortLabel: 'Ship something', command: 'ship what I built', description: 'Share what you\'re building' },
|
|
40
|
-
'leave_feedback': { shortLabel: 'Give feedback', command: 'talk to @echo', description: 'Help improve /vibe' }
|
|
41
|
-
};
|
|
42
|
-
return taskActions[nextTask.id] || null;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return null;
|
|
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
|
+
});
|
|
46
75
|
} catch (e) {
|
|
47
|
-
|
|
76
|
+
// Continue without presence info
|
|
48
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
|
+
};
|
|
49
120
|
}
|
|
50
121
|
|
|
51
122
|
const definition = {
|
|
@@ -68,49 +139,72 @@ async function handler(args) {
|
|
|
68
139
|
notify.checkAll(store);
|
|
69
140
|
|
|
70
141
|
if (!threads || threads.length === 0) {
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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);
|
|
81
158
|
}
|
|
82
159
|
|
|
83
|
-
//
|
|
84
|
-
let cta =
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
});
|
|
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) {}
|
|
95
171
|
|
|
96
172
|
return {
|
|
97
|
-
display:
|
|
98
|
-
|
|
99
|
-
${cta}
|
|
100
|
-
──────────────────────────────────────`,
|
|
101
|
-
hint: 'suggest_compose',
|
|
102
|
-
actions: formatActions(actions.emptyInbox({
|
|
103
|
-
recentThreads: [],
|
|
104
|
-
recentShips,
|
|
105
|
-
onboardingTask
|
|
106
|
-
}))
|
|
173
|
+
display: `📭 No messages yet. ${cta}`,
|
|
174
|
+
actions: formatActions(actions.recommendedConnections([]))
|
|
107
175
|
};
|
|
108
176
|
}
|
|
109
177
|
|
|
110
|
-
//
|
|
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
|
|
111
195
|
const sorted = threads.sort((a, b) => {
|
|
196
|
+
// Primary: unread vs read
|
|
112
197
|
if (a.unread > 0 && b.unread === 0) return -1;
|
|
113
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)
|
|
114
208
|
return (b.lastTimestamp || 0) - (a.lastTimestamp || 0);
|
|
115
209
|
});
|
|
116
210
|
|
|
@@ -119,51 +213,141 @@ async function handler(args) {
|
|
|
119
213
|
|
|
120
214
|
// Handle case where all messages are read (no unread)
|
|
121
215
|
if (totalUnread === 0) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (recentShips.length > 0) {
|
|
134
|
-
socialProof = `\n 💫 @${recentShips[0].author} just shipped`;
|
|
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);
|
|
135
227
|
}
|
|
136
228
|
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
recentThreads: recentHandles,
|
|
140
|
-
recentShips,
|
|
141
|
-
onboardingTask,
|
|
142
|
-
state: 'all_caught_up'
|
|
143
|
-
});
|
|
144
|
-
|
|
229
|
+
// Fallback: show recent threads
|
|
230
|
+
const recentHandles = sorted.slice(0, 3).map(t => `@${t.handle}`).join(', ');
|
|
145
231
|
return {
|
|
146
|
-
display:
|
|
147
|
-
|
|
148
|
-
──────────────────────────────────────`,
|
|
149
|
-
hint: 'suggest_compose',
|
|
150
|
-
actions: formatActions(actions.emptyInbox({
|
|
151
|
-
recentThreads: recentHandles,
|
|
152
|
-
recentShips,
|
|
153
|
-
onboardingTask
|
|
154
|
-
}))
|
|
232
|
+
display: `📭 All caught up! Recent: ${recentHandles}`,
|
|
233
|
+
actions: formatActions(actions.recommendedConnections([]))
|
|
155
234
|
};
|
|
156
235
|
}
|
|
157
236
|
|
|
158
|
-
// Auto-open single unread
|
|
237
|
+
// Auto-open single unread thread inline (no second tool call needed)
|
|
159
238
|
if (totalUnread === 1 && unreadSenders.length === 1) {
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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`;
|
|
291
|
+
}
|
|
292
|
+
|
|
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 };
|
|
167
351
|
}
|
|
168
352
|
|
|
169
353
|
// Build compact display (3 lines above the fold)
|