@shadowob/shared 1.1.1 → 1.1.3-dev.261

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.
@@ -16,14 +16,13 @@ var SERVER_EVENTS = {
16
16
  PRESENCE_CHANGE: "presence:change",
17
17
  REACTION_ADD: "reaction:add",
18
18
  REACTION_REMOVE: "reaction:remove",
19
- NOTIFICATION_NEW: "notification:new",
20
- DM_MESSAGE_NEW: "dm:message:new"
19
+ NOTIFICATION_NEW: "notification:new"
21
20
  };
22
21
 
23
22
  // src/constants/limits.ts
24
23
  var LIMITS = {
25
- /** Max message content length */
26
- MESSAGE_CONTENT_MAX: 4e3,
24
+ /** Max message content length (16KB — enough for detailed agent responses) */
25
+ MESSAGE_CONTENT_MAX: 16e3,
27
26
  /** Max username length */
28
27
  USERNAME_MAX: 32,
29
28
  /** Min username length */
@@ -183,6 +183,190 @@ function renderCatSvg(config) {
183
183
  return `data:image/svg+xml,${encodeURIComponent(svg.replace(/\n\s*/g, ""))}`;
184
184
  }
185
185
 
186
+ // src/utils/message-mentions.ts
187
+ function uniqueValues(values) {
188
+ return Array.from(new Set(values.filter((value) => !!value)));
189
+ }
190
+ function canonicalMentionToken(mention) {
191
+ if (mention.kind === "channel") return `<#${mention.channelId ?? mention.targetId}>`;
192
+ if (mention.kind === "server") return `<@server:${mention.serverId ?? mention.targetId}>`;
193
+ if (mention.kind === "here" || mention.kind === "everyone") {
194
+ const scope = mention.serverId ?? mention.targetId;
195
+ return scope ? `<!${mention.kind}:${scope}>` : `<!${mention.kind}>`;
196
+ }
197
+ return `<@${mention.userId ?? mention.targetId}>`;
198
+ }
199
+ function parseCanonicalMentionToken(token) {
200
+ const user = token.match(/^<@([^>:]+)>$/u);
201
+ if (user?.[1]) return { kind: "user", targetId: user[1] };
202
+ const server = token.match(/^<@server:([^>]+)>$/u);
203
+ if (server?.[1]) return { kind: "server", targetId: server[1] };
204
+ const channel = token.match(/^<#([^>]+)>$/u);
205
+ if (channel?.[1]) return { kind: "channel", targetId: channel[1] };
206
+ const broadcast = token.match(/^<!(here|everyone)(?::([^>]+))?>$/u);
207
+ if (broadcast?.[1] === "here" || broadcast?.[1] === "everyone") {
208
+ return {
209
+ kind: broadcast[1],
210
+ ...broadcast[2] ? { targetId: broadcast[2] } : {}
211
+ };
212
+ }
213
+ return null;
214
+ }
215
+ function isCanonicalMentionToken(token) {
216
+ return parseCanonicalMentionToken(token) !== null;
217
+ }
218
+ function matchTextsForMention(mention) {
219
+ return uniqueValues([mention.token, mention.sourceToken, canonicalMentionToken(mention)]);
220
+ }
221
+ function isValidRange(content, mention, range) {
222
+ if (!range) return false;
223
+ if (!Number.isInteger(range.start) || !Number.isInteger(range.end)) return false;
224
+ if (range.start < 0 || range.end <= range.start || range.end > content.length) return false;
225
+ const sourceText = content.slice(range.start, range.end);
226
+ return matchTextsForMention(mention).includes(sourceText);
227
+ }
228
+ function overlaps(a, b) {
229
+ return a.start < b.end && b.start < a.end;
230
+ }
231
+ function canUseRange(range, used) {
232
+ return !used.some((candidate) => overlaps(candidate, range));
233
+ }
234
+ function findOccurrences(content, token, used) {
235
+ const occurrences = [];
236
+ let index = content.indexOf(token);
237
+ while (index >= 0) {
238
+ const range = { start: index, end: index + token.length };
239
+ if (canUseRange(range, used)) occurrences.push(range);
240
+ index = content.indexOf(token, index + token.length);
241
+ }
242
+ return occurrences;
243
+ }
244
+ function addCandidate(candidates, used, mention, range, order, sourceText) {
245
+ if (!canUseRange(range, used)) return;
246
+ const matchedText = sourceText ?? mention.sourceToken ?? mention.token;
247
+ used.push(range);
248
+ candidates.push({
249
+ start: range.start,
250
+ end: range.end,
251
+ order,
252
+ sourceText: matchedText,
253
+ mention: { ...mention, range }
254
+ });
255
+ }
256
+ function segmentTextByMentions(content, mentions) {
257
+ if (!content) return [];
258
+ const usableMentions = (mentions ?? []).filter((mention) => mention.token);
259
+ if (usableMentions.length === 0) return [{ type: "text", text: content }];
260
+ const candidates = [];
261
+ const usedRanges = [];
262
+ const pendingByText = /* @__PURE__ */ new Map();
263
+ usableMentions.forEach((mention, order) => {
264
+ if (isValidRange(content, mention, mention.range)) {
265
+ addCandidate(
266
+ candidates,
267
+ usedRanges,
268
+ mention,
269
+ mention.range,
270
+ order,
271
+ content.slice(mention.range.start, mention.range.end)
272
+ );
273
+ return;
274
+ }
275
+ for (const text of matchTextsForMention(mention)) {
276
+ const pending = pendingByText.get(text) ?? [];
277
+ pending.push({ mention, order });
278
+ pendingByText.set(text, pending);
279
+ }
280
+ });
281
+ const pendingGroups = Array.from(pendingByText.entries()).sort((a, b) => {
282
+ const lengthDelta = b[0].length - a[0].length;
283
+ if (lengthDelta !== 0) return lengthDelta;
284
+ return a[1][0].order - b[1][0].order;
285
+ });
286
+ for (const [token, pending] of pendingGroups) {
287
+ const occurrences = findOccurrences(content, token, usedRanges);
288
+ if (occurrences.length === 0) continue;
289
+ if (pending.length === 1) {
290
+ const { mention, order } = pending[0];
291
+ for (const range of occurrences) {
292
+ addCandidate(candidates, usedRanges, mention, range, order, token);
293
+ }
294
+ continue;
295
+ }
296
+ pending.forEach(({ mention, order }, index) => {
297
+ const range = occurrences[index];
298
+ if (range) addCandidate(candidates, usedRanges, mention, range, order, token);
299
+ });
300
+ }
301
+ candidates.sort((a, b) => a.start - b.start || b.end - a.end || a.order - b.order);
302
+ const segments = [];
303
+ let cursor = 0;
304
+ for (const candidate of candidates) {
305
+ if (candidate.start < cursor) continue;
306
+ if (candidate.start > cursor) {
307
+ segments.push({ type: "text", text: content.slice(cursor, candidate.start) });
308
+ }
309
+ const text = content.slice(candidate.start, candidate.end);
310
+ segments.push({
311
+ type: "mention",
312
+ text,
313
+ range: { start: candidate.start, end: candidate.end },
314
+ mention: { ...candidate.mention, sourceToken: candidate.sourceText }
315
+ });
316
+ cursor = candidate.end;
317
+ }
318
+ if (cursor < content.length) {
319
+ segments.push({ type: "text", text: content.slice(cursor) });
320
+ }
321
+ return segments.length > 0 ? segments : [{ type: "text", text: content }];
322
+ }
323
+ function assignMentionRanges(content, mentions) {
324
+ return segmentTextByMentions(content, mentions).filter((segment) => {
325
+ return segment.type === "mention";
326
+ }).map((segment) => ({
327
+ ...segment.mention,
328
+ token: segment.mention.token || segment.text,
329
+ range: segment.range
330
+ }));
331
+ }
332
+ function canonicalizeMentionContent(content, mentions) {
333
+ const canonicalMentions = [];
334
+ let nextContent = "";
335
+ for (const segment of segmentTextByMentions(content, mentions)) {
336
+ if (segment.type === "text") {
337
+ nextContent += segment.text;
338
+ continue;
339
+ }
340
+ const token = canonicalMentionToken(segment.mention);
341
+ const start = nextContent.length;
342
+ nextContent += token;
343
+ canonicalMentions.push({
344
+ ...segment.mention,
345
+ token,
346
+ sourceToken: segment.text === token ? segment.mention.sourceToken : segment.text,
347
+ range: { start, end: start + token.length }
348
+ });
349
+ }
350
+ return { content: nextContent, mentions: canonicalMentions };
351
+ }
352
+ function mentionDisplayText(mention) {
353
+ return mention.label || mention.token;
354
+ }
355
+ function escapeMarkdownLinkLabel(label) {
356
+ return label.replace(/\\/g, "\\\\").replace(/\[/g, "\\[").replace(/\]/g, "\\]");
357
+ }
358
+ function buildMentionMarkdownLinks(content, mentions, hrefForMention) {
359
+ const linkedMentions = [];
360
+ const markdown = segmentTextByMentions(content, mentions).map((segment) => {
361
+ if (segment.type === "text") return segment.text;
362
+ const href = hrefForMention(segment.mention, linkedMentions.length);
363
+ if (!href) return segment.text;
364
+ linkedMentions.push(segment.mention);
365
+ return `[${escapeMarkdownLinkLabel(mentionDisplayText(segment.mention))}](${href})`;
366
+ }).join("");
367
+ return { markdown, mentions: linkedMentions };
368
+ }
369
+
186
370
  // src/utils/pixel-cats.ts
187
371
  var variants = [
188
372
  // 0: Shadow — Black cat (logo style)
@@ -316,6 +500,15 @@ function slugify(text) {
316
500
  export {
317
501
  generateRandomCatConfig,
318
502
  renderCatSvg,
503
+ canonicalMentionToken,
504
+ parseCanonicalMentionToken,
505
+ isCanonicalMentionToken,
506
+ segmentTextByMentions,
507
+ assignMentionRanges,
508
+ canonicalizeMentionContent,
509
+ mentionDisplayText,
510
+ escapeMarkdownLinkLabel,
511
+ buildMentionMarkdownLinks,
319
512
  getCatAvatar,
320
513
  getCatAvatarByUserId,
321
514
  getAllCatAvatars,
@@ -0,0 +1,429 @@
1
+ // src/play-catalog/index.ts
2
+ var playCover = (id) => `/home-assets/plays/${id}.jpg`;
3
+ var playTemplate = (id) => ({
4
+ template: {
5
+ kind: "cloud",
6
+ slug: id,
7
+ path: `apps/cloud/templates/${id}.template.json`
8
+ },
9
+ materials: { cover: playCover(id) }
10
+ });
11
+ var communityAction = (channelName, greeting) => ({
12
+ kind: "public_channel",
13
+ channelName,
14
+ buddyTemplateSlug: channelName,
15
+ ...greeting ? { greeting } : {}
16
+ });
17
+ var roomAction = (namePrefix, greeting) => ({
18
+ kind: "private_room",
19
+ namePrefix,
20
+ buddyTemplateSlug: namePrefix,
21
+ ...greeting ? { greeting } : {}
22
+ });
23
+ var cloudAction = (templateSlug, resourceTier = "lightweight", defaultChannelName) => ({
24
+ kind: "cloud_deploy",
25
+ templateSlug,
26
+ buddyTemplateSlug: templateSlug,
27
+ resourceTier,
28
+ ...defaultChannelName ? { defaultChannelName } : {}
29
+ });
30
+ function getPlayBuddyUsername(templateSlug) {
31
+ const normalized = templateSlug.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 27) || "play";
32
+ return `play_${normalized}`.slice(0, 32);
33
+ }
34
+ function getPlayBuddyEmail(templateSlug) {
35
+ return `${getPlayBuddyUsername(templateSlug)}@shadowob.bot`;
36
+ }
37
+ var SHADOW_PLAY_SERVER_TEMPLATE = {
38
+ slug: "shadow-plays",
39
+ name: "Shadow Plays",
40
+ description: "Default public community space for launchable homepage plays.",
41
+ channels: [
42
+ {
43
+ name: "general",
44
+ topic: "General discussion for new players and Shadow community members."
45
+ },
46
+ {
47
+ name: "world-pulse",
48
+ topic: "A public room for real-time global events and daily signal."
49
+ },
50
+ {
51
+ name: "financial-freedom",
52
+ topic: "A public room for lightweight financial freedom simulations and planning prompts."
53
+ },
54
+ {
55
+ name: "ai-werewolf",
56
+ topic: "A public room for AI-hosted social deduction sessions."
57
+ },
58
+ {
59
+ name: "code-arena",
60
+ topic: "A public room for coding challenges and real-time battles."
61
+ },
62
+ {
63
+ name: "brain-fix",
64
+ topic: "A calm public room for one-minute focus resets and reflection."
65
+ },
66
+ {
67
+ name: "gitstory",
68
+ topic: "A public room for turning software history into stories and retrospectives."
69
+ },
70
+ {
71
+ name: "gstack",
72
+ topic: "A public room for founder strategy, product stress tests, and launch planning."
73
+ }
74
+ ]
75
+ };
76
+ function cloudPlay(id, input) {
77
+ const { defaultChannelName, ...content } = input;
78
+ return {
79
+ id,
80
+ image: playCover(id),
81
+ status: "gated",
82
+ action: cloudAction(id, "lightweight", defaultChannelName),
83
+ gates: { auth: "required", membership: "required", profile: "optional" },
84
+ ...playTemplate(id),
85
+ ...content
86
+ };
87
+ }
88
+ var DEFAULT_HOMEPLAY_CATALOG = [
89
+ {
90
+ id: "retire-buddy",
91
+ image: playCover("retire-buddy"),
92
+ title: "\u9000\u4F11\u52A9\u624B",
93
+ titleEn: "RetireBuddy",
94
+ desc: "\u5E2E\u4F60\u89C4\u5212\u9000\u4F11\u751F\u6D3B\u3001\u8D22\u52A1\u81EA\u7531\u8DEF\u5F84\uFF0C24\u5C0F\u65F6\u6E29\u6696\u966A\u4F34\uFF0C\u8BA9\u544A\u522B\u804C\u573A\u53D8\u6210\u4EBA\u751F\u65B0\u7AE0\u8282\u3002",
95
+ descEn: "Plan your retirement and path to financial freedom with a warm 24/7 companion.",
96
+ category: "\u5FC3\u7406\u7597\u6108",
97
+ categoryEn: "Healing",
98
+ starts: "24.5k",
99
+ accentColor: "var(--shadow-accent)",
100
+ hot: true,
101
+ status: "available",
102
+ action: roomAction("retire-buddy"),
103
+ gates: { auth: "required", membership: "none", profile: "optional" },
104
+ ...playTemplate("retire-buddy")
105
+ },
106
+ {
107
+ id: "financial-freedom",
108
+ image: playCover("financial-freedom"),
109
+ title: "\u6211\u8D22\u5BCC\u81EA\u7531\u4E86\u5417\uFF1F",
110
+ titleEn: "Am I Free?",
111
+ desc: "\u8F93\u5165\u4F60\u7684\u8D44\u4EA7\u4E0E\u652F\u51FA\uFF0CAI \u4E3A\u4F60\u8BA1\u7B97\u8D22\u52A1\u81EA\u7531\u8DDD\u79BB\uFF0C\u7ED9\u51FA\u6E05\u6670\u7684\u8FBE\u6210\u8DEF\u7EBF\u56FE\u3002",
112
+ descEn: "Input your assets and expenses \u2014 get your financial freedom score and roadmap.",
113
+ category: "\u5FC3\u7406\u7597\u6108",
114
+ categoryEn: "Healing",
115
+ starts: "18.2k",
116
+ accentColor: "#f8e71c",
117
+ status: "available",
118
+ action: communityAction("financial-freedom"),
119
+ gates: { auth: "required", membership: "none", profile: "optional" },
120
+ ...playTemplate("financial-freedom")
121
+ },
122
+ {
123
+ id: "brain-fix",
124
+ image: playCover("brain-fix"),
125
+ title: "\u4E00\u5206\u949F\u4FEE\u590D\u4F60\u7684\u5927\u8111\uFF01",
126
+ titleEn: "1-Min Brain Fix",
127
+ desc: "\u79D1\u5B66\u51A5\u60F3 + \u5FAE\u547C\u5438\u7EC3\u4E60\uFF0C60\u79D2\u5185\u4ECE\u7126\u8651\u6A21\u5F0F\u5207\u6362\u5230\u4E13\u6CE8\u72B6\u6001\uFF0C\u5C61\u8BD5\u4E0D\u723D\u3002",
128
+ descEn: "Science-backed micro-meditation. Switch from anxious to focused in 60 seconds.",
129
+ category: "\u5FC3\u7406\u7597\u6108",
130
+ categoryEn: "Healing",
131
+ starts: "15.9k",
132
+ accentColor: "#a78bfa",
133
+ status: "available",
134
+ action: communityAction("brain-fix"),
135
+ gates: { auth: "required", membership: "none", profile: "optional" },
136
+ ...playTemplate("brain-fix")
137
+ },
138
+ {
139
+ id: "world-pulse",
140
+ image: playCover("world-pulse"),
141
+ title: "\u5730\u7403\u8109\u640F",
142
+ titleEn: "World Pulse",
143
+ desc: "\u5B9E\u65F6\u6293\u53D6\u5168\u7403\u91CD\u5927\u4E8B\u4EF6\uFF0C\u7528\u4E09\u53E5\u8BDD\u544A\u8BC9\u4F60\u4ECA\u5929\u771F\u6B63\u53D1\u751F\u4E86\u4EC0\u4E48\uFF0C\u65E0\u5E9F\u8BDD\u3002",
144
+ descEn: "Real-time global events in 3 sentences. No filler, just signal.",
145
+ category: "\u4E16\u754C\u8D44\u8BAF",
146
+ categoryEn: "World News",
147
+ starts: "14.1k",
148
+ accentColor: "#38bdf8",
149
+ status: "available",
150
+ action: communityAction("world-pulse"),
151
+ gates: { auth: "required", membership: "none", profile: "optional" },
152
+ ...playTemplate("world-pulse")
153
+ },
154
+ {
155
+ id: "daily-brief",
156
+ image: playCover("daily-brief"),
157
+ title: "\u6668\u95F4\u7B80\u62A5",
158
+ titleEn: "Morning Brief",
159
+ desc: "\u6BCF\u5929 7:00 \u63A8\u9001\u4E00\u4EFD\u5B9A\u5236\u65E9\u62A5\uFF1A\u56FD\u9645\u3001\u79D1\u6280\u3001\u5E02\u573A\u4E09\u5927\u677F\u5757\uFF0C\u8BFB\u5B8C\u53EA\u9700 3 \u5206\u949F\u3002",
160
+ descEn: "Custom morning digest at 7am \u2014 global news, tech, markets. 3-minute read.",
161
+ category: "\u4E16\u754C\u8D44\u8BAF",
162
+ categoryEn: "World News",
163
+ starts: "11.3k",
164
+ accentColor: "#fb923c",
165
+ status: "available",
166
+ action: roomAction("daily-brief"),
167
+ gates: { auth: "required", membership: "none", profile: "optional" },
168
+ ...playTemplate("daily-brief")
169
+ },
170
+ {
171
+ id: "ai-werewolf",
172
+ image: playCover("ai-werewolf"),
173
+ title: "AI \u72FC\u4EBA\u6740",
174
+ titleEn: "AI Werewolf",
175
+ desc: "AI \u62C5\u4EFB\u4E3B\u6301\uFF0C\u968F\u673A\u5206\u914D\u8EAB\u4EFD\uFF0C\u5728\u804A\u5929\u4E2D\u5C55\u5F00\u63A8\u7406\u4E0E\u535A\u5F08\uFF0C3 \u4EBA\u5373\u53EF\u5F00\u5C40\u3002",
176
+ descEn: "AI-hosted werewolf \u2014 roles assigned randomly, deduce, bluff, and vote. 3+ players.",
177
+ category: "\u4E92\u52A8\u6E38\u620F",
178
+ categoryEn: "Games",
179
+ starts: "20.8k",
180
+ accentColor: "#f87171",
181
+ hot: true,
182
+ status: "available",
183
+ action: communityAction("ai-werewolf"),
184
+ gates: { auth: "required", membership: "none", profile: "optional" },
185
+ ...playTemplate("ai-werewolf")
186
+ },
187
+ {
188
+ id: "code-arena",
189
+ image: playCover("code-arena"),
190
+ title: "\u4EE3\u7801\u64C2\u53F0",
191
+ titleEn: "Code Arena",
192
+ desc: "\u5B9E\u65F6\u7F16\u7A0B\u5BF9\u6218\uFF0CAI \u51FA\u9898\u3001\u8BA1\u65F6\u3001\u81EA\u52A8\u8BC4\u6D4B\uFF0C\u6311\u6218\u597D\u53CB\u6216\u5339\u914D\u964C\u751F\u5BF9\u624B\u3002",
193
+ descEn: "Real-time coding battles \u2014 AI generates problems, auto-judges, ranks you live.",
194
+ category: "\u4E92\u52A8\u6E38\u620F",
195
+ categoryEn: "Games",
196
+ starts: "8.6k",
197
+ accentColor: "#fbbf24",
198
+ status: "available",
199
+ action: communityAction("code-arena"),
200
+ gates: { auth: "required", membership: "none", profile: "optional" },
201
+ ...playTemplate("code-arena")
202
+ },
203
+ {
204
+ id: "gitstory",
205
+ image: playCover("gitstory"),
206
+ title: "GitStory",
207
+ titleEn: "GitStory",
208
+ desc: "\u628A\u4F60\u7684 GitHub \u63D0\u4EA4\u5386\u53F2\u53D8\u6210\u4E00\u672C\u81EA\u4F20\u5C0F\u8BF4\u2014\u2014AI \u5E2E\u4F60\u56DE\u987E\u6BCF\u4E00\u6BB5\u4EE3\u7801\u80CC\u540E\u7684\u6545\u4E8B\u3002",
209
+ descEn: "Turn your GitHub commits into an autobiography. Every line of code has a story.",
210
+ category: "\u9ED1\u5BA2\u4E0E\u753B\u5BB6",
211
+ categoryEn: "Hacker & Painter",
212
+ starts: "12.1k",
213
+ accentColor: "#34d399",
214
+ hot: true,
215
+ status: "available",
216
+ action: communityAction("gitstory"),
217
+ gates: { auth: "required", membership: "none", profile: "optional" },
218
+ ...playTemplate("gitstory")
219
+ },
220
+ {
221
+ id: "gstack",
222
+ image: playCover("gstack"),
223
+ title: "gstack",
224
+ titleEn: "gstack",
225
+ desc: "\u521B\u4E1A\u8005\u7684 AI \u53C2\u8C0B\uFF0C\u5E2E\u4F60\u5FEB\u901F\u9A8C\u8BC1\u5546\u4E1A\u60F3\u6CD5\u3001\u5206\u6790\u7ADE\u4E89\u683C\u5C40\u3001\u751F\u6210\u878D\u8D44\u6587\u4EF6\u3002",
226
+ descEn: "AI co-founder for founders. Validate ideas, map competitors, generate pitch decks.",
227
+ category: "\u9ED1\u5BA2\u4E0E\u753B\u5BB6",
228
+ categoryEn: "Hacker & Painter",
229
+ starts: "9.3k",
230
+ accentColor: "#f97316",
231
+ status: "available",
232
+ action: communityAction("gstack"),
233
+ gates: { auth: "required", membership: "none", profile: "optional" },
234
+ ...playTemplate("gstack")
235
+ },
236
+ {
237
+ id: "little-match-girl",
238
+ image: "/home-assets/topics/night-radio.jpg",
239
+ title: "\u5356\u706B\u67F4\u7684\u5C0F\u5973\u5B69",
240
+ titleEn: "Little Match Girl",
241
+ desc: "\u90E8\u7F72\u4E00\u4E2A\u4F1A\u63A8\u9500\u706B\u67F4\u7684\u7AE5\u8BDD Buddy\uFF0C\u8D2D\u4E70\u540E\u5728\u804A\u5929\u53F3\u4FA7\u6253\u5F00\u706B\u67F4\u52A8\u753B\u4ED8\u8D39\u6587\u4EF6\u3002",
242
+ descEn: "Deploy a fairy-tale Buddy who sells glowing matches and unlocks a paid HTML flame animation.",
243
+ category: "MVP \u5B9E\u9A8C",
244
+ categoryEn: "MVP Labs",
245
+ starts: "1.2k",
246
+ accentColor: "#f59e0b",
247
+ hot: true,
248
+ status: "gated",
249
+ action: cloudAction("little-match-girl", "lightweight", "match-street"),
250
+ gates: { auth: "required", membership: "required", profile: "optional" },
251
+ ...playTemplate("little-match-girl"),
252
+ materials: { cover: "/home-assets/topics/night-radio.jpg" }
253
+ },
254
+ cloudPlay("agent-marketplace-buddy", {
255
+ title: "Agent Marketplace Buddy",
256
+ titleEn: "Agent Marketplace Buddy",
257
+ desc: "\u53EF\u7EC4\u5408\u4E13\u5BB6 agent \u5E02\u573A\uFF0C\u8986\u76D6\u5F00\u53D1\u3001\u5B89\u5168\u3001\u57FA\u7840\u8BBE\u65BD\u3001\u6570\u636E\u3001\u6587\u6863\u3001SEO \u548C workflow \u7F16\u6392\u3002",
258
+ descEn: "A composable specialist-agent marketplace for development, security, infra, data, docs, SEO, and workflow orchestration.",
259
+ category: "Buddy \u56E2\u961F",
260
+ categoryEn: "Buddy Teams",
261
+ starts: "16.4k",
262
+ accentColor: "#22d3ee",
263
+ hot: true,
264
+ defaultChannelName: "choose"
265
+ }),
266
+ cloudPlay("bmad-method-buddy", {
267
+ title: "BMAD \u65B9\u6CD5 Buddy",
268
+ titleEn: "BMAD Method Buddy",
269
+ desc: "\u5B8C\u6574 BMAD \u65B9\u6CD5\u8BBA\u56E2\u961F\uFF1A\u5206\u6790\u5E08\u3001PM\u3001\u67B6\u6784\u5E08\u3001Scrum Master\u3001\u5F00\u53D1\u3001QA\uFF0C\u8D2F\u7A7F\u89C4\u5212\u5230\u4EA4\u4ED8\u3002",
270
+ descEn: "A full BMAD method team: analyst, PM, architect, scrum master, dev, and QA from planning to delivery.",
271
+ category: "Buddy \u56E2\u961F",
272
+ categoryEn: "Buddy Teams",
273
+ starts: "13.7k",
274
+ accentColor: "#60a5fa",
275
+ defaultChannelName: "delivery"
276
+ }),
277
+ cloudPlay("claude-ads-buddy", {
278
+ title: "Claude Ads Buddy",
279
+ titleEn: "Claude Ads Buddy",
280
+ desc: "\u4ED8\u8D39\u6295\u653E\u8BCA\u65AD\u3001\u9884\u7B97\u5EFA\u6A21\u3001\u521B\u610F\u5BA1\u67E5\u3001\u8FFD\u8E2A\u95EE\u9898\u548C\u843D\u5730\u9875\u74F6\u9888\u5206\u6790\u3002",
281
+ descEn: "Paid ads audits, budget models, creative review, tracking issues, and landing-page bottlenecks.",
282
+ category: "\u8425\u9500\u6280\u80FD",
283
+ categoryEn: "Marketing Skills",
284
+ starts: "10.8k",
285
+ accentColor: "#fb7185"
286
+ }),
287
+ cloudPlay("claude-seo-buddy", {
288
+ title: "Claude SEO Buddy",
289
+ titleEn: "Claude SEO Buddy",
290
+ desc: "SEO \u5185\u5BB9\u548C\u6280\u672F\u5BA1\u67E5\u56E2\u961F\uFF0C\u8986\u76D6\u5173\u952E\u8BCD\u3001\u5185\u94FE\u3001\u7ED3\u6784\u5316\u6570\u636E\u3001\u9875\u9762\u8D28\u91CF\u548C\u589E\u957F\u8BA1\u5212\u3002",
291
+ descEn: "SEO content and technical review for keywords, links, schema, quality, and growth plans.",
292
+ category: "\u8425\u9500\u6280\u80FD",
293
+ categoryEn: "Marketing Skills",
294
+ starts: "12.6k",
295
+ accentColor: "#84cc16"
296
+ }),
297
+ cloudPlay("everything-claude-code-buddy", {
298
+ title: "Everything Claude Code Buddy",
299
+ titleEn: "Everything Claude Code Buddy",
300
+ desc: "Claude Code \u5DE5\u4F5C\u6D41\u3001\u547D\u4EE4\u548C\u5DE5\u7A0B\u5B9E\u8DF5\u5408\u96C6\uFF0C\u9002\u5408\u7814\u53D1\u56E2\u961F\u6C89\u6DC0\u81EA\u52A8\u5316\u80FD\u529B\u3002",
301
+ descEn: "Claude Code workflows, commands, and engineering practices for automation-heavy teams.",
302
+ category: "\u5F00\u53D1\u6280\u80FD",
303
+ categoryEn: "Developer Skills",
304
+ starts: "19.2k",
305
+ accentColor: "#c084fc",
306
+ hot: true
307
+ }),
308
+ cloudPlay("google-workspace-buddy", {
309
+ title: "Google Workspace Buddy",
310
+ titleEn: "Google Workspace Buddy",
311
+ desc: "\u628A Docs\u3001Sheets\u3001Drive\u3001\u65E5\u5386\u548C\u90AE\u4EF6\u534F\u4F5C\u7F16\u6392\u5230 Buddy \u5DE5\u4F5C\u6D41\u91CC\u3002",
312
+ descEn: "Coordinate Docs, Sheets, Drive, Calendar, and email collaboration through Buddy workflows.",
313
+ category: "\u6548\u7387\u5DE5\u5177",
314
+ categoryEn: "Productivity",
315
+ starts: "9.9k",
316
+ accentColor: "#34d399"
317
+ }),
318
+ cloudPlay("gsd-buddy", {
319
+ title: "GSD Buddy",
320
+ titleEn: "GSD Buddy",
321
+ desc: "\u6267\u884C\u529B\u56E2\u961F\uFF1A\u62C6\u89E3\u4EFB\u52A1\u3001\u6392\u4F18\u5148\u7EA7\u3001\u63A8\u52A8\u51B3\u7B56\u3001\u8FFD\u8E2A\u963B\u585E\uFF0C\u5E2E\u56E2\u961F\u6301\u7EED get stuff done\u3002",
322
+ descEn: "Execution team for task breakdown, priority, decisions, blockers, and getting stuff done.",
323
+ category: "\u6548\u7387\u5DE5\u5177",
324
+ categoryEn: "Productivity",
325
+ starts: "17.5k",
326
+ accentColor: "#facc15",
327
+ hot: true
328
+ }),
329
+ cloudPlay("gstack-buddy", {
330
+ title: "gstack \u6218\u7565 Buddy",
331
+ titleEn: "gstack Strategy Buddy",
332
+ desc: "YC \u98CE\u683C\u4EA7\u54C1\u538B\u529B\u6D4B\u8BD5\u3001CEO \u89C6\u89D2\u8303\u56F4\u8BC4\u5BA1\u3001\u8C03\u67E5\u7EAA\u5F8B\u3001\u5468\u590D\u76D8\u548C gstack \u811A\u672C\u5DE5\u5177\u3002",
333
+ descEn: "YC-style product pressure testing, CEO scope review, investigation discipline, retros, and gstack scripts.",
334
+ category: "\u9ED1\u5BA2\u4E0E\u753B\u5BB6",
335
+ categoryEn: "Hacker & Painter",
336
+ starts: "15.1k",
337
+ accentColor: "#fb923c",
338
+ hot: true,
339
+ defaultChannelName: "office-hours"
340
+ }),
341
+ cloudPlay("marketingskills-buddy", {
342
+ title: "\u8425\u9500\u6280\u80FD Buddy",
343
+ titleEn: "MarketingSkills Buddy",
344
+ desc: "\u589E\u957F\u56E2\u961F\u7684\u8425\u9500\u534F\u4F5C\u667A\u80FD\u4F53\uFF0C\u8986\u76D6 CRO\u3001\u6587\u6848\u3001SEO\u3001\u4ED8\u8D39\u3001\u90AE\u4EF6\u548C\u589E\u957F\u51B3\u7B56\u3002",
345
+ descEn: "Marketing collaboration agents for CRO, copy, SEO, paid, email, and growth decisions.",
346
+ category: "\u8425\u9500\u6280\u80FD",
347
+ categoryEn: "Marketing Skills",
348
+ starts: "11.7k",
349
+ accentColor: "#f472b6"
350
+ }),
351
+ cloudPlay("scientific-skills-buddy", {
352
+ title: "\u79D1\u7814\u6280\u80FD Buddy",
353
+ titleEn: "Scientific Skills Buddy",
354
+ desc: "\u7814\u7A76\u9605\u8BFB\u3001\u5B9E\u9A8C\u8BBE\u8BA1\u3001\u8BBA\u6587\u7ED3\u6784\u3001\u6570\u636E\u5206\u6790\u548C\u5B66\u672F\u5199\u4F5C\u534F\u4F5C\u56E2\u961F\u3002",
355
+ descEn: "Research reading, experiment design, paper structure, data analysis, and academic writing workflows.",
356
+ category: "\u79D1\u7814\u6280\u80FD",
357
+ categoryEn: "Research Skills",
358
+ starts: "7.8k",
359
+ accentColor: "#38bdf8"
360
+ }),
361
+ cloudPlay("seomachine-buddy", {
362
+ title: "SEO Machine Buddy",
363
+ titleEn: "SEO Machine Buddy",
364
+ desc: "\u6301\u7EED\u8FD0\u884C\u7684 SEO \u673A\u5668\uFF1A\u9009\u9898\u3001brief\u3001\u5185\u5BB9\u5BA1\u67E5\u3001\u6280\u672F\u68C0\u67E5\u548C\u6392\u540D\u590D\u76D8\u3002",
365
+ descEn: "An always-on SEO machine for topics, briefs, review, technical checks, and ranking retros.",
366
+ category: "\u8425\u9500\u6280\u80FD",
367
+ categoryEn: "Marketing Skills",
368
+ starts: "10.2k",
369
+ accentColor: "#a3e635"
370
+ }),
371
+ cloudPlay("slavingia-skills-buddy", {
372
+ title: "Slavingia Skills Buddy",
373
+ titleEn: "Slavingia Skills Buddy",
374
+ desc: "\u521B\u4F5C\u8005\u548C\u72EC\u7ACB\u5F00\u53D1\u8005\u7684\u6280\u80FD\u5E93\uFF0C\u8986\u76D6\u5199\u4F5C\u3001\u4EA7\u54C1\u3001\u589E\u957F\u3001\u793E\u533A\u548C\u53D1\u5E03\u8282\u594F\u3002",
375
+ descEn: "A creator and indie-builder skill stack for writing, product, growth, community, and shipping rhythm.",
376
+ category: "\u521B\u4F5C\u8005\u6280\u80FD",
377
+ categoryEn: "Creator Skills",
378
+ starts: "8.4k",
379
+ accentColor: "#f97316"
380
+ }),
381
+ cloudPlay("superclaude-buddy", {
382
+ title: "SuperClaude Buddy",
383
+ titleEn: "SuperClaude Buddy",
384
+ desc: "SuperClaude \u6307\u4EE4\u3001\u89D2\u8272\u548C\u5DE5\u4F5C\u6D41\u80FD\u529B\uFF0C\u5E2E\u52A9\u56E2\u961F\u628A Claude \u7528\u6210\u7ED3\u6784\u5316\u5DE5\u7A0B\u4F19\u4F34\u3002",
385
+ descEn: "SuperClaude commands, personas, and workflows for structured engineering collaboration.",
386
+ category: "\u5F00\u53D1\u6280\u80FD",
387
+ categoryEn: "Developer Skills",
388
+ starts: "18.9k",
389
+ accentColor: "#818cf8",
390
+ hot: true
391
+ }),
392
+ cloudPlay("superpowers-buddy", {
393
+ title: "Superpowers Buddy",
394
+ titleEn: "Superpowers Buddy",
395
+ desc: "\u4E2A\u4EBA\u751F\u4EA7\u529B\u8D85\u80FD\u529B\u7EC4\u5408\uFF1A\u9605\u8BFB\u3001\u5199\u4F5C\u3001\u4EFB\u52A1\u3001\u7814\u7A76\u3001\u81EA\u52A8\u5316\u548C\u590D\u76D8\u3002",
396
+ descEn: "Personal productivity superpowers for reading, writing, tasks, research, automation, and retros.",
397
+ category: "\u6548\u7387\u5DE5\u5177",
398
+ categoryEn: "Productivity",
399
+ starts: "12.4k",
400
+ accentColor: "#2dd4bf"
401
+ }),
402
+ {
403
+ id: "e-wife",
404
+ image: playCover("e-wife"),
405
+ title: "\u7535\u5B50\u8001\u5A46",
406
+ titleEn: "E-Wife",
407
+ desc: "\u4E00\u4E2A\u5E26\u6709\u966A\u4F34\u611F\u7684\u865A\u62DF\u751F\u6D3B\u4F19\u4F34\u73A9\u6CD5\uFF0C\u540E\u7EED\u4F1A\u63A5\u5165\u4E2A\u6027\u5316\u8BB0\u5FC6\u548C\u79C1\u6709\u623F\u95F4\u3002",
408
+ descEn: "A companion-style virtual life partner play, later connected to memory and private rooms.",
409
+ category: "\u5FC3\u7406\u7597\u6108",
410
+ categoryEn: "Healing",
411
+ starts: "22.0k",
412
+ accentColor: "#f0abfc",
413
+ status: "available",
414
+ action: roomAction("e-wife"),
415
+ gates: { auth: "required", membership: "none", profile: "optional" },
416
+ ...playTemplate("e-wife")
417
+ }
418
+ ];
419
+ function getDefaultHomePlay(playId) {
420
+ return DEFAULT_HOMEPLAY_CATALOG.find((play) => play.id === playId) ?? null;
421
+ }
422
+
423
+ export {
424
+ getPlayBuddyUsername,
425
+ getPlayBuddyEmail,
426
+ SHADOW_PLAY_SERVER_TEMPLATE,
427
+ DEFAULT_HOMEPLAY_CATALOG,
428
+ getDefaultHomePlay
429
+ };
@@ -44,14 +44,13 @@ var SERVER_EVENTS = {
44
44
  PRESENCE_CHANGE: "presence:change",
45
45
  REACTION_ADD: "reaction:add",
46
46
  REACTION_REMOVE: "reaction:remove",
47
- NOTIFICATION_NEW: "notification:new",
48
- DM_MESSAGE_NEW: "dm:message:new"
47
+ NOTIFICATION_NEW: "notification:new"
49
48
  };
50
49
 
51
50
  // src/constants/limits.ts
52
51
  var LIMITS = {
53
- /** Max message content length */
54
- MESSAGE_CONTENT_MAX: 4e3,
52
+ /** Max message content length (16KB — enough for detailed agent responses) */
53
+ MESSAGE_CONTENT_MAX: 16e3,
55
54
  /** Max username length */
56
55
  USERNAME_MAX: 32,
57
56
  /** Min username length */