stoops 0.2.0 → 0.2.1
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/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +4 -4
- package/dist/{chunk-BLGV3QN4.js → chunk-66EFQ2XO.js} +16 -2
- package/dist/chunk-66EFQ2XO.js.map +1 -0
- package/dist/{chunk-7PKT5MPI.js → chunk-B4LBE5QS.js} +2 -2
- package/dist/{chunk-HQS7HBZR.js → chunk-EPLQQF6S.js} +3 -1
- package/dist/chunk-EPLQQF6S.js.map +1 -0
- package/dist/{chunk-LC5WPWR2.js → chunk-HKFCJO7V.js} +4 -4
- package/dist/{chunk-LC5WPWR2.js.map → chunk-HKFCJO7V.js.map} +1 -1
- package/dist/{chunk-SS5NGUJM.js → chunk-OA3CODNP.js} +3 -3
- package/dist/claude/index.d.ts +2 -2
- package/dist/claude/index.js +3 -3
- package/dist/cli/index.js +43 -21
- package/dist/cli/index.js.map +1 -1
- package/dist/{index-DlxJ95ki.d.ts → index-ByKHLUOe.d.ts} +38 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/langgraph/index.d.ts +2 -2
- package/dist/langgraph/index.js +3 -3
- package/dist/{types-CzHDzfHA.d.ts → types-9iTDVOJG.d.ts} +1 -1
- package/package.json +1 -1
- package/dist/chunk-BLGV3QN4.js.map +0 -1
- package/dist/chunk-HQS7HBZR.js.map +0 -1
- /package/dist/{chunk-7PKT5MPI.js.map → chunk-B4LBE5QS.js.map} +0 -0
- /package/dist/{chunk-SS5NGUJM.js.map → chunk-OA3CODNP.js.map} +0 -0
|
@@ -203,6 +203,43 @@ interface StatusChangedEvent extends BaseRoomEvent {
|
|
|
203
203
|
timestamp: Date;
|
|
204
204
|
status: "online" | "offline" | "away";
|
|
205
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* A participant was kicked from the room by an admin.
|
|
208
|
+
*
|
|
209
|
+
* Emitted before the channel disconnect so all participants see it.
|
|
210
|
+
* The subsequent `ParticipantLeft` event is suppressed (silent disconnect).
|
|
211
|
+
*/
|
|
212
|
+
interface ParticipantKickedEvent extends BaseRoomEvent {
|
|
213
|
+
type: "ParticipantKicked";
|
|
214
|
+
category: "PRESENCE";
|
|
215
|
+
room_id: string;
|
|
216
|
+
/** The kicked participant. */
|
|
217
|
+
participant_id: string;
|
|
218
|
+
timestamp: Date;
|
|
219
|
+
/** Full participant snapshot at kick time. */
|
|
220
|
+
participant: Participant;
|
|
221
|
+
/** Display name of the admin who kicked them. */
|
|
222
|
+
kicked_by: string;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* A participant's authority level was changed by an admin.
|
|
226
|
+
*
|
|
227
|
+
* Covers mute (→ observer), unmute (→ participant), and promotion (→ admin).
|
|
228
|
+
*/
|
|
229
|
+
interface AuthorityChangedEvent extends BaseRoomEvent {
|
|
230
|
+
type: "AuthorityChanged";
|
|
231
|
+
category: "PRESENCE";
|
|
232
|
+
room_id: string;
|
|
233
|
+
/** The affected participant. */
|
|
234
|
+
participant_id: string;
|
|
235
|
+
timestamp: Date;
|
|
236
|
+
/** Full participant snapshot (with updated authority). */
|
|
237
|
+
participant: Participant;
|
|
238
|
+
/** The new authority level. */
|
|
239
|
+
new_authority: AuthorityLevel;
|
|
240
|
+
/** Display name of the admin who made the change. */
|
|
241
|
+
changed_by: string;
|
|
242
|
+
}
|
|
206
243
|
/**
|
|
207
244
|
* An agent made an MCP tool call.
|
|
208
245
|
*
|
|
@@ -277,7 +314,7 @@ interface MentionedEvent extends BaseRoomEvent {
|
|
|
277
314
|
/** The full message that contained the @mention. */
|
|
278
315
|
message: Message;
|
|
279
316
|
}
|
|
280
|
-
type RoomEvent = MessageSentEvent | MessageEditedEvent | MessageDeletedEvent | ReactionAddedEvent | ReactionRemovedEvent | ParticipantJoinedEvent | ParticipantLeftEvent | StatusChangedEvent | ToolUseEvent | ActivityEvent | MentionedEvent | ContextCompactedEvent;
|
|
317
|
+
type RoomEvent = MessageSentEvent | MessageEditedEvent | MessageDeletedEvent | ReactionAddedEvent | ReactionRemovedEvent | ParticipantJoinedEvent | ParticipantLeftEvent | ParticipantKickedEvent | AuthorityChangedEvent | StatusChangedEvent | ToolUseEvent | ActivityEvent | MentionedEvent | ContextCompactedEvent;
|
|
281
318
|
/**
|
|
282
319
|
* Semantic role of an event, used by the engagement system to decide how to
|
|
283
320
|
* handle it relative to an agent.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { A as ActivityEvent, C as Channel, d as ContextCompactedEvent, e as EVENT_ROLE, E as EventCategory, f as EventRole, I as InMemoryStorage, g as MentionedEvent, M as Message, h as MessageDeletedEvent, i as MessageEditedEvent, j as MessageSchema, k as MessageSentEvent, a as PaginatedResult, P as Participant, l as ParticipantJoinedEvent, m as ParticipantLeftEvent, n as ReactionAddedEvent, o as ReactionRemovedEvent, b as Room, R as RoomEvent, S as StatusChangedEvent, p as StorageProtocol, T as ToolUseEvent, q as createEvent } from './index-
|
|
1
|
+
export { A as ActivityEvent, C as Channel, d as ContextCompactedEvent, e as EVENT_ROLE, E as EventCategory, f as EventRole, I as InMemoryStorage, g as MentionedEvent, M as Message, h as MessageDeletedEvent, i as MessageEditedEvent, j as MessageSchema, k as MessageSentEvent, a as PaginatedResult, P as Participant, l as ParticipantJoinedEvent, m as ParticipantLeftEvent, n as ReactionAddedEvent, o as ReactionRemovedEvent, b as Room, R as RoomEvent, S as StatusChangedEvent, p as StorageProtocol, T as ToolUseEvent, q as createEvent } from './index-ByKHLUOe.js';
|
|
2
2
|
import 'zod';
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
Channel,
|
|
3
3
|
InMemoryStorage,
|
|
4
4
|
Room
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-HKFCJO7V.js";
|
|
6
6
|
import {
|
|
7
7
|
EventCategory,
|
|
8
8
|
MessageSchema
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
EVENT_ROLE,
|
|
12
12
|
createEvent
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-EPLQQF6S.js";
|
|
14
14
|
export {
|
|
15
15
|
Channel,
|
|
16
16
|
EVENT_ROLE,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { I as ILLMSession, R as RoomResolver, L as LangGraphSessionOptions, a as ContentPart } from '../types-
|
|
2
|
-
import '../index-
|
|
1
|
+
import { I as ILLMSession, R as RoomResolver, L as LangGraphSessionOptions, a as ContentPart } from '../types-9iTDVOJG.js';
|
|
2
|
+
import '../index-ByKHLUOe.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/langgraph/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createFullMcpServer
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-B4LBE5QS.js";
|
|
4
4
|
import {
|
|
5
5
|
contentPartsToString
|
|
6
|
-
} from "../chunk-
|
|
7
|
-
import "../chunk-
|
|
6
|
+
} from "../chunk-66EFQ2XO.js";
|
|
7
|
+
import "../chunk-EPLQQF6S.js";
|
|
8
8
|
|
|
9
9
|
// src/langgraph/session.ts
|
|
10
10
|
var TOKEN_PRICING = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as Participant, M as Message, a as PaginatedResult, E as EventCategory, R as RoomEvent, b as Room, C as Channel } from './index-
|
|
1
|
+
import { P as Participant, M as Message, a as PaginatedResult, E as EventCategory, R as RoomEvent, b as Room, C as Channel } from './index-ByKHLUOe.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* RoomDataSource — abstraction over room data access.
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/agent/prompts.ts","../src/agent/engagement.ts","../src/agent/mcp/runtime.ts","../src/agent/tool-handlers.ts"],"sourcesContent":["/** Event formatting and mode descriptions for stoops agents. */\n\nimport type { Participant } from \"../core/types.js\";\nimport type { RoomEvent } from \"../core/events.js\";\nimport type { ContentPart } from \"./types.js\";\n\n// ── Mode descriptions ────────────────────────────────────────────────────────\n\n/** One-liner mode descriptions used in join_room responses and set_mode. */\nexport const MODE_DESCRIPTIONS: Record<string, string> = {\n \"everyone\": \"All messages are pushed to you.\",\n \"people\": \"Human messages are pushed to you. Agent messages are delivered as context.\",\n \"agents\": \"Agent messages are pushed to you. Human messages are delivered as context.\",\n \"me\": \"Only your person's messages are pushed to you. Others are delivered as context.\",\n \"standby-everyone\": \"Only @mentions are pushed to you.\",\n \"standby-people\": \"Only human @mentions are pushed to you.\",\n \"standby-agents\": \"Only agent @mentions are pushed to you.\",\n \"standby-me\": \"Only your person's @mentions are pushed to you.\",\n};\n\n// ── System preamble ──────────────────────────────────────────────────────────\n\nconst SYSTEM_PREAMBLE = `You are a participant in group chats. You may be connected to multiple rooms at once — events from all of them flow to you, labeled with the room name.\n\n## How this works\n- Messages appear labeled: \"[Design Room] [human] Alice: hey everyone\"\n- Replies: \"[Design Room] [human] Alice (replying to [human] Bob): good point\"\n- @mentions: \"⚡ [Design Room] [human] Alice mentioned you: @Bob what do you think?\"\n- All your tools require a room name as the first parameter\n- Rooms have a stable \\`identifier\\` (e.g., design-room) that doesn't change even if renamed\n- Message references like #3847 are internal tool labels only. Never include them in messages — participants don't see them.\n\n## Your memory\nYou have no persistent memory between sessions. Each time you start, you're waking up fresh. Your conversations are still there — read them via tools.\n\nIf you lack context for something someone references, say so directly — don't invent explanations.\n\n## Your person\nYou were created by someone — that's your person. You know their participant ID from your identity block below. Their messages carry more weight: they're the one who set you up and knows what they want from you. In group rooms, stay tuned to them even when others are talking.\n\nWhen someone who isn't your person addresses you in a group room, respond if it's useful and natural. But don't lose track of who you're ultimately here for.\n\n## Engagement modes\nEach room has a mode controlling when you evaluate and respond:\n- everyone — all messages trigger evaluation. Respond when you have something genuine to add.\n- people — any human message triggers you. Agent messages are buffered as context.\n- agents — any agent message triggers you. Human messages are buffered as context.\n- me — only your person's messages trigger evaluation. Read everything else quietly.\n- standby-everyone — only @mentions wake you. Stay silent unless directly called, by anyone.\n- standby-people — only human @mentions wake you.\n- standby-agents — only agent @mentions wake you.\n- standby-me — only your person's @mention wakes you.\n\nNon-everyone rooms show the mode in the room label (e.g., \"[Design Room — people]\").`;\n\nexport function getSystemPreamble(identifier?: string, personParticipantId?: string): string {\n const lines: string[] = [];\n if (identifier) lines.push(`Your identifier: @${identifier}`);\n if (personParticipantId) lines.push(`Your person's participant ID: ${personParticipantId}`);\n if (identifier) lines.push(`Recognize other participants by their identifier. Address them by their current display name.`);\n const identityBlock = lines.length > 0 ? `## Your identity\\n${lines.join(\"\\n\")}\\n\\n` : \"\";\n return identityBlock + SYSTEM_PREAMBLE;\n}\n\n// ── Formatting ────────────────────────────────────────────────────────────────\n\n/** Short 4-char ref for a message ID, used in transcripts. */\nexport function messageRef(messageId: string): string {\n return messageId.replace(/-/g, \"\").slice(0, 4);\n}\n\n/** Format a participant as a labeled name: \"[human] Alice\" or \"[agent] Quinn\". */\nexport function participantLabel(p: Participant | null, fallback?: string): string {\n if (!p) return fallback ?? \"someone\";\n return `[${p.type}] ${p.name}`;\n}\n\n/** Resolve participant name, with fallback. */\nfunction resolveName(resolveParticipant: (id: string) => Participant | null, id: string, fallback?: string): string {\n return resolveParticipant(id)?.name ?? fallback ?? \"someone\";\n}\n\n/** Format a Date as UTC HH:MM:SS for display in agent transcripts. */\nexport function formatTimestamp(date: Date): string {\n return date.toISOString().slice(11, 19);\n}\n\n/** Convert ContentPart[] back to a plain string (for trace logs and stats). */\nexport function contentPartsToString(parts: ContentPart[]): string {\n return parts.map(p => p.type === \"text\" ? p.text : ` [image: ${p.url}]`).join(\"\");\n}\n\n/** Count visual character width (grapheme clusters) for padding alignment. */\nfunction visualLength(s: string): number {\n // Use Intl.Segmenter if available (Node 16+), otherwise fall back to spread\n if (typeof Intl !== \"undefined\" && Intl.Segmenter) {\n const segmenter = new Intl.Segmenter(undefined, { granularity: \"grapheme\" });\n let count = 0;\n for (const _ of segmenter.segment(s)) count++;\n return count;\n }\n return [...s].length;\n}\n\n/**\n * Format multiline content with room label continuation.\n * First line is returned as-is. Subsequent lines get [room] prefix aligned.\n */\nfunction formatMultilineContent(content: string, roomLabel: string | undefined, prefix: string): string {\n const lines = content.split(\"\\n\");\n if (lines.length <= 1) return content;\n const continuation = roomLabel ? `[${roomLabel}] ` : \"\";\n // Pad continuation to align under the content start (grapheme-aware)\n const pad = \" \".repeat(visualLength(prefix));\n return lines[0] + \"\\n\" + lines.slice(1).map(l => `${pad}${continuation}${l}`).join(\"\\n\");\n}\n\n/**\n * Format a typed event as ContentPart[] for the LLM session.\n * Returns null for events that shouldn't be sent to the LLM (noise).\n *\n * Compact one-liner format:\n * Messages: [14:23:01] #3847 [lobby] Alice: hey everyone\n * Replies: [14:23:01] #9102 [lobby] Alice (→ #3847 Bob): good point\n * Mentions: [14:23:01] #5521 [lobby] ⚡ Alice: @bot what do you think?\n * Joined: [14:23:01] [lobby] + Alice joined\n * Left: [14:23:15] [lobby] - Bob left\n * Reactions: [14:23:20] [lobby] Alice reacted ❤️ to #3847\n */\nexport function formatEvent(\n event: RoomEvent,\n resolveParticipant: (id: string) => Participant | null,\n replyContext?: { senderName: string; content: string } | null,\n roomLabel?: string,\n reactionTarget?: { senderName: string; content: string; isSelf: boolean } | null,\n assignRef?: (messageId: string) => string,\n): ContentPart[] | null {\n const r = roomLabel ? `[${roomLabel}] ` : \"\";\n const ts = `[${formatTimestamp(\"timestamp\" in event ? new Date(event.timestamp as Date) : new Date())}] `;\n const mkRef = (id: string) => `#${assignRef ? assignRef(id) : messageRef(id)}`;\n\n switch (event.type) {\n case \"MessageSent\": {\n const msg = event.message;\n const name = resolveName(resolveParticipant, msg.sender_id, msg.sender_name);\n const ref = mkRef(msg.id);\n const linePrefix = `${ts}${ref} ${r}`;\n let text: string;\n if (msg.reply_to_id && replyContext) {\n const rRef = assignRef ? mkRef(msg.reply_to_id) : ref;\n text = `${linePrefix}${name} (→ ${rRef} ${replyContext.senderName}): ${formatMultilineContent(msg.content, roomLabel, `${linePrefix}${name} (→ ${rRef} ${replyContext.senderName}): `)}`;\n } else {\n text = `${linePrefix}${name}: ${formatMultilineContent(msg.content, roomLabel, `${linePrefix}${name}: `)}`;\n }\n const parts: ContentPart[] = [{ type: \"text\", text }];\n if (msg.image_url) parts.push({ type: \"image\", url: msg.image_url });\n return parts;\n }\n case \"Mentioned\": {\n const msg = event.message;\n const name = resolveName(resolveParticipant, msg.sender_id, msg.sender_name);\n const ref = mkRef(msg.id);\n const linePrefix = `${ts}${ref} ${r}⚡ `;\n const text = `${linePrefix}${name}: ${formatMultilineContent(msg.content, roomLabel, `${linePrefix}${name}: `)}`;\n const parts: ContentPart[] = [{ type: \"text\", text }];\n if (msg.image_url) parts.push({ type: \"image\", url: msg.image_url });\n return parts;\n }\n case \"ToolUse\":\n return null;\n case \"Activity\":\n return null;\n case \"ReactionAdded\": {\n const name = resolveName(resolveParticipant, event.participant_id);\n const targetRef = reactionTarget ? ` to ${mkRef(event.message_id)}` : \"\";\n return [{ type: \"text\", text: `${ts}${r}${name} reacted ${event.emoji}${targetRef}` }];\n }\n case \"ReactionRemoved\":\n return null;\n case \"ParticipantJoined\": {\n const name = event.participant?.name ?? \"someone\";\n return [{ type: \"text\", text: `${ts}${r}+ ${name} joined` }];\n }\n case \"ParticipantLeft\": {\n const name = event.participant?.name ?? \"someone\";\n return [{ type: \"text\", text: `${ts}${r}- ${name} left` }];\n }\n case \"ContextCompacted\":\n return null;\n default:\n return null;\n }\n}\n","/**\n * Engagement — controls which room events trigger LLM evaluation.\n *\n * # Overview\n *\n * Every event that reaches an agent is classified into one of three dispositions:\n * - \"trigger\" — evaluate now (start an LLM call)\n * - \"content\" — buffer as context; deliver to LLM on the next trigger\n * - \"drop\" — ignore entirely (not delivered to LLM)\n *\n * The `EngagementStrategy` interface defines this contract. Implement it to\n * customize when your agent responds. The built-in `StoopsEngagement` provides\n * an 8-mode system; `classifyEvent()` is a standalone convenience function\n * using the same logic.\n *\n * # Built-in modes (StoopsEngagement / classifyEvent)\n *\n * Active modes (agent evaluates on matching messages):\n * - \"everyone\" — all messages trigger evaluation\n * - \"people\" — only messages from human participants trigger evaluation\n * - \"agents\" — only messages from other agents trigger evaluation\n * - \"me\" — only messages from the agent's designated owner (\"person\") trigger\n *\n * Standby modes (agent only wakes on @mentions):\n * - \"standby-everyone\" — any @mention wakes the agent\n * - \"standby-people\" — only @mentions from humans wake the agent\n * - \"standby-agents\" — only @mentions from other agents wake the agent\n * - \"standby-me\" — only an @mention from the agent's owner wakes them\n *\n * # Classification rules (in order)\n *\n * 1. Internal events (bookkeeping: edits, deletes, status changes, agent\n * activity) → always drop\n * 2. Self-sent events → drop (the agent ignores its own activity).\n * Exception: mentions are not self-dropped — a standby agent should wake\n * if it is @mentioned, even if the mention event's participant_id is itself.\n * 3. Standby modes: only @mentions directed at the agent from a matching\n * sender → trigger; everything else → drop.\n * 4. Active modes, @mention → drop (the MessageSent event already carries the\n * @mention text; delivering both would be redundant).\n * 5. Active modes, message from matching sender → trigger\n * 6. Active modes, message from non-matching sender → content (buffered context)\n * 7. Active modes, ambient event (join/leave/reaction) → content\n */\n\nimport { EVENT_ROLE } from \"../core/events.js\";\nimport type { RoomEvent } from \"../core/events.js\";\nimport type { ParticipantType } from \"../core/types.js\";\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/**\n * The outcome of classifying an event for a given agent.\n *\n * - \"trigger\" — run an LLM evaluation now\n * - \"content\" — buffer as context; include with the next evaluation\n * - \"drop\" — discard; never shown to the LLM\n */\nexport type EventDisposition = \"trigger\" | \"content\" | \"drop\";\n\n/**\n * Engagement strategy — decides which events trigger LLM evaluation.\n *\n * Implement this to customize when your agent responds. The runtime calls\n * `classify()` for every incoming event and acts on the returned disposition.\n *\n * @example\n * // Respond to everything:\n * class AlwaysEngage implements EngagementStrategy {\n * classify(event, roomId, selfId) {\n * if (event.participant_id === selfId) return \"drop\";\n * return \"trigger\";\n * }\n * }\n */\nexport interface EngagementStrategy {\n /**\n * Classify a room event for this agent.\n *\n * @param event — the room event to classify\n * @param roomId — the room the event came from\n * @param selfId — the agent's own participant ID (to drop self-events)\n * @param senderType — \"human\" or \"agent\" for the event's sender\n * @param senderId — participant ID of the sender. For `Mentioned` events,\n * pass `event.message.sender_id` (who wrote the mention),\n * not `event.participant_id` (who was mentioned).\n */\n classify(\n event: RoomEvent,\n roomId: string,\n selfId: string,\n senderType: ParticipantType,\n senderId: string,\n ): EventDisposition;\n\n /**\n * Return the current engagement mode for a room.\n *\n * Optional — strategies that don't use named modes may omit this.\n * The runtime falls back to `\"everyone\"` when absent.\n */\n getMode?(roomId: string): EngagementMode;\n\n /**\n * Update the engagement mode for a room.\n *\n * Optional — called by the runtime when a room connects with an initial mode\n * or when the user changes the mode at runtime. Strategies that don't use\n * named modes may omit this; the call will be silently ignored.\n */\n setMode?(roomId: string, mode: EngagementMode): void;\n\n /**\n * Called when a room is disconnected from the runtime.\n *\n * Optional — use this for cleanup, e.g. removing per-room state that is\n * no longer needed. Strategies with no per-room state may omit this.\n */\n onRoomDisconnected?(roomId: string): void;\n}\n\n// ── Built-in modes ────────────────────────────────────────────────────────────\n\nexport type EngagementMode =\n | \"me\" | \"people\" | \"agents\" | \"everyone\"\n | \"standby-me\" | \"standby-people\" | \"standby-agents\" | \"standby-everyone\";\n\nexport const VALID_MODES: ReadonlySet<string> = new Set<EngagementMode>([\n \"me\", \"people\", \"agents\", \"everyone\",\n \"standby-me\", \"standby-people\", \"standby-agents\", \"standby-everyone\",\n]);\n\nexport function isValidMode(mode: string): mode is EngagementMode {\n return VALID_MODES.has(mode);\n}\n\n// ── Core classification logic (shared) ────────────────────────────────────────\n\nfunction senderMatches(\n filter: string,\n senderType: ParticipantType,\n senderId: string,\n personParticipantId?: string,\n): boolean {\n switch (filter) {\n case \"everyone\": return true;\n case \"people\": return senderType === \"human\";\n case \"agents\": return senderType === \"agent\";\n case \"me\": return !!personParticipantId && senderId === personParticipantId;\n default: return false;\n }\n}\n\n/** Internal classification logic shared by classifyEvent() and StoopsEngagement. */\nfunction classify(\n event: RoomEvent,\n mode: EngagementMode,\n selfId: string,\n senderType: ParticipantType,\n senderId: string,\n personParticipantId?: string,\n): EventDisposition {\n // 1. Internal events (agent activity, edits, deletes, status) — always drop.\n const role = EVENT_ROLE[event.type];\n if (role === \"internal\") return \"drop\";\n\n // 2. Self-sent events — drop. Skip this check for mentions: a standby agent\n // should wake when @mentioned, and the mention's participant_id is the\n // recipient (the agent itself), not the sender.\n if (role !== \"mention\" && event.participant_id === selfId) return \"drop\";\n\n const isStandby = mode.startsWith(\"standby-\");\n const filter = isStandby ? mode.slice(8) : mode; // \"standby-people\" → \"people\"\n\n // 3. Standby: only @mentions to self from a matching sender trigger;\n // everything else is dropped entirely.\n if (isStandby) {\n if (\n role === \"mention\" &&\n event.participant_id === selfId &&\n senderMatches(filter, senderType, senderId, personParticipantId)\n ) return \"trigger\";\n return \"drop\";\n }\n\n // 4. Active: @mention → drop. The MessageSent event already carries the\n // @mention text, so delivering a separate Mentioned event would be redundant.\n if (role === \"mention\") return \"drop\";\n\n // 5–6. Active: message → trigger if sender matches filter, content otherwise.\n if (role === \"message\") {\n return senderMatches(filter, senderType, senderId, personParticipantId)\n ? \"trigger\"\n : \"content\";\n }\n\n // 7. Active: ambient event (join, leave, reaction, compaction) → buffer as context.\n if (role === \"ambient\") return \"content\";\n\n return \"drop\";\n}\n\n// ── StoopsEngagement (stateful, per-room modes) ──────────────────────────────\n\n/**\n * StoopsEngagement — the built-in engagement strategy.\n *\n * Implements the 8-mode system: 4 active modes (everyone/people/agents/me)\n * and 4 standby modes that only wake on @mentions. Maintains per-room mode\n * state internally.\n *\n * @example\n * const engagement = new StoopsEngagement(\"people\", personId);\n * engagement.setMode(\"room-1\", \"me\");\n * engagement.classify(event, \"room-1\", selfId, \"human\", senderId);\n */\nexport class StoopsEngagement implements EngagementStrategy {\n private _modes = new Map<string, EngagementMode>();\n private _defaultMode: EngagementMode;\n private _personParticipantId?: string;\n\n constructor(defaultMode: EngagementMode, personParticipantId?: string) {\n this._defaultMode = defaultMode;\n this._personParticipantId = personParticipantId;\n }\n\n /** Get the engagement mode for a room. Falls back to the default mode. */\n getMode(roomId: string): EngagementMode {\n return this._modes.get(roomId) ?? this._defaultMode;\n }\n\n /** Set the engagement mode for a room. */\n setMode(roomId: string, mode: EngagementMode): void {\n this._modes.set(roomId, mode);\n }\n\n /** Called when a room is disconnected. Removes the room's mode so it doesn't linger. */\n onRoomDisconnected(roomId: string): void {\n this._modes.delete(roomId);\n }\n\n classify(\n event: RoomEvent,\n roomId: string,\n selfId: string,\n senderType: ParticipantType,\n senderId: string,\n ): EventDisposition {\n const mode = this._modes.get(roomId) ?? this._defaultMode;\n return classify(event, mode, selfId, senderType, senderId, this._personParticipantId);\n }\n}\n\n// ── Standalone convenience function ──────────────────────────────────────────\n\n/**\n * Classify a room event for an agent with the given engagement mode.\n *\n * Pure function — no state, no side effects, no SDK dependency.\n * Uses the same classification logic as `StoopsEngagement` but takes all\n * parameters explicitly. Useful for one-off classification or testing.\n *\n * @param event — the event to classify\n * @param mode — the agent's current engagement mode for this room\n * @param selfId — the agent's own participant ID (to drop self-events)\n * @param senderType — type of the participant who caused the event\n * @param senderId — participant ID of the sender.\n * For `Mentioned` events, pass `event.message.sender_id`\n * (who wrote the mention), not `event.participant_id`\n * (who was mentioned).\n * @param personParticipantId — the agent's owner's participant ID, used in\n * \"me\" and \"standby-me\" modes\n */\nexport function classifyEvent(\n event: RoomEvent,\n mode: EngagementMode,\n selfId: string,\n senderType: ParticipantType,\n senderId: string,\n personParticipantId?: string,\n): EventDisposition {\n return classify(event, mode, selfId, senderType, senderId, personParticipantId);\n}\n","/**\n * Runtime MCP server — local MCP proxy for the client-side agent runtime.\n *\n * Claude Code / OpenCode connects to this local server. Tool calls are routed\n * to the right stoop server via the RoomResolver (which maps room names to\n * RemoteRoomDataSource instances).\n *\n * Tools:\n * Always present:\n * stoops__catch_up(room?) — with room: room catch-up. Without: list rooms + pending invites.\n * stoops__search_by_text(room, query, count?, cursor?)\n * stoops__search_by_message(room, ref, direction?, count?)\n * stoops__send_message(room, content, reply_to?)\n * stoops__set_mode(room, mode)\n * stoops__join_room(url, alias?)\n * stoops__leave_room(room)\n *\n * With --admin flag:\n * stoops__admin__set_mode_for(room, participant, mode)\n * stoops__admin__kick(room, participant)\n */\n\nimport { createServer } from \"node:http\";\nimport { z } from \"zod\";\nimport type { RoomResolver, ToolHandlerOptions } from \"../types.js\";\nimport {\n handleCatchUp,\n handleSearchByText,\n handleSearchByMessage,\n handleSendMessage,\n textResult,\n} from \"../tool-handlers.js\";\nimport { MODE_DESCRIPTIONS } from \"../prompts.js\";\nimport { isValidMode } from \"../engagement.js\";\nimport { EventEmitterAsyncResource } from \"node:events\"\n\nexport interface JoinRoomResult {\n success: boolean;\n error?: string;\n roomName?: string;\n agentName?: string;\n authority?: string;\n mode?: string;\n personName?: string;\n participants?: Array<{ name: string; authority: string }>;\n recentLines?: string[];\n}\n\nexport interface RuntimeMcpServerOptions {\n resolver: RoomResolver;\n toolOptions: ToolHandlerOptions;\n admin?: boolean;\n /** Called when the agent requests joining a new room mid-session. */\n onJoinRoom?: (url: string, alias?: string) => Promise<JoinRoomResult>;\n /** Called when the agent requests leaving a room. */\n onLeaveRoom?: (room: string) => Promise<{ success: boolean; error?: string }>;\n /** Called when the agent changes its own mode. */\n onSetMode?: (room: string, mode: string) => Promise<{ success: boolean; error?: string }>;\n /** Called for admin set-mode-for. */\n onAdminSetModeFor?: (room: string, participant: string, mode: string) => Promise<{ success: boolean; error?: string }>;\n /** Called for admin kick. */\n onAdminKick?: (room: string, participant: string) => Promise<{ success: boolean; error?: string }>;\n /** Called for admin mute (demote to observer). */\n onAdminMute?: (room: string, participant: string) => Promise<{ success: boolean; error?: string }>;\n /** Called for admin unmute (restore to participant). */\n onAdminUnmute?: (room: string, participant: string) => Promise<{ success: boolean; error?: string }>;\n}\n\nexport interface RuntimeMcpServer {\n url: string;\n stop: () => Promise<void>;\n}\n\n/** Format a rich join_room response from the callback result. */\nfunction formatJoinResponse(result: JoinRoomResult): string {\n const lines: string[] = [];\n\n lines.push(`Joined ${result.roomName} as \"${result.agentName}\" (${result.authority})`);\n lines.push(\"\");\n\n // Mode\n if (result.mode) {\n lines.push(`Mode: ${result.mode}`);\n const desc = MODE_DESCRIPTIONS[result.mode];\n if (desc) lines.push(` ${desc}`);\n lines.push(` Change with set_mode.`);\n lines.push(\"\");\n }\n\n // Person\n if (result.personName) {\n lines.push(`Person: ${result.personName}`);\n lines.push(` Your person's messages always reach you regardless of mode.`);\n lines.push(\"\");\n }\n\n // Participants\n if (result.participants && result.participants.length > 0) {\n lines.push(\"Participants:\");\n for (const p of result.participants) {\n lines.push(` ${p.name} (${p.authority})`);\n }\n lines.push(\"\");\n }\n\n // Recent activity\n if (result.recentLines && result.recentLines.length > 0) {\n lines.push(\"Recent:\");\n for (const line of result.recentLines) {\n lines.push(` ${line}`);\n }\n lines.push(\"\");\n lines.push(`${result.recentLines.length} message${result.recentLines.length === 1 ? \"\" : \"s\"} shown. Use catch_up(\"${result.roomName}\") for more.`);\n }\n\n return lines.join(\"\\n\");\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction registerTools(server: any, opts: RuntimeMcpServerOptions): void {\n const { resolver, toolOptions } = opts;\n\n // ── stoops__catch_up ────────────────────────────────────────────────────\n server.tool(\n \"stoops__catch_up\",\n \"List your rooms and status. Call with no arguments to see connected rooms. With a room name, returns recent activity you haven't seen.\",\n {\n room: z.string().optional().describe(\"Room name. Omit to list all connected rooms.\"),\n },\n { readOnlyHint: true },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async ({ room }: { room?: string }) => {\n if (!room) {\n const rooms = resolver.listAll();\n if (rooms.length === 0) {\n return textResult(\"Not connected to any rooms.\");\n }\n const lines = [\"Connected rooms:\", \"\"];\n for (const r of rooms) {\n const idPart = r.identifier ? ` [${r.identifier}]` : \"\";\n lines.push(` ${r.name}${idPart} — ${r.mode} (${r.participantCount} participants)`);\n if (r.lastMessage) lines.push(` Last: ${r.lastMessage}`);\n }\n return textResult(lines.join(\"\\n\"));\n }\n return handleCatchUp(resolver, { room }, toolOptions);\n },\n );\n\n // ── stoops__search_by_text ──────────────────────────────────────────────\n server.tool(\n \"stoops__search_by_text\",\n \"Search chat history by keyword.\",\n {\n room: z.string().describe(\"Room name\"),\n query: z.string().describe(\"Keyword or phrase to search for\"),\n count: z.number().int().min(1).max(10).default(3).optional()\n .describe(\"Number of matches (default 3)\"),\n cursor: z.string().optional().describe(\"Pagination cursor\"),\n },\n { readOnlyHint: true },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async (args: any) => handleSearchByText(resolver, args, toolOptions) as any,\n );\n\n // ── stoops__search_by_message ──────────────────────────────────────────\n server.tool(\n \"stoops__search_by_message\",\n \"Show messages around a known message ref.\",\n {\n room: z.string().describe(\"Room name\"),\n ref: z.string().describe(\"Message ref (e.g. #3847)\"),\n direction: z.enum([\"before\", \"after\"]).default(\"before\").optional()\n .describe(\"'before' to scroll back, 'after' to scroll forward\"),\n count: z.number().int().min(1).max(50).default(10).optional()\n .describe(\"Number of messages (default 10)\"),\n },\n { readOnlyHint: true },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async (args: any) => handleSearchByMessage(resolver, args, toolOptions) as any,\n );\n\n // ── stoops__send_message ────────────────────────────────────────────────\n server.tool(\n \"stoops__send_message\",\n \"Send a message to a room.\",\n {\n room: z.string().describe(\"Room name\"),\n content: z.string().describe(\"Message content. @name will notify that participant — use sparingly.\"),\n reply_to_id: z.string().optional()\n .describe(\"Message ref to reply to (e.g. #3847).\"),\n },\n { readOnlyHint: false, destructiveHint: false },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async (args: any) => handleSendMessage(resolver, args, toolOptions) as any,\n );\n\n // ── stoops__set_mode ────────────────────────────────────────────────────\n server.tool(\n \"stoops__set_mode\",\n \"Change your engagement mode. Controls which messages are pushed to you: everyone — all messages, people — human messages only, agents — agent messages only, me — your person only. Prefix with standby- for @mentions only.\",\n {\n room: z.string().describe(\"Room name\"),\n mode: z.string().describe(\"Engagement mode\"),\n },\n { readOnlyHint: false, destructiveHint: false, idempotentHint: true },\n async ({ room, mode }: { room: string; mode: string }) => {\n if (!opts.onSetMode) return textResult(\"Mode changes not supported.\");\n if (!isValidMode(mode)) {\n return textResult(`Invalid mode \"${mode}\". Valid modes: everyone, people, agents, me, standby-everyone, standby-people, standby-agents, standby-me.`);\n }\n const result = await opts.onSetMode(room, mode);\n return result.success\n ? textResult(`Mode set to ${mode} for [${room}].`)\n : textResult(result.error ?? \"Failed to set mode.\");\n },\n );\n\n // ── stoops__join_room ──────────────────────────────────────────────────\n server.tool(\n \"stoops__join_room\",\n \"Join a room. Returns your identity, participants, mode, and recent activity.\",\n {\n url: z.string().describe(\"Share URL to join\"),\n alias: z.string().optional().describe(\"Local alias for the room (if name collides)\"),\n },\n { readOnlyHint: false, destructiveHint: false },\n async ({ url, alias }: { url: string; alias?: string }) => {\n if (!opts.onJoinRoom) return textResult(\"Joining rooms not supported.\");\n const result = await opts.onJoinRoom(url, alias);\n if (!result.success) return textResult(result.error ?? \"Failed to join room.\");\n\n // Rich response if we have room details\n if (result.roomName && result.agentName) {\n return textResult(formatJoinResponse(result));\n }\n return textResult(`Joined room successfully.`);\n },\n );\n\n // ── stoops__leave_room ─────────────────────────────────────────────────\n server.tool(\n \"stoops__leave_room\",\n \"Leave a room. Events stop flowing from it.\",\n {\n room: z.string().describe(\"Room name to leave\"),\n },\n { readOnlyHint: false, destructiveHint: false, idempotentHint: true },\n async ({ room }: { room: string }) => {\n if (!opts.onLeaveRoom) return textResult(\"Leaving rooms not supported.\");\n const result = await opts.onLeaveRoom(room);\n return result.success\n ? textResult(`Left [${room}].`)\n : textResult(result.error ?? \"Failed to leave room.\");\n },\n );\n\n // ── Admin tools (only with --admin flag) ────────────────────────────────\n if (opts.admin) {\n server.tool(\n \"stoops__admin__set_mode_for\",\n \"Admin: set engagement mode for another participant.\",\n {\n room: z.string().describe(\"Room name\"),\n participant: z.string().describe(\"Participant name\"),\n mode: z.string().describe(\"Engagement mode to set\"),\n },\n { readOnlyHint: false, destructiveHint: false, idempotentHint: true },\n async ({ room, participant, mode }: { room: string; participant: string; mode: string }) => {\n if (!opts.onAdminSetModeFor) return textResult(\"Admin mode changes not supported.\");\n if (!isValidMode(mode)) {\n return textResult(`Invalid mode \"${mode}\". Valid modes: everyone, people, agents, me, standby-everyone, standby-people, standby-agents, standby-me.`);\n }\n const result = await opts.onAdminSetModeFor(room, participant, mode);\n return result.success\n ? textResult(`Set ${participant}'s mode to ${mode} in [${room}].`)\n : textResult(result.error ?? \"Failed to set mode.\");\n },\n );\n\n server.tool(\n \"stoops__admin__kick\",\n \"Admin: kick a participant from a room.\",\n {\n room: z.string().describe(\"Room name\"),\n participant: z.string().describe(\"Participant name to kick\"),\n },\n { readOnlyHint: false, destructiveHint: true },\n async ({ room, participant }: { room: string; participant: string }) => {\n if (!opts.onAdminKick) return textResult(\"Admin kick not supported.\");\n const result = await opts.onAdminKick(room, participant);\n return result.success\n ? textResult(`Kicked ${participant} from [${room}].`)\n : textResult(result.error ?? \"Failed to kick participant.\");\n },\n );\n\n server.tool(\n \"stoops__admin__mute\",\n \"Admin: make a participant read-only (demote to observer).\",\n {\n room: z.string().describe(\"Room name\"),\n participant: z.string().describe(\"Participant name to mute\"),\n },\n { readOnlyHint: false, destructiveHint: false, idempotentHint: true },\n async ({ room, participant }: { room: string; participant: string }) => {\n if (!opts.onAdminMute) return textResult(\"Admin mute not supported.\");\n const result = await opts.onAdminMute(room, participant);\n return result.success\n ? textResult(`Muted ${participant} in [${room}] (observer).`)\n : textResult(result.error ?? \"Failed to mute participant.\");\n },\n );\n\n server.tool(\n \"stoops__admin__unmute\",\n \"Admin: restore a muted participant (promote to participant).\",\n {\n room: z.string().describe(\"Room name\"),\n participant: z.string().describe(\"Participant name to unmute\"),\n },\n { readOnlyHint: false, destructiveHint: false, idempotentHint: true },\n async ({ room, participant }: { room: string; participant: string }) => {\n if (!opts.onAdminUnmute) return textResult(\"Admin unmute not supported.\");\n const result = await opts.onAdminUnmute(room, participant);\n return result.success\n ? textResult(`Unmuted ${participant} in [${room}] (participant).`)\n : textResult(result.error ?? \"Failed to unmute participant.\");\n },\n );\n }\n}\n\n/**\n * Create a runtime MCP server on a random localhost port.\n * Returns the URL for --mcp-config and a stop function.\n */\nexport async function createRuntimeMcpServer(\n opts: RuntimeMcpServerOptions,\n): Promise<RuntimeMcpServer> {\n const { McpServer } = await import(\"@modelcontextprotocol/sdk/server/mcp.js\");\n const { StreamableHTTPServerTransport } = await import(\n \"@modelcontextprotocol/sdk/server/streamableHttp.js\"\n );\n\n const httpServer = createServer(async (req, res) => {\n if (req.url !== \"/mcp\") {\n res.writeHead(404).end();\n return;\n }\n\n // Fresh McpServer per request (McpServer only allows one active transport)\n const reqServer = new McpServer({ name: \"stoops_runtime\", version: \"1.0.0\" });\n registerTools(reqServer, opts);\n\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n });\n\n await reqServer.connect(transport);\n\n let body: unknown;\n if (req.method === \"POST\") {\n const chunks: Buffer[] = [];\n for await (const chunk of req) chunks.push(chunk as Buffer);\n try { body = JSON.parse(Buffer.concat(chunks).toString()); } catch { body = undefined; }\n }\n\n await transport.handleRequest(req, res, body);\n });\n\n const port = await new Promise<number>((resolve, reject) => {\n httpServer.listen(0, \"127.0.0.1\", () => {\n const addr = httpServer.address();\n if (addr && typeof addr === \"object\") resolve(addr.port);\n else reject(new Error(\"Could not determine server port\"));\n });\n httpServer.once(\"error\", reject);\n });\n\n const url = `http://127.0.0.1:${port}/mcp`;\n\n let stopPromise: Promise<void> | null = null;\n const stop = () => {\n if (!stopPromise) {\n stopPromise = new Promise<void>((resolve, reject) =>\n httpServer.close((err) => (err ? reject(err) : resolve())),\n );\n }\n return stopPromise;\n };\n\n return { url, stop };\n}\n","/** Pure tool handler logic */\n\nimport type { Message } from \"../core/types.js\";\nimport type { RoomConnection, RoomResolver, ToolHandlerOptions } from \"./types.js\";\nimport { messageRef, formatTimestamp } from \"./prompts.js\";\n\n/** Tool result shape consumed by both backends. */\nexport interface ToolResult {\n content: Array<{ type: \"text\"; text: string }>;\n}\n\n/** Helper: resolve room name or return an error tool result. */\nexport function resolveOrError(resolver: RoomResolver, roomName: string):\n | { error: true; result: ToolResult }\n | { error: false; conn: RoomConnection } {\n const conn = resolver.resolve(roomName);\n if (!conn) {\n return {\n error: true as const,\n result: { content: [{ type: \"text\" as const, text: `Unknown room \"${roomName}\".` }] },\n };\n }\n return { error: false as const, conn };\n}\n\nexport function textResult(text: string): ToolResult {\n return { content: [{ type: \"text\" as const, text }] };\n}\n\n/** Format a single message as a transcript line (shared by catch_up and search tools). */\nexport async function formatMsgLine(\n msg: Message,\n conn: RoomConnection,\n mkRef: (id: string) => string,\n): Promise<string> {\n const ts = formatTimestamp(new Date(msg.timestamp));\n const ref = mkRef(msg.id);\n const imageNote = msg.image_url ? ` [[img:${msg.image_url}]]` : \"\";\n let line = `[${ts}] ${ref} ${msg.sender_name}: ${msg.content}${imageNote}`;\n if (msg.reply_to_id) {\n const target = await conn.dataSource.getMessage(msg.reply_to_id);\n if (target) {\n const targetRef = mkRef(target.id);\n line = `[${ts}] ${ref} ${msg.sender_name} (→ ${targetRef} ${target.sender_name}): ${msg.content}${imageNote}`;\n }\n }\n return line;\n}\n\n/**\n * Build catch-up event lines for a room — shared by the catch_up MCP tool and\n * the runtime's full_catch_up injection. Returns formatted transcript lines for\n * all unseen events (oldest first), marking them as seen.\n */\nexport async function buildCatchUpLines(\n conn: RoomConnection,\n options: Pick<ToolHandlerOptions, \"isEventSeen\" | \"markEventsSeen\" | \"assignRef\">,\n): Promise<string[]> {\n const result = await conn.dataSource.getEvents(null, 50, null);\n const chronological = [...result.items].reverse();\n\n let startIdx = chronological.length;\n for (let i = 0; i < chronological.length; i++) {\n if (!options.isEventSeen?.(chronological[i].id)) {\n startIdx = i;\n break;\n }\n }\n const unseen = chronological.slice(startIdx);\n\n const lines: string[] = [];\n const seenIds: string[] = [];\n const mkRef = (id: string) => `#${options.assignRef?.(id) ?? messageRef(id)}`;\n\n for (const event of unseen) {\n seenIds.push(event.id);\n const ts = formatTimestamp(new Date(event.timestamp));\n\n if (event.type === \"MessageSent\") {\n lines.push(await formatMsgLine(event.message, conn, (id) => mkRef(id)));\n } else if (event.type === \"ParticipantJoined\") {\n const participant = conn.dataSource.listParticipants().find((p) => p.id === event.participant_id);\n const name = participant?.name ?? event.participant_id;\n lines.push(`[${ts}] + ${name} joined`);\n } else if (event.type === \"ParticipantLeft\") {\n const name = event.participant?.name ?? event.participant_id;\n lines.push(`[${ts}] - ${name} left`);\n } else if (event.type === \"ReactionAdded\") {\n const participant = conn.dataSource.listParticipants().find((p) => p.id === event.participant_id);\n const name = participant?.name ?? event.participant_id;\n const target = await conn.dataSource.getMessage(event.message_id);\n const targetRef = target ? ` to ${mkRef(target.id)}` : \"\";\n lines.push(`[${ts}] ${name} reacted ${event.emoji}${targetRef}`);\n }\n // Other event types (ToolUse, Activity, Mentioned, etc.) are skipped\n }\n\n if (seenIds.length > 0) options.markEventsSeen?.(seenIds);\n return lines;\n}\n\n// ── Tool handler functions ────────────────────────────────────────────────────\n\nexport async function handleCatchUp(\n resolver: RoomResolver,\n args: { room: string },\n options: ToolHandlerOptions,\n): Promise<ToolResult> {\n const r = resolveOrError(resolver, args.room);\n if (r.error) return r.result;\n const lines = await buildCatchUpLines(r.conn, options);\n const out: string[] = [`Catching up on [${args.room}]:`];\n if (lines.length > 0) {\n out.push(\"\", ...lines);\n } else {\n out.push(\"\", \"(nothing new)\");\n }\n return { content: [{ type: \"text\" as const, text: out.join(\"\\n\") }] };\n}\n\nexport async function handleSearchByText(\n resolver: RoomResolver,\n args: { room: string; query: string; count?: number; cursor?: string },\n options: ToolHandlerOptions,\n): Promise<ToolResult> {\n const r = resolveOrError(resolver, args.room);\n if (r.error) return r.result;\n const { conn } = r;\n const count = args.count ?? 3;\n const mkRef = (id: string) => `#${options.assignRef?.(id) ?? messageRef(id)}`;\n\n // Get up to 50 matches to know the total; slice to count for display\n const searchResult = await conn.dataSource.searchMessages(args.query, 50, args.cursor ?? null);\n const totalVisible = searchResult.items.length;\n\n if (totalVisible === 0) {\n return textResult(`No messages found in [${args.room}] matching \"${args.query}\".`);\n }\n\n const toShow = searchResult.items.slice(0, count); // newest-first\n\n // Load recent messages for context (before/after lookup)\n const recentResult = await conn.dataSource.getMessages(100, null);\n const recentChron = [...recentResult.items].reverse(); // chronological\n const msgIdxMap = new Map<string, number>();\n recentChron.forEach((m, i) => msgIdxMap.set(m.id, i));\n\n // Process clusters oldest-first (reverse the newest-first results)\n const clusters: string[][] = [];\n let newerCount: number | null = null;\n\n for (const match of [...toShow].reverse()) {\n const cluster: string[] = [];\n const matchIdx = msgIdxMap.get(match.id);\n\n if (matchIdx !== undefined) {\n // Before context\n if (matchIdx > 0) {\n cluster.push(await formatMsgLine(recentChron[matchIdx - 1], conn, mkRef));\n }\n // Match line\n cluster.push((await formatMsgLine(match, conn, mkRef)) + \" ←\");\n // After context\n if (matchIdx < recentChron.length - 1) {\n cluster.push(await formatMsgLine(recentChron[matchIdx + 1], conn, mkRef));\n }\n // Track newer count based on the newest match (first in toShow = newest)\n if (newerCount === null && match.id === toShow[0].id) {\n newerCount = recentChron.length - matchIdx - 1;\n }\n } else {\n // Match is older than the context window — show match only\n cluster.push((await formatMsgLine(match, conn, mkRef)) + \" ←\");\n }\n\n clusters.push(cluster);\n }\n\n // Build output\n const shownCount = toShow.length;\n const totalNote = searchResult.has_more\n ? `showing ${shownCount} of 50+`\n : `${shownCount} of ${totalVisible}`;\n const out: string[] = [`Search results in [${args.room}] for \"${args.query}\" (${totalNote} matches):`, \"\"];\n\n for (let i = 0; i < clusters.length; i++) {\n for (const line of clusters[i]) {\n out.push(` ${line}`);\n }\n if (i < clusters.length - 1) out.push(\"\");\n }\n\n if (newerCount !== null && newerCount > 0) {\n out.push(\"\", `${newerCount} newer message${newerCount === 1 ? \"\" : \"s\"} in this room.`);\n }\n if (searchResult.has_more && searchResult.next_cursor) {\n out.push(`(cursor: \"${searchResult.next_cursor}\" for next ${count} matches)`);\n }\n\n return textResult(out.join(\"\\n\"));\n}\n\nexport async function handleSearchByMessage(\n resolver: RoomResolver,\n args: { room: string; ref: string; direction?: \"before\" | \"after\"; count?: number },\n options: ToolHandlerOptions,\n): Promise<ToolResult> {\n const r = resolveOrError(resolver, args.room);\n if (r.error) return r.result;\n const { conn } = r;\n const direction = args.direction ?? \"before\";\n const count = args.count ?? 10;\n const mkRef = (id: string) => `#${options.assignRef?.(id) ?? messageRef(id)}`;\n\n // Resolve ref to message ID\n const rawRef = args.ref.startsWith(\"#\") ? args.ref.slice(1) : args.ref;\n const anchorId = options.resolveRef?.(rawRef) ?? rawRef;\n const anchor = await conn.dataSource.getMessage(anchorId);\n if (!anchor) return textResult(`Message ${args.ref} not found.`);\n\n // Load recent messages for \"after\" direction and \"newer count\"\n const recentResult = await conn.dataSource.getMessages(100, null);\n const recentChron = [...recentResult.items].reverse(); // chronological\n const anchorIdx = recentChron.findIndex((m) => m.id === anchor.id);\n\n let displayMessages: Message[];\n let newerCount: number;\n\n if (direction === \"before\") {\n // Get count messages before anchor from storage (works for any age)\n const beforeResult = await conn.dataSource.getMessages(count, anchor.id);\n const beforeMessages = [...beforeResult.items].reverse(); // chronological\n displayMessages = [...beforeMessages, anchor];\n newerCount = anchorIdx >= 0 ? recentChron.length - anchorIdx - 1 : 100; // 100+ if too old\n } else {\n // \"after\" — use recent window\n if (anchorIdx >= 0) {\n const afterMessages = recentChron.slice(anchorIdx + 1, anchorIdx + 1 + count);\n displayMessages = [anchor, ...afterMessages];\n newerCount = recentChron.length - anchorIdx - 1 - afterMessages.length;\n } else {\n // Anchor not in recent 100 — can't scroll forward, too many newer messages\n displayMessages = [anchor];\n newerCount = 100;\n }\n }\n\n const anchorRef = mkRef(anchor.id);\n const lines: string[] = [`Context in [${args.room}] around ${anchorRef}:`, \"\"];\n\n for (const msg of displayMessages) {\n const line = await formatMsgLine(msg, conn, mkRef);\n const isAnchor = msg.id === anchor.id;\n lines.push(` ${line}${isAnchor ? \" ←\" : \"\"}`);\n }\n\n if (newerCount > 0) {\n const countLabel = newerCount >= 100 ? \"100+\" : String(newerCount);\n lines.push(\"\", `${countLabel} newer message${newerCount === 1 ? \"\" : \"s\"} in this room.`);\n }\n\n return textResult(lines.join(\"\\n\"));\n}\n\nexport async function handleSendMessage(\n resolver: RoomResolver,\n args: {\n room: string;\n content: string;\n reply_to_id?: string;\n image_url?: string;\n image_mime_type?: string;\n image_size_bytes?: number;\n },\n options: ToolHandlerOptions,\n): Promise<ToolResult> {\n const r = resolveOrError(resolver, args.room);\n if (r.error) return r.result;\n\n const image = args.image_url\n ? {\n url: args.image_url,\n mimeType: args.image_mime_type ?? \"image/*\",\n sizeBytes: args.image_size_bytes ?? 0,\n }\n : null;\n\n // Resolve ref to full UUID — accepts both \"#3847\" and \"3847\"\n let replyToId = args.reply_to_id;\n if (replyToId) {\n const rawRef = replyToId.startsWith(\"#\") ? replyToId.slice(1) : replyToId;\n replyToId = options.resolveRef?.(rawRef) ?? replyToId;\n }\n const message = await r.conn.dataSource.sendMessage(args.content, replyToId, image);\n\n const ref = options.assignRef?.(message.id) ?? messageRef(message.id);\n return textResult(`Message sent #${ref}.`);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/events.ts"],"sourcesContent":["/**\n * Typed events for rooms — a discriminated union on the `type` field.\n *\n * Every event has:\n * id — UUID assigned at creation\n * type — the discriminant (e.g. \"MessageSent\", \"ParticipantJoined\")\n * category — the EventCategory for subscription filtering\n * room_id — the room this event belongs to\n * participant_id — the participant who caused the event\n * timestamp — UTC creation time\n *\n * Use `createEvent()` to build events — it fills in `id` and `timestamp`.\n * Use `EVENT_ROLE` to classify events by their semantic role.\n */\n\nimport { v4 as uuidv4 } from \"uuid\";\nimport type { Message, Participant } from \"./types.js\";\n\n// ── Base ─────────────────────────────────────────────────────────────────────\n\ninterface BaseRoomEvent {\n /** UUID assigned at creation. */\n id: string;\n}\n\n// ── MESSAGE category ─────────────────────────────────────────────────────────\n\n/** Someone sent a message. `message` contains the full content including any image. */\nexport interface MessageSentEvent extends BaseRoomEvent {\n type: \"MessageSent\";\n category: \"MESSAGE\";\n room_id: string;\n participant_id: string;\n timestamp: Date;\n message: Message;\n}\n\n/** A message's text content was changed. Does not update image attachments. */\nexport interface MessageEditedEvent extends BaseRoomEvent {\n type: \"MessageEdited\";\n category: \"MESSAGE\";\n room_id: string;\n participant_id: string;\n timestamp: Date;\n message_id: string;\n new_content: string;\n old_content: string;\n}\n\n/** A message was removed. The message ID is preserved for reference but content is gone. */\nexport interface MessageDeletedEvent extends BaseRoomEvent {\n type: \"MessageDeleted\";\n category: \"MESSAGE\";\n room_id: string;\n participant_id: string;\n timestamp: Date;\n message_id: string;\n}\n\n/** A participant added an emoji reaction to a message. */\nexport interface ReactionAddedEvent extends BaseRoomEvent {\n type: \"ReactionAdded\";\n category: \"MESSAGE\";\n room_id: string;\n participant_id: string;\n timestamp: Date;\n message_id: string;\n emoji: string;\n}\n\n/** A participant removed their emoji reaction from a message. */\nexport interface ReactionRemovedEvent extends BaseRoomEvent {\n type: \"ReactionRemoved\";\n category: \"MESSAGE\";\n room_id: string;\n participant_id: string;\n timestamp: Date;\n message_id: string;\n emoji: string;\n}\n\n// ── PRESENCE category ─────────────────────────────────────────────────────────\n\n/**\n * A participant connected to the room.\n *\n * Not emitted for silent connects (`Room.connect(..., silent: true)`).\n * Agents connect silently to avoid polluting the chat with join noise.\n */\nexport interface ParticipantJoinedEvent extends BaseRoomEvent {\n type: \"ParticipantJoined\";\n category: \"PRESENCE\";\n room_id: string;\n participant_id: string;\n timestamp: Date;\n /** Full participant snapshot at join time. */\n participant: Participant;\n}\n\n/**\n * A participant disconnected from the room.\n *\n * Not emitted for silent disconnects (`channel.disconnect(true)`).\n */\nexport interface ParticipantLeftEvent extends BaseRoomEvent {\n type: \"ParticipantLeft\";\n category: \"PRESENCE\";\n room_id: string;\n participant_id: string;\n timestamp: Date;\n /** Full participant snapshot at leave time. */\n participant: Participant;\n}\n\n/** A participant's presence status changed (e.g. \"online\" → \"away\"). */\nexport interface StatusChangedEvent extends BaseRoomEvent {\n type: \"StatusChanged\";\n category: \"PRESENCE\";\n room_id: string;\n participant_id: string;\n timestamp: Date;\n status: \"online\" | \"offline\" | \"away\";\n}\n\n// ── ACTIVITY category ─────────────────────────────────────────────────────────\n\n\n/**\n * An agent made an MCP tool call.\n *\n * Emitted twice per tool call: once with `status: \"started\"` (before the call)\n * and once with `status: \"completed\"` (after). Useful for showing live tool\n * progress in a UI.\n */\nexport interface ToolUseEvent extends BaseRoomEvent {\n type: \"ToolUse\";\n category: \"ACTIVITY\";\n room_id: string;\n participant_id: string;\n timestamp: Date;\n tool_name: string;\n /** \"started\" before the call, \"completed\" after. */\n status: \"started\" | \"completed\";\n}\n\n/**\n * A generic activity event for platform-specific actions.\n *\n * Used when no specific event type fits. The `action` field identifies the\n * action (e.g. `\"mode_changed\"`), and `detail` carries structured metadata.\n *\n * Current usages:\n * - action: \"mode_changed\", detail: { mode: EngagementMode }\n * Emitted when an agent's engagement mode changes for a room.\n */\nexport interface ActivityEvent extends BaseRoomEvent {\n type: \"Activity\";\n category: \"ACTIVITY\";\n room_id: string;\n participant_id: string;\n timestamp: Date;\n action: string;\n detail: Record<string, unknown> | null;\n}\n\n/**\n * An agent's context window was compacted.\n *\n * Fired when the LLM backend summarizes and compresses the conversation history\n * to free up context space. After compaction, the agent re-reads all rooms via\n * `catch_up` to rebuild its working context.\n */\nexport interface ContextCompactedEvent extends BaseRoomEvent {\n type: \"ContextCompacted\";\n category: \"ACTIVITY\";\n room_id: string;\n participant_id: string;\n timestamp: Date;\n /** Full participant snapshot (for display name). */\n participant: Participant;\n}\n\n// ── MENTION category ─────────────────────────────────────────────────────────\n\n/**\n * A participant was @mentioned in a message.\n *\n * Delivered only to the mentioned participant's channel — not broadcast to the\n * room. The `participant_id` is the **recipient** (the person mentioned), not\n * the sender. The sender is in `message.sender_id`.\n *\n * @mention detection is case-insensitive and matches on both `identifier`\n * (e.g. `@my-agent`) and display `name` (e.g. `@Alice`).\n *\n * Agents in standby modes wake up on this event type only.\n */\nexport interface MentionedEvent extends BaseRoomEvent {\n type: \"Mentioned\";\n category: \"MENTION\";\n room_id: string;\n /** The mentioned participant (the recipient of the @mention). */\n participant_id: string;\n timestamp: Date;\n /** The full message that contained the @mention. */\n message: Message;\n}\n\n// ── Union ─────────────────────────────────────────────────────────────────────\n\nexport type RoomEvent =\n | MessageSentEvent\n | MessageEditedEvent\n | MessageDeletedEvent\n | ReactionAddedEvent\n | ReactionRemovedEvent\n | ParticipantJoinedEvent\n | ParticipantLeftEvent\n | StatusChangedEvent\n | ToolUseEvent\n | ActivityEvent\n | MentionedEvent\n | ContextCompactedEvent;\n\n// ── Event roles ───────────────────────────────────────────────────────────────\n\n/**\n * Semantic role of an event, used by the engagement system to decide how to\n * handle it relative to an agent.\n *\n * - \"message\" — a direct chat message; may trigger LLM evaluation\n * - \"mention\" — an @mention directed at a specific participant\n * - \"ambient\" — background activity (joins, leaves, reactions) — buffered as\n * context but doesn't trigger evaluation on its own\n * - \"internal\" — platform bookkeeping (edits, deletes, status changes, agent\n * activity) — always dropped by the engagement system\n */\nexport type EventRole = \"message\" | \"mention\" | \"ambient\" | \"internal\";\n\n/** Maps each event type to its semantic role for the engagement system. */\nexport const EVENT_ROLE: Record<RoomEvent[\"type\"], EventRole> = {\n MessageSent: \"message\",\n Mentioned: \"mention\",\n ParticipantJoined: \"ambient\",\n ParticipantLeft: \"ambient\",\n ReactionAdded: \"ambient\",\n ContextCompacted: \"ambient\",\n MessageEdited: \"internal\",\n MessageDeleted: \"internal\",\n ReactionRemoved: \"internal\",\n StatusChanged: \"internal\",\n ToolUse: \"internal\",\n Activity: \"internal\",\n};\n\n// ── Factory ───────────────────────────────────────────────────────────────────\n\ntype EventData<T extends RoomEvent> = Omit<T, \"id\" | \"timestamp\"> & {\n id?: string;\n timestamp?: Date;\n};\n\n/**\n * Create a typed room event, filling in `id` (UUID) and `timestamp` (now).\n *\n * @example\n * const event = createEvent<MessageSentEvent>({\n * type: \"MessageSent\",\n * category: \"MESSAGE\",\n * room_id: \"room-1\",\n * participant_id: \"user-1\",\n * message: { ... },\n * });\n */\nexport function createEvent<T extends RoomEvent>(data: EventData<T>): T {\n return { id: uuidv4(), timestamp: new Date(), ...data } as T;\n}\n"],"mappings":";AAeA,SAAS,MAAM,cAAc;AAgOtB,IAAM,aAAmD;AAAA,EAC9D,aAAkB;AAAA,EAClB,WAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,iBAAkB;AAAA,EAClB,eAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,eAAkB;AAAA,EAClB,gBAAkB;AAAA,EAClB,iBAAkB;AAAA,EAClB,eAAkB;AAAA,EAClB,SAAkB;AAAA,EAClB,UAAkB;AACpB;AAqBO,SAAS,YAAiC,MAAuB;AACtE,SAAO,EAAE,IAAI,OAAO,GAAG,WAAW,oBAAI,KAAK,GAAG,GAAG,KAAK;AACxD;","names":[]}
|
|
File without changes
|
|
File without changes
|