stoops 0.1.0 → 0.3.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/LICENSE +21 -0
- package/README.md +192 -4
- package/dist/agent/index.d.ts +553 -0
- package/dist/agent/index.js +40 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/chunk-JXI4CSEP.js +115 -0
- package/dist/chunk-JXI4CSEP.js.map +1 -0
- package/dist/chunk-OFYRNGD6.js +1029 -0
- package/dist/chunk-OFYRNGD6.js.map +1 -0
- package/dist/chunk-PKFZHCQF.js +51 -0
- package/dist/chunk-PKFZHCQF.js.map +1 -0
- package/dist/chunk-TGA24MC3.js +638 -0
- package/dist/chunk-TGA24MC3.js.map +1 -0
- package/dist/chunk-TN56PBF3.js +688 -0
- package/dist/chunk-TN56PBF3.js.map +1 -0
- package/dist/claude/index.d.ts +26 -0
- package/dist/claude/index.js +214 -0
- package/dist/claude/index.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +2762 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index-DGncuUqB.d.ts +674 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +21 -2
- package/dist/index.js.map +1 -0
- package/dist/langgraph/index.d.ts +30 -0
- package/dist/langgraph/index.js +249 -0
- package/dist/langgraph/index.js.map +1 -0
- package/dist/sdk-YTUDDE6G.js +11945 -0
- package/dist/sdk-YTUDDE6G.js.map +1 -0
- package/dist/types-Co2KKpkh.d.ts +201 -0
- package/package.json +69 -11
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/agent/room-data-source.ts","../src/agent/engagement.ts","../src/agent/multiplexer.ts","../src/agent/ref-map.ts","../src/agent/connection-registry.ts","../src/agent/content-buffer.ts","../src/agent/event-tracker.ts","../src/agent/event-processor.ts","../src/agent/remote-room-data-source.ts","../src/agent/sse-multiplexer.ts"],"sourcesContent":["/**\n * RoomDataSource — abstraction over room data access.\n *\n * Allows tool handlers and EventProcessor to work against either a local\n * Room object or a remote HTTP API. This is the critical enabler for\n * client-side agent runtimes that connect to remote stoop servers.\n */\n\nimport type { Room } from \"../core/room.js\";\nimport type { Channel } from \"../core/channel.js\";\nimport type { RoomEvent } from \"../core/events.js\";\nimport type { EventCategory, Message, PaginatedResult, Participant } from \"../core/types.js\";\n\n/**\n * Uniform interface for reading/writing room data.\n *\n * Implemented by:\n * - `LocalRoomDataSource` — wraps a Room + Channel for in-process access\n * - `RemoteRoomDataSource` — wraps HTTP calls to a stoop server (Phase 5)\n */\nexport interface RoomDataSource {\n readonly roomId: string;\n\n listParticipants(): Participant[];\n getMessage(id: string): Promise<Message | null>;\n searchMessages(query: string, limit?: number, cursor?: string | null): Promise<PaginatedResult<Message>>;\n getMessages(limit?: number, cursor?: string | null): Promise<PaginatedResult<Message>>;\n getEvents(category?: EventCategory | null, limit?: number, cursor?: string | null): Promise<PaginatedResult<RoomEvent>>;\n sendMessage(content: string, replyToId?: string, image?: { url: string; mimeType: string; sizeBytes: number } | null): Promise<Message>;\n emitEvent?(event: RoomEvent): Promise<void>;\n}\n\n/**\n * LocalRoomDataSource — wraps a Room + Channel for in-process access.\n *\n * Used by app-path consumers (ClaudeSession, LangGraphSession) and the\n * CLI server's EventProcessor. Transparent — all calls delegate directly\n * to the Room and Channel.\n */\nexport class LocalRoomDataSource implements RoomDataSource {\n constructor(\n private _room: Room,\n private _channel: Channel,\n ) {}\n\n get roomId(): string {\n return this._room.roomId;\n }\n\n /** Direct access to the underlying Room (for backward compat / internal use). */\n get room(): Room {\n return this._room;\n }\n\n /** Direct access to the underlying Channel (for backward compat / internal use). */\n get channel(): Channel {\n return this._channel;\n }\n\n listParticipants(): Participant[] {\n return this._room.listParticipants();\n }\n\n async getMessage(id: string): Promise<Message | null> {\n return this._room.getMessage(id);\n }\n\n async searchMessages(query: string, limit = 10, cursor: string | null = null): Promise<PaginatedResult<Message>> {\n return this._room.searchMessages(query, limit, cursor);\n }\n\n async getMessages(limit = 30, cursor: string | null = null): Promise<PaginatedResult<Message>> {\n return this._room.listMessages(limit, cursor);\n }\n\n async getEvents(category: EventCategory | null = null, limit = 50, cursor: string | null = null): Promise<PaginatedResult<RoomEvent>> {\n return this._room.listEvents(category, limit, cursor);\n }\n\n async sendMessage(content: string, replyToId?: string, image?: { url: string; mimeType: string; sizeBytes: number } | null): Promise<Message> {\n return this._channel.sendMessage(content, replyToId, image ?? undefined);\n }\n\n async emitEvent(event: RoomEvent): Promise<void> {\n await this._channel.emit(event);\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","/** EventMultiplexer — merges N channel async streams into one labeled stream. */\n\nimport type { RoomEvent } from \"../core/events.js\";\nimport type { Channel } from \"../core/channel.js\";\n\nexport interface LabeledEvent {\n roomId: string;\n roomName: string;\n event: RoomEvent;\n}\n\ninterface ChannelEntry {\n channel: Channel;\n roomName: string;\n abortController: AbortController;\n loopPromise: Promise<void>;\n}\n\n/**\n * Merges events from multiple channels into a single async iterable stream.\n * Each event is labeled with its source room's ID and name.\n *\n * Channels can be added/removed while the multiplexer is running.\n */\nexport class EventMultiplexer {\n private _queue: LabeledEvent[] = [];\n private _waiters: Array<{ resolve: (value: LabeledEvent) => void }> = [];\n private _channels = new Map<string, ChannelEntry>();\n private _closed = false;\n private _closeResolve: (() => void) | null = null;\n\n addChannel(roomId: string, roomName: string, channel: Channel): void {\n if (this._channels.has(roomId) || this._closed) return;\n const abortController = new AbortController();\n const loopPromise = this._listenLoop(roomId, roomName, channel, abortController.signal);\n this._channels.set(roomId, { channel, roomName, abortController, loopPromise });\n }\n\n removeChannel(roomId: string): void {\n const entry = this._channels.get(roomId);\n if (!entry) return;\n entry.abortController.abort();\n this._channels.delete(roomId);\n }\n\n close(): void {\n this._closed = true;\n for (const [, entry] of this._channels) {\n entry.abortController.abort();\n }\n this._channels.clear();\n // Wake any pending iterator so it returns done\n if (this._closeResolve) {\n this._closeResolve();\n this._closeResolve = null;\n }\n // Resolve all waiters with a sentinel — they'll see done: true on next call\n for (const waiter of this._waiters) {\n // Push a dummy event — the iterator will check _closed on next call\n waiter.resolve(null as unknown as LabeledEvent);\n }\n this._waiters = [];\n }\n\n private async _listenLoop(\n roomId: string,\n roomName: string,\n channel: Channel,\n signal: AbortSignal,\n ): Promise<void> {\n try {\n for await (const event of channel) {\n if (signal.aborted) break;\n this._push({ roomId, roomName, event });\n }\n } catch {\n // Channel disconnected — exit gracefully\n }\n }\n\n private _push(labeled: LabeledEvent): void {\n if (this._closed) return;\n if (this._waiters.length > 0) {\n const waiter = this._waiters.shift()!;\n waiter.resolve(labeled);\n } else {\n this._queue.push(labeled);\n }\n }\n\n [Symbol.asyncIterator](): AsyncIterator<LabeledEvent> {\n return {\n next: (): Promise<IteratorResult<LabeledEvent>> => {\n // Drain buffered events first\n if (this._queue.length > 0) {\n return Promise.resolve({ value: this._queue.shift()!, done: false });\n }\n\n if (this._closed) {\n return Promise.resolve({ value: undefined as unknown as LabeledEvent, done: true });\n }\n\n return new Promise<IteratorResult<LabeledEvent>>((resolve) => {\n this._waiters.push({\n resolve: (value) => {\n if (this._closed || value === null) {\n resolve({ value: undefined as unknown as LabeledEvent, done: true });\n } else {\n resolve({ value, done: false });\n }\n },\n });\n });\n },\n };\n }\n}\n","/**\n * RefMap — bidirectional map between short decimal refs and full message UUIDs.\n *\n * Why 4 digits: a ref like #3847 tokenizes to 1–2 tokens in most LLMs, while a\n * raw UUID (a1b2c3d4-e5f6-...) burns 8–12 tokens for no benefit. Since refs\n * appear on every message in transcripts and tool output, the savings compound.\n *\n * 10,000 unique refs per cycle is far more than an LLM context window can hold\n * (a 200k-token window fits ~2000 messages at most). The map is cleared on every\n * context compaction, so overflow is essentially impossible in practice. If it\n * does happen, the fallback to a UUID-derived hash still works — just less tidy.\n *\n * Uses a linear congruential generator (n * 6337 mod 10000) to produce\n * non-sequential refs — gcd(6337, 10000) = 1 guarantees a full cycle through\n * all 10,000 values before any collision.\n *\n * @example\n * const refs = new RefMap();\n * const ref = refs.assign(\"msg-uuid-abc\"); // \"3847\"\n * refs.assign(\"msg-uuid-abc\"); // \"3847\" (idempotent)\n * refs.resolve(\"3847\"); // \"msg-uuid-abc\"\n * refs.clear(); // reset on compaction\n */\nexport class RefMap {\n private _counter = Math.floor(Math.random() * 10000);\n private _refToId = new Map<string, string>();\n private _idToRef = new Map<string, string>();\n\n /** Assign a short ref to a message ID. Returns existing ref if already assigned. */\n assign(messageId: string): string {\n const existing = this._idToRef.get(messageId);\n if (existing) return existing;\n\n const ref = String((this._counter * 6337) % 10000).padStart(4, \"0\");\n this._counter++;\n\n if (this._refToId.has(ref)) {\n // Wrap-around (>10000 assignments without compaction — rare in practice).\n // Fall back to hex-derived ref, checking for collisions.\n const hex = messageId.replace(/-/g, \"\");\n let fallback: string | null = null;\n for (let i = 0; i <= hex.length - 4; i++) {\n const candidate = hex.slice(i, i + 4);\n if (!this._refToId.has(candidate)) {\n fallback = candidate;\n break;\n }\n }\n if (!fallback) fallback = messageId.slice(0, 8);\n this._refToId.set(fallback, messageId);\n this._idToRef.set(messageId, fallback);\n return fallback;\n }\n\n this._refToId.set(ref, messageId);\n this._idToRef.set(messageId, ref);\n return ref;\n }\n\n /** Resolve a ref back to the full message UUID. Returns undefined if unknown. */\n resolve(ref: string): string | undefined {\n return this._refToId.get(ref);\n }\n\n /** Clear all mappings and reset the counter. Called on context compaction. */\n clear(): void {\n this._refToId.clear();\n this._idToRef.clear();\n this._counter = Math.floor(Math.random() * 10000);\n }\n}\n","/** Room connection registry — manages live room connections for an agent. */\n\nimport type { RoomConnection } from \"./types.js\";\n\nexport interface InternalConnection extends RoomConnection {\n identifier?: string;\n}\n\nexport class ConnectionRegistry {\n private _connections = new Map<string, InternalConnection>();\n private _nameToId = new Map<string, string>();\n private _identifierToId = new Map<string, string>();\n private _lastMessages = new Map<string, string>();\n\n add(roomId: string, conn: InternalConnection): void {\n this._connections.set(roomId, conn);\n this._nameToId.set(conn.name, roomId);\n if (conn.identifier) this._identifierToId.set(conn.identifier, roomId);\n }\n\n remove(roomId: string): InternalConnection | undefined {\n const conn = this._connections.get(roomId);\n if (!conn) return undefined;\n this._nameToId.delete(conn.name);\n if (conn.identifier) this._identifierToId.delete(conn.identifier);\n this._connections.delete(roomId);\n this._lastMessages.delete(roomId);\n return conn;\n }\n\n get(roomId: string): InternalConnection | undefined {\n return this._connections.get(roomId);\n }\n\n has(roomId: string): boolean {\n return this._connections.has(roomId);\n }\n\n resolve(roomName: string): RoomConnection | null {\n const roomId = this._nameToId.get(roomName);\n if (roomId) return this._connections.get(roomId) ?? null;\n const idFromIdentifier = this._identifierToId.get(roomName);\n if (idFromIdentifier) return this._connections.get(idFromIdentifier) ?? null;\n return this._connections.get(roomName) ?? null;\n }\n\n listAll(getModeForRoom: (roomId: string) => string): Array<{\n name: string;\n roomId: string;\n identifier?: string;\n mode: string;\n participantCount: number;\n lastMessage?: string;\n }> {\n return [...this._connections.entries()].map(([roomId, conn]) => ({\n name: conn.name,\n roomId,\n ...(conn.identifier ? { identifier: conn.identifier } : {}),\n mode: getModeForRoom(roomId),\n participantCount: conn.dataSource.listParticipants().length,\n ...(this._lastMessages.has(roomId) ? { lastMessage: this._lastMessages.get(roomId) } : {}),\n }));\n }\n\n setLastMessage(roomId: string, text: string): void {\n this._lastMessages.set(roomId, text);\n }\n\n entries(): IterableIterator<[string, InternalConnection]> {\n return this._connections.entries();\n }\n\n get size(): number {\n return this._connections.size;\n }\n\n clear(): void {\n this._connections.clear();\n this._nameToId.clear();\n this._identifierToId.clear();\n this._lastMessages.clear();\n }\n\n values(): IterableIterator<InternalConnection> {\n return this._connections.values();\n }\n}\n","/** Per-room content buffer — accumulates events between triggers. */\n\nimport type { RoomEvent } from \"../core/events.js\";\n\nexport interface BufferedContent {\n event: RoomEvent;\n roomId: string;\n roomName: string;\n}\n\nexport class ContentBuffer {\n private _buffer = new Map<string, BufferedContent[]>();\n\n push(roomId: string, item: BufferedContent): void {\n const buf = this._buffer.get(roomId) ?? [];\n buf.push(item);\n this._buffer.set(roomId, buf);\n }\n\n flush(roomId: string): BufferedContent[] {\n const items = this._buffer.get(roomId) ?? [];\n this._buffer.delete(roomId);\n return items;\n }\n\n delete(roomId: string): void {\n this._buffer.delete(roomId);\n }\n\n clear(): void {\n this._buffer.clear();\n }\n}\n","/** Deduplication and delivery tracking for event IDs. */\n\nexport class EventTracker {\n private _processedIds = new Set<string>();\n private _deliveredIds = new Set<string>();\n\n /** Returns true if this event was already processed (and adds it if not). */\n isDuplicate(id: string): boolean {\n if (this._processedIds.has(id)) return true;\n this._processedIds.add(id);\n if (this._processedIds.size > 500) {\n const arr = [...this._processedIds];\n this._processedIds = new Set(arr.slice(arr.length >> 1));\n }\n return false;\n }\n\n isDelivered(id: string): boolean {\n return this._deliveredIds.has(id);\n }\n\n markDelivered(id: string): void {\n this._deliveredIds.add(id);\n }\n\n markManyDelivered(ids: string[]): void {\n for (const id of ids) {\n this._deliveredIds.add(id);\n this._processedIds.add(id);\n }\n }\n\n clearDelivered(): void {\n this._deliveredIds.clear();\n }\n\n clearAll(): void {\n this._processedIds.clear();\n this._deliveredIds.clear();\n }\n}\n","/**\n * EventProcessor — core event loop for stoops agents.\n *\n * Owns: engagement classification, content buffering, event formatting,\n * ref map, room connections, mode management. Does NOT own: LLM sessions,\n * MCP servers, compaction hooks, stats tracking.\n *\n * Delivery is pluggable — pass a `deliver` callback to `run()`.\n * The callback receives formatted ContentPart[] and does whatever the\n * consumer needs (Claude SDK query, LangGraph injection, tmux send-keys).\n */\n\nimport { EventCategory, type Participant, type ParticipantType } from \"../core/types.js\";\nimport {\n createEvent,\n type RoomEvent,\n type ActivityEvent,\n type MentionedEvent,\n type ToolUseEvent,\n} from \"../core/events.js\";\nimport type { Room } from \"../core/room.js\";\nimport { type EngagementMode, type EventDisposition, type EngagementStrategy, StoopsEngagement } from \"./engagement.js\";\nimport type { ContentPart, RoomConnection, RoomResolver } from \"./types.js\";\nimport { LocalRoomDataSource } from \"./room-data-source.js\";\nimport type { RoomDataSource } from \"./room-data-source.js\";\nimport { formatEvent } from \"./prompts.js\";\nimport { buildCatchUpLines } from \"./tool-handlers.js\";\nimport { EventMultiplexer, type LabeledEvent } from \"./multiplexer.js\";\nimport { RefMap } from \"./ref-map.js\";\nimport { ConnectionRegistry, type InternalConnection } from \"./connection-registry.js\";\nimport { ContentBuffer } from \"./content-buffer.js\";\nimport { EventTracker } from \"./event-tracker.js\";\n\n// ── Options ─────────────────────────────────────────────────────────────────────\n\nexport interface EventProcessorOptions {\n /** Engagement mode when no per-room override is set. */\n defaultMode?: EngagementMode;\n /** Custom engagement strategy. Defaults to StoopsEngagement. */\n engagement?: EngagementStrategy;\n /** The agent owner's participant ID (for \"me\" / \"standby-me\" modes). */\n personParticipantId?: string;\n /** The agent's own stable identifier slug (e.g. \"my-agent\"). */\n selfIdentifier?: string;\n /** Called when engagement mode changes for a room. */\n onModeChange?: (roomId: string, roomName: string, mode: EngagementMode) => void;\n /** Called before each delivery. Return false to skip. */\n preQuery?: () => Promise<boolean>;\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────────────\n\nfunction mergeParts(arrays: ContentPart[][]): ContentPart[] {\n const result: ContentPart[] = [];\n for (let i = 0; i < arrays.length; i++) {\n if (i > 0) result.push({ type: \"text\", text: \"\\n\" });\n result.push(...arrays[i]);\n }\n return result;\n}\n\n// ── EventProcessor ──────────────────────────────────────────────────────────────\n\nexport class EventProcessor implements RoomResolver {\n private _participantId: string;\n private _participantName: string;\n private _options: EventProcessorOptions;\n private _engagement: EngagementStrategy;\n\n private _deliver: ((parts: ContentPart[]) => Promise<void>) | null = null;\n private _multiplexer = new EventMultiplexer();\n private _registry = new ConnectionRegistry();\n private _buffer = new ContentBuffer();\n private _tracker = new EventTracker();\n private _processing = false;\n private _eventQueue: LabeledEvent[] = [];\n private _stopped = false;\n private _currentContextRoomId: string | null = null;\n private _log: RoomEvent[] = [];\n private _refMap = new RefMap();\n private _injectBuffer: ContentPart[][] = [];\n /** Per-room participant IDs (for multi-server CLI agents where each server assigns a different ID). */\n private _roomSelfIds = new Map<string, string>();\n\n constructor(\n participantId: string,\n participantName: string,\n options: EventProcessorOptions = {},\n ) {\n this._participantId = participantId;\n this._participantName = participantName;\n this._options = options;\n this._engagement = options.engagement\n ?? new StoopsEngagement(options.defaultMode ?? \"everyone\", options.personParticipantId);\n }\n\n // ── Public accessors ────────────────────────────────────────────────────────\n\n get participantId(): string { return this._participantId; }\n set participantId(id: string) { this._participantId = id; }\n get participantName(): string { return this._participantName; }\n get currentContextRoomId(): string | null { return this._currentContextRoomId; }\n\n /** Set a room-specific participant ID (for multi-server CLI agents). */\n setRoomParticipantId(roomId: string, participantId: string): void {\n this._roomSelfIds.set(roomId, participantId);\n }\n\n /** Get the effective selfId for a room — room-specific if set, otherwise the global one. */\n getSelfIdForRoom(roomId: string): string {\n return this._roomSelfIds.get(roomId) ?? this._participantId;\n }\n\n // ── Ref map (consumer calls these for MCP tools) ────────────────────────────\n\n assignRef(messageId: string): string {\n return this._refMap.assign(messageId);\n }\n\n resolveRef(ref: string): string | undefined {\n return this._refMap.resolve(ref);\n }\n\n // ── Seen-event cache (consumer calls these for catch_up MCP tool) ───────────\n\n isEventSeen(eventId: string): boolean {\n return this._tracker.isDelivered(eventId);\n }\n\n markEventsSeen(eventIds: string[]): void {\n this._tracker.markManyDelivered(eventIds);\n }\n\n // ── Inject buffer (LangGraph mid-loop event injection) ──────────────────────\n\n drainInjectBuffer(): ContentPart[][] | null {\n if (this._injectBuffer.length === 0) return null;\n const drained = this._injectBuffer;\n this._injectBuffer = [];\n return drained;\n }\n\n // ── Consumer hooks (called by consumer during/after delivery) ────────────────\n\n /**\n * Called by the consumer when context was compacted.\n * Clears seen-event cache and ref map so catch_up returns full history.\n */\n onContextCompacted(): void {\n this._tracker.clearDelivered();\n this._refMap.clear();\n }\n\n /**\n * Called by the consumer when a tool call starts or completes.\n * Routes ToolUseEvent to the room that triggered the current evaluation.\n */\n emitToolUse(toolName: string, status: \"started\" | \"completed\"): void {\n if (this._currentContextRoomId) {\n const conn = this._registry.get(this._currentContextRoomId);\n if (conn) {\n const event = createEvent<ToolUseEvent>({\n type: \"ToolUse\",\n category: \"ACTIVITY\",\n room_id: this._currentContextRoomId,\n participant_id: this._participantId,\n tool_name: toolName,\n status,\n });\n const emitter = conn.dataSource.emitEvent\n ? (e: RoomEvent) => conn.dataSource.emitEvent!(e)\n : conn.channel\n ? (e: RoomEvent) => conn.channel!.emit(e)\n : null;\n if (!emitter) return;\n emitter(event).catch(() => {});\n }\n }\n }\n\n // ── RoomResolver implementation ─────────────────────────────────────────────\n\n resolve(roomName: string): RoomConnection | null {\n return this._registry.resolve(roomName);\n }\n\n listAll(): Array<{\n name: string;\n roomId: string;\n identifier?: string;\n mode: string;\n participantCount: number;\n lastMessage?: string;\n }> {\n return this._registry.listAll((roomId) => this.getModeForRoom(roomId));\n }\n\n // ── Room connection management ──────────────────────────────────────────────\n\n async connectRoom(\n room: Room,\n roomName: string,\n mode?: EngagementMode,\n identifier?: string,\n ): Promise<void> {\n if (this._registry.has(room.roomId)) return;\n\n const channel = await room.connect(this._participantId, this._participantName, {\n type: \"agent\",\n identifier: this._options.selfIdentifier,\n subscribe: new Set([\n EventCategory.MESSAGE,\n EventCategory.PRESENCE,\n EventCategory.ACTIVITY,\n EventCategory.MENTION,\n ]),\n silent: true,\n });\n\n const dataSource = new LocalRoomDataSource(room, channel);\n const conn: InternalConnection = { dataSource, room, channel, name: roomName, identifier };\n this._registry.add(room.roomId, conn);\n if (mode) this._engagement.setMode?.(room.roomId, mode);\n\n // Emit initial mode\n const initialMode = this.getModeForRoom(room.roomId);\n channel.emit(createEvent<ActivityEvent>({\n type: \"Activity\",\n category: \"ACTIVITY\",\n room_id: room.roomId,\n participant_id: this._participantId,\n action: \"mode_changed\",\n detail: { mode: initialMode },\n })).catch(() => {});\n\n this._multiplexer.addChannel(room.roomId, roomName, channel);\n }\n\n /**\n * Connect a remote room via a RoomDataSource (no local Room/Channel).\n *\n * Used by the client-side agent runtime to register rooms that are\n * accessed over HTTP. Events come from an external source (SSE multiplexer)\n * passed to run(), not from the internal EventMultiplexer.\n */\n connectRemoteRoom(\n dataSource: RoomDataSource,\n roomName: string,\n mode?: EngagementMode,\n identifier?: string,\n ): void {\n if (this._registry.has(dataSource.roomId)) return;\n\n const conn: InternalConnection = {\n dataSource,\n name: roomName,\n identifier,\n };\n this._registry.add(dataSource.roomId, conn);\n if (mode) this._engagement.setMode?.(dataSource.roomId, mode);\n }\n\n /** Disconnect a remote room (by room ID). */\n disconnectRemoteRoom(roomId: string): void {\n if (!this._registry.has(roomId)) return;\n this._registry.remove(roomId);\n this._engagement.onRoomDisconnected?.(roomId);\n this._buffer.delete(roomId);\n this._roomSelfIds.delete(roomId);\n }\n\n async disconnectRoom(roomId: string): Promise<void> {\n const conn = this._registry.get(roomId);\n if (!conn) return;\n\n this._multiplexer.removeChannel(roomId);\n await conn.channel?.disconnect(true);\n this._registry.remove(roomId);\n this._engagement.onRoomDisconnected?.(roomId);\n this._buffer.delete(roomId);\n this._roomSelfIds.delete(roomId);\n }\n\n // ── Mode management ─────────────────────────────────────────────────────────\n\n getModeForRoom(roomId: string): EngagementMode {\n return this._engagement.getMode?.(roomId) ?? \"everyone\";\n }\n\n setModeForRoom(roomId: string, mode: EngagementMode, notifyAgent = true): void {\n this._engagement.setMode?.(roomId, mode);\n const conn = this._registry.get(roomId);\n if (conn) {\n conn.channel?.emit(createEvent<ActivityEvent>({\n type: \"Activity\",\n category: \"ACTIVITY\",\n room_id: roomId,\n participant_id: this._participantId,\n action: \"mode_changed\",\n detail: { mode },\n }))?.catch(() => {});\n this._options.onModeChange?.(roomId, conn.name, mode);\n }\n }\n\n // ── Log ─────────────────────────────────────────────────────────────────────\n\n getLog(): RoomEvent[] {\n return this._log;\n }\n\n // ── Main event loop ─────────────────────────────────────────────────────────\n\n /**\n * Start the event loop.\n *\n * @param deliver — callback that receives formatted content and delivers\n * it to the agent. This is the consumer's responsibility. The function\n * should block until delivery is complete (e.g., LLM evaluation finished).\n * @param eventSource — optional external event source (e.g. SseMultiplexer).\n * If provided, events are consumed from this instead of the internal\n * EventMultiplexer. Used by the client-side agent runtime.\n * @param initialParts — optional content to deliver before entering the\n * event loop. Used by the runtime to deliver auto-join confirmation.\n */\n async run(\n deliver: (parts: ContentPart[]) => Promise<void>,\n eventSource?: AsyncIterable<LabeledEvent>,\n initialParts?: ContentPart[],\n ): Promise<void> {\n this._deliver = deliver;\n\n // Deliver initial content if provided (e.g. auto-join confirmation)\n if (initialParts && initialParts.length > 0) {\n await this._processRaw(\n initialParts,\n this._registry.values().next().value?.dataSource.roomId ?? null,\n );\n }\n\n // Main event loop — use external source if provided, otherwise internal multiplexer\n const source = eventSource ?? this._multiplexer;\n for await (const labeled of source) {\n if (this._stopped) break;\n await this._handleLabeledEvent(labeled);\n }\n }\n\n async stop(): Promise<void> {\n this._stopped = true;\n this._multiplexer.close();\n await Promise.allSettled(\n [...this._registry.values()]\n .filter((conn) => conn.channel)\n .map((conn) => conn.channel!.disconnect(true)),\n );\n this._registry.clear();\n this._buffer.clear();\n this._tracker.clearAll();\n this._refMap.clear();\n this._roomSelfIds.clear();\n this._deliver = null;\n }\n\n // ── Full catch-up (kept for app-path consumers) ────────────────────────────\n\n async buildFullCatchUp(): Promise<ContentPart[]> {\n return this._buildFullCatchUp();\n }\n\n private async _buildFullCatchUp(): Promise<ContentPart[]> {\n const sections: string[] = [\"[Session context — loaded automatically]\"];\n\n const nameCounts = new Map<string, number>();\n for (const [, c] of this._registry.entries()) nameCounts.set(c.name, (nameCounts.get(c.name) ?? 0) + 1);\n\n for (const [roomId, conn] of this._registry.entries()) {\n const mode = this.getModeForRoom(roomId);\n const isDuplicate = (nameCounts.get(conn.name) ?? 0) > 1;\n const ref = isDuplicate ? roomId : (conn.identifier ?? roomId);\n\n sections.push(`\\n${conn.name} [${ref}] — ${mode}`);\n\n if (mode.startsWith(\"standby-\")) {\n sections.push(\" (standby — @mentions only)\");\n } else {\n const participants = conn.dataSource.listParticipants()\n .filter((p) => p.id !== this._participantId);\n if (participants.length > 0) {\n const pList = participants\n .map((p) => `${p.type} ${p.name}`)\n .join(\", \");\n sections.push(`Participants: ${pList}`);\n }\n\n const lines = await buildCatchUpLines(conn, {\n isEventSeen: (id) => this._tracker.isDelivered(id),\n markEventsSeen: (ids) => { this._tracker.markManyDelivered(ids); },\n assignRef: (id) => this.assignRef(id),\n });\n if (lines.length > 0) {\n for (const line of lines) sections.push(` ${line}`);\n } else {\n sections.push(\" (nothing new)\");\n }\n }\n }\n\n sections.push(\n \"\\n────────────────────────────────────────────────\",\n \"Continue immediately if you see fit, or explore further in any active room.\",\n );\n\n return [{ type: \"text\", text: sections.join(\"\\n\") }];\n }\n\n // ── Event handling ──────────────────────────────────────────────────────────\n\n private async _handleLabeledEvent(labeled: LabeledEvent): Promise<void> {\n if (this._tracker.isDuplicate(labeled.event.id)) return;\n\n const { roomId, event } = labeled;\n\n const conn = this._registry.get(roomId);\n const senderLookupId =\n event.type === \"Mentioned\"\n ? (event as MentionedEvent).message.sender_id\n : event.participant_id;\n const sender = conn?.dataSource.listParticipants().find((p) => p.id === senderLookupId);\n const senderType: ParticipantType = sender?.type ?? \"human\";\n\n const selfId = this.getSelfIdForRoom(roomId);\n const disposition: EventDisposition = this._engagement.classify(event, roomId, selfId, senderType, senderLookupId);\n\n if (disposition === \"drop\") return;\n\n this._tracker.markDelivered(event.id);\n\n if (disposition === \"content\") {\n this._buffer.push(roomId, { event, roomId, roomName: labeled.roomName });\n return;\n }\n\n // trigger\n this._log.push(event);\n\n if (this._processing) {\n this._eventQueue.push(labeled);\n this._formatForLLM(event, roomId, labeled.roomName).then((parts) => {\n if (parts) this._injectBuffer.push(parts);\n }).catch((err) => {\n console.error(`[${this._participantName}] inject buffer format error:`, err);\n });\n return;\n }\n\n await this._processTrigger(labeled);\n\n // Drain queued events\n while (this._eventQueue.length > 0) {\n const queued = this._eventQueue;\n this._eventQueue = [];\n\n const formatted: ContentPart[][] = [];\n let batchContextRoom: string | null = null;\n const roomsProcessed = new Set<string>();\n\n for (const qe of queued) {\n const qConn = this._registry.get(qe.roomId);\n const qSenderLookupId =\n qe.event.type === \"Mentioned\"\n ? (qe.event as MentionedEvent).message.sender_id\n : qe.event.participant_id;\n const qSender = qConn?.dataSource.listParticipants().find((p) => p.id === qSenderLookupId);\n const qSenderType: ParticipantType = qSender?.type ?? \"human\";\n const qSelfId = this.getSelfIdForRoom(qe.roomId);\n const qDisposition = this._engagement.classify(qe.event, qe.roomId, qSelfId, qSenderType, qSenderLookupId);\n\n if (qDisposition === \"drop\") continue;\n\n if (!roomsProcessed.has(qe.roomId)) {\n roomsProcessed.add(qe.roomId);\n const buffered = this._buffer.flush(qe.roomId);\n for (const item of buffered) {\n const parts = await this._formatForLLM(item.event, item.roomId, item.roomName);\n if (parts) {\n formatted.push(parts);\n if (!batchContextRoom) batchContextRoom = qe.roomId;\n }\n }\n }\n\n this._log.push(qe.event);\n const parts = await this._formatForLLM(qe.event, qe.roomId, qe.roomName);\n if (parts) {\n formatted.push(parts);\n if (!batchContextRoom) batchContextRoom = qe.roomId;\n }\n }\n\n if (formatted.length > 0) {\n await this._processRaw(mergeParts(formatted), batchContextRoom);\n }\n }\n }\n\n private async _processTrigger(labeled: LabeledEvent): Promise<void> {\n const { roomId, roomName, event } = labeled;\n\n const buffered = this._buffer.flush(roomId);\n\n const contentPartArrays: ContentPart[][] = [];\n for (const item of buffered) {\n const parts = await this._formatForLLM(item.event, item.roomId, item.roomName);\n if (parts) contentPartArrays.push(parts);\n }\n\n // Cache lastMessage\n if (event.type === \"MessageSent\") {\n const conn = this._registry.get(roomId);\n const senderLabel = conn?.dataSource.listParticipants().find((p) => p.id === event.message.sender_id)?.name ?? event.message.sender_name;\n const contentPreview = event.message.content.length > 60 ? event.message.content.slice(0, 57) + \"...\" : event.message.content;\n const preview = event.message.image_url && !event.message.content.trim()\n ? \"sent an image\"\n : contentPreview;\n this._registry.setLastMessage(roomId, `${senderLabel}: ${preview}`);\n }\n\n const triggerParts = await this._formatForLLM(event, roomId, roomName);\n if (!triggerParts && contentPartArrays.length === 0) return;\n\n const mergedParts = mergeParts([...contentPartArrays, ...(triggerParts ? [triggerParts] : [])]);\n await this._processRaw(mergedParts, roomId);\n }\n\n // ── Formatting ──────────────────────────────────────────────────────────────\n\n private _resolveParticipantForRoom(roomId: string): (id: string) => Participant | null {\n return (id: string) => {\n const conn = this._registry.get(roomId);\n if (!conn) return null;\n return conn.dataSource.listParticipants().find((p) => p.id === id) ?? null;\n };\n }\n\n private async _resolveReplyContext(\n event: RoomEvent,\n roomId: string,\n ): Promise<{ senderName: string; content: string } | null> {\n if (event.type !== \"MessageSent\" && event.type !== \"Mentioned\") return null;\n const msg = event.message;\n if (!msg.reply_to_id) return null;\n const conn = this._registry.get(roomId);\n if (!conn) return null;\n const repliedTo = await conn.dataSource.getMessage(msg.reply_to_id);\n return repliedTo ? { senderName: repliedTo.sender_name, content: repliedTo.content } : null;\n }\n\n private async _resolveReactionTarget(\n event: RoomEvent,\n roomId: string,\n ): Promise<{ senderName: string; content: string; isSelf: boolean } | null> {\n if (event.type !== \"ReactionAdded\") return null;\n const conn = this._registry.get(roomId);\n if (!conn) return null;\n const target = await conn.dataSource.getMessage(event.message_id);\n if (!target) return null;\n return {\n senderName: target.sender_name,\n content: target.content,\n isSelf: target.sender_id === this._participantId,\n };\n }\n\n private async _formatForLLM(\n event: RoomEvent,\n roomId: string,\n roomName: string,\n ): Promise<ContentPart[] | null> {\n const mode = this.getModeForRoom(roomId);\n const label = mode !== \"everyone\" ? `${roomName} — ${mode}` : roomName;\n const replyContext = await this._resolveReplyContext(event, roomId);\n const reactionTarget = await this._resolveReactionTarget(event, roomId);\n return formatEvent(\n event,\n this._resolveParticipantForRoom(roomId),\n replyContext,\n label,\n reactionTarget,\n (id) => this.assignRef(id),\n );\n }\n\n // ── Delivery ────────────────────────────────────────────────────────────────\n\n private async _processRaw(\n parts: ContentPart[],\n contextRoomId: string | null,\n ): Promise<void> {\n if (this._options.preQuery && !(await this._options.preQuery())) {\n return;\n }\n\n this._processing = true;\n this._currentContextRoomId = contextRoomId;\n\n try {\n if (!this._deliver) return;\n await this._deliver(parts);\n } catch (err) {\n console.error(`[${this._participantName}] error:`, err);\n } finally {\n this._currentContextRoomId = null;\n this._processing = false;\n }\n }\n}\n","/**\n * RemoteRoomDataSource — HTTP-backed RoomDataSource.\n *\n * Implements the RoomDataSource interface by making HTTP calls to a stoop\n * server. Used by the client-side agent runtime when connecting to remote\n * stoops.\n *\n * Participant list is cached locally and updated by the agent runtime\n * when it processes ParticipantJoined/Left events from the SSE stream.\n */\n\nimport type { RoomEvent } from \"../core/events.js\";\nimport type { EventCategory, Message, PaginatedResult, Participant } from \"../core/types.js\";\nimport type { RoomDataSource } from \"./room-data-source.js\";\n\nexport class RemoteRoomDataSource implements RoomDataSource {\n private _participants: Participant[] = [];\n private _selfId = \"\";\n private _selfName = \"\";\n\n constructor(\n private _serverUrl: string,\n private _sessionToken: string,\n private _roomId: string,\n ) {}\n\n /** Set own identity for populating outgoing message stubs. */\n setSelf(id: string, name: string): void {\n this._selfId = id;\n this._selfName = name;\n }\n\n get roomId(): string {\n return this._roomId;\n }\n\n get serverUrl(): string {\n return this._serverUrl;\n }\n\n get sessionToken(): string {\n return this._sessionToken;\n }\n\n // ── Participant cache ─────────────────────────────────────────────────────\n\n /** Set the initial participant list (from join response). */\n setParticipants(participants: Participant[]): void {\n this._participants = [...participants];\n }\n\n /** Add a participant (on ParticipantJoined event). */\n addParticipant(participant: Participant): void {\n // Remove any existing entry with same ID first\n this._participants = this._participants.filter((p) => p.id !== participant.id);\n this._participants.push(participant);\n }\n\n /** Remove a participant (on ParticipantLeft event). */\n removeParticipant(participantId: string): void {\n this._participants = this._participants.filter((p) => p.id !== participantId);\n }\n\n listParticipants(): Participant[] {\n return [...this._participants];\n }\n\n // ── HTTP-backed data access ───────────────────────────────────────────────\n\n async getMessage(id: string): Promise<Message | null> {\n try {\n const res = await fetch(\n `${this._serverUrl}/message/${encodeURIComponent(id)}?token=${this._sessionToken}`,\n );\n if (!res.ok) return null;\n const data = (await res.json()) as { message: Message };\n return data.message;\n } catch {\n return null;\n }\n }\n\n async searchMessages(\n query: string,\n limit = 10,\n cursor: string | null = null,\n ): Promise<PaginatedResult<Message>> {\n const params = new URLSearchParams({ token: this._sessionToken, query, count: String(limit) });\n if (cursor) params.set(\"cursor\", cursor);\n\n try {\n const res = await fetch(`${this._serverUrl}/search?${params}`);\n if (!res.ok) return { items: [], has_more: false, next_cursor: null };\n return (await res.json()) as PaginatedResult<Message>;\n } catch {\n return { items: [], has_more: false, next_cursor: null };\n }\n }\n\n async getMessages(\n limit = 30,\n cursor: string | null = null,\n ): Promise<PaginatedResult<Message>> {\n const params = new URLSearchParams({ token: this._sessionToken, count: String(limit) });\n if (cursor) params.set(\"cursor\", cursor);\n\n try {\n const res = await fetch(`${this._serverUrl}/messages?${params}`);\n if (!res.ok) return { items: [], has_more: false, next_cursor: null };\n return (await res.json()) as PaginatedResult<Message>;\n } catch {\n return { items: [], has_more: false, next_cursor: null };\n }\n }\n\n async getEvents(\n category: EventCategory | null = null,\n limit = 50,\n cursor: string | null = null,\n ): Promise<PaginatedResult<RoomEvent>> {\n const params = new URLSearchParams({ token: this._sessionToken, count: String(limit) });\n if (category) params.set(\"category\", category);\n if (cursor) params.set(\"cursor\", cursor);\n\n try {\n const res = await fetch(`${this._serverUrl}/events/history?${params}`);\n if (!res.ok) return { items: [], has_more: false, next_cursor: null };\n return (await res.json()) as PaginatedResult<RoomEvent>;\n } catch {\n return { items: [], has_more: false, next_cursor: null };\n }\n }\n\n async sendMessage(\n content: string,\n replyToId?: string,\n image?: { url: string; mimeType: string; sizeBytes: number } | null,\n ): Promise<Message> {\n const body: Record<string, unknown> = { token: this._sessionToken, content };\n if (replyToId) body.replyTo = replyToId;\n if (image) body.image = image;\n\n const res = await fetch(`${this._serverUrl}/message`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n\n if (!res.ok) {\n const err = await res.text();\n throw new Error(`Failed to send message: ${err}`);\n }\n\n const data = (await res.json()) as { ok: boolean; messageId: string };\n\n // Return a stub — the full message will arrive via SSE\n return {\n id: data.messageId,\n room_id: this._roomId,\n sender_id: this._selfId,\n sender_name: this._selfName,\n content,\n timestamp: new Date(),\n } as Message;\n }\n\n async emitEvent(event: RoomEvent): Promise<void> {\n const res = await fetch(`${this._serverUrl}/event`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ token: this._sessionToken, event }),\n });\n\n if (!res.ok) {\n // Best effort — don't throw for event emission failures\n }\n }\n}\n","/**\n * SseMultiplexer — merges N SSE connections into one labeled event stream.\n *\n * Each connection is an HTTP fetch to a stoop server's /events endpoint.\n * Events are parsed from the SSE `data:` lines and wrapped as LabeledEvents\n * (same shape as EventMultiplexer output so EventProcessor can consume either).\n *\n * Connections can be added/removed while the multiplexer is running.\n * Each connection has its own AbortController for independent lifecycle.\n */\n\nimport type { RoomEvent } from \"../core/events.js\";\nimport type { LabeledEvent } from \"./multiplexer.js\";\n\ninterface SseConnection {\n serverUrl: string;\n sessionToken: string;\n roomName: string;\n roomId: string;\n abortController: AbortController;\n loopPromise: Promise<void>;\n}\n\nexport class SseMultiplexer {\n private _queue: LabeledEvent[] = [];\n private _waiters: Array<{ resolve: (value: LabeledEvent) => void }> = [];\n private _connections = new Map<string, SseConnection>();\n private _closed = false;\n\n /**\n * Add an SSE connection to a stoop server.\n * Starts streaming events immediately.\n */\n addConnection(serverUrl: string, sessionToken: string, roomName: string, roomId: string): void {\n if (this._connections.has(roomId) || this._closed) return;\n\n const abortController = new AbortController();\n const loopPromise = this._sseLoop(serverUrl, sessionToken, roomName, roomId, abortController.signal);\n this._connections.set(roomId, { serverUrl, sessionToken, roomName, roomId, abortController, loopPromise });\n }\n\n /** Remove a connection by room ID. */\n removeConnection(roomId: string): void {\n const entry = this._connections.get(roomId);\n if (!entry) return;\n entry.abortController.abort();\n this._connections.delete(roomId);\n }\n\n /** Close all connections and signal the iterator to finish. */\n close(): void {\n this._closed = true;\n for (const [, entry] of this._connections) {\n entry.abortController.abort();\n }\n this._connections.clear();\n\n // Resolve all pending waiters so the iterator returns done\n for (const waiter of this._waiters) {\n waiter.resolve(null as unknown as LabeledEvent);\n }\n this._waiters = [];\n }\n\n private async _sseLoop(\n serverUrl: string,\n sessionToken: string,\n roomName: string,\n roomId: string,\n signal: AbortSignal,\n ): Promise<void> {\n const INITIAL_BACKOFF = 1000;\n const MAX_BACKOFF = 30000;\n let backoff = INITIAL_BACKOFF;\n\n while (!signal.aborted && !this._closed) {\n try {\n // POST required — Cloudflare Quick Tunnels buffer GET streaming\n // responses and only flush on connection close. POST streams in real-time.\n const res = await fetch(`${serverUrl}/events`, {\n method: \"POST\",\n headers: {\n Accept: \"text/event-stream\",\n Authorization: `Bearer ${sessionToken}`,\n },\n signal,\n });\n\n if (!res.ok || !res.body) {\n throw new Error(`SSE connect failed: ${res.status}`);\n }\n\n // Connected — reset backoff\n backoff = INITIAL_BACKOFF;\n\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n while (!signal.aborted) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n // Parse SSE: split on double newline\n const parts = buffer.split(\"\\n\\n\");\n buffer = parts.pop()!; // keep incomplete chunk\n\n for (const part of parts) {\n const dataLine = part.split(\"\\n\").find((l) => l.startsWith(\"data: \"));\n if (!dataLine) continue;\n\n try {\n const event = JSON.parse(dataLine.slice(6)) as RoomEvent;\n this._push({ roomId, roomName, event });\n } catch {\n // Malformed event — skip\n }\n }\n }\n } catch (err) {\n if (signal.aborted) break;\n // Connection failed or dropped — backoff and retry\n }\n\n if (!signal.aborted && !this._closed) {\n await new Promise((r) => setTimeout(r, backoff));\n backoff = Math.min(backoff * 2, MAX_BACKOFF);\n }\n }\n }\n\n private _push(labeled: LabeledEvent): void {\n if (this._closed) return;\n if (this._waiters.length > 0) {\n const waiter = this._waiters.shift()!;\n waiter.resolve(labeled);\n } else {\n this._queue.push(labeled);\n }\n }\n\n [Symbol.asyncIterator](): AsyncIterator<LabeledEvent> {\n return {\n next: (): Promise<IteratorResult<LabeledEvent>> => {\n // Drain buffered events first\n if (this._queue.length > 0) {\n return Promise.resolve({ value: this._queue.shift()!, done: false });\n }\n\n if (this._closed) {\n return Promise.resolve({ value: undefined as unknown as LabeledEvent, done: true });\n }\n\n return new Promise<IteratorResult<LabeledEvent>>((resolve) => {\n this._waiters.push({\n resolve: (value) => {\n if (this._closed || value === null) {\n resolve({ value: undefined as unknown as LabeledEvent, done: true });\n } else {\n resolve({ value, done: false });\n }\n },\n });\n });\n },\n };\n }\n}\n"],"mappings":"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","names":["parts"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// src/core/types.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { v4 as uuidv4 } from "uuid";
|
|
4
|
+
var EventCategory = {
|
|
5
|
+
MESSAGE: "MESSAGE",
|
|
6
|
+
PRESENCE: "PRESENCE",
|
|
7
|
+
ACTIVITY: "ACTIVITY",
|
|
8
|
+
MENTION: "MENTION"
|
|
9
|
+
};
|
|
10
|
+
var MessageSchema = z.object({
|
|
11
|
+
id: z.string().default(() => uuidv4()),
|
|
12
|
+
room_id: z.string(),
|
|
13
|
+
sender_id: z.string(),
|
|
14
|
+
sender_name: z.string(),
|
|
15
|
+
content: z.string(),
|
|
16
|
+
reply_to_id: z.string().nullable().default(null),
|
|
17
|
+
image_url: z.string().nullable().default(null),
|
|
18
|
+
image_mime_type: z.string().nullable().default(null),
|
|
19
|
+
image_size_bytes: z.number().int().nullable().default(null),
|
|
20
|
+
timestamp: z.date().default(() => /* @__PURE__ */ new Date())
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// src/core/events.ts
|
|
24
|
+
import { v4 as uuidv42 } from "uuid";
|
|
25
|
+
var EVENT_ROLE = {
|
|
26
|
+
MessageSent: "message",
|
|
27
|
+
Mentioned: "mention",
|
|
28
|
+
ParticipantJoined: "ambient",
|
|
29
|
+
ParticipantLeft: "ambient",
|
|
30
|
+
ParticipantKicked: "ambient",
|
|
31
|
+
AuthorityChanged: "ambient",
|
|
32
|
+
ReactionAdded: "ambient",
|
|
33
|
+
ContextCompacted: "ambient",
|
|
34
|
+
MessageEdited: "internal",
|
|
35
|
+
MessageDeleted: "internal",
|
|
36
|
+
ReactionRemoved: "internal",
|
|
37
|
+
StatusChanged: "internal",
|
|
38
|
+
ToolUse: "internal",
|
|
39
|
+
Activity: "internal"
|
|
40
|
+
};
|
|
41
|
+
function createEvent(data) {
|
|
42
|
+
return { id: uuidv42(), timestamp: /* @__PURE__ */ new Date(), ...data };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export {
|
|
46
|
+
EventCategory,
|
|
47
|
+
MessageSchema,
|
|
48
|
+
EVENT_ROLE,
|
|
49
|
+
createEvent
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=chunk-PKFZHCQF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/types.ts","../src/core/events.ts"],"sourcesContent":["/** Core types for stoops — messages, participants, pagination. */\n\nimport { z } from \"zod\";\nimport { v4 as uuidv4 } from \"uuid\";\n\n// ── Event categories ─────────────────────────────────────────────────────────\n\n/**\n * The four top-level categories events are grouped under.\n *\n * Channels subscribe to one or more categories — they only receive events in\n * their subscription set. Use this to filter what an agent or observer sees:\n *\n * - MESSAGE — chat messages and reactions (sent, edited, deleted, reacted)\n * - PRESENCE — participants joining and leaving\n * - ACTIVITY — agent activity (thinking, tool use, mode changes, compaction)\n * - MENTION — direct @mentions delivered only to the mentioned participant\n */\nexport const EventCategory = {\n MESSAGE: \"MESSAGE\",\n PRESENCE: \"PRESENCE\",\n ACTIVITY: \"ACTIVITY\",\n MENTION: \"MENTION\",\n} as const;\n\nexport type EventCategory = (typeof EventCategory)[keyof typeof EventCategory];\n\n// ── Message ──────────────────────────────────────────────────────────────────\n\n/**\n * A chat message. Immutable once stored — edits and deletes are separate events.\n *\n * - `id` — UUID, assigned by the room on creation\n * - `room_id` — the room this message belongs to\n * - `sender_id` — participant ID of the author\n * - `sender_name` — display name at the time of sending (denormalized)\n * - `content` — text body (may be empty if the message is image-only)\n * - `reply_to_id` — if set, this message is a reply to that message ID\n * - `image_url` — optional attached image URL\n * - `image_mime_type` — MIME type of the attached image (e.g. \"image/jpeg\")\n * - `image_size_bytes` — size of the image in bytes\n * - `timestamp` — creation time (UTC)\n */\nexport const MessageSchema = z.object({\n id: z.string().default(() => uuidv4()),\n room_id: z.string(),\n sender_id: z.string(),\n sender_name: z.string(),\n content: z.string(),\n reply_to_id: z.string().nullable().default(null),\n image_url: z.string().nullable().default(null),\n image_mime_type: z.string().nullable().default(null),\n image_size_bytes: z.number().int().nullable().default(null),\n timestamp: z.date().default(() => new Date()),\n});\n\nexport type Message = z.infer<typeof MessageSchema>;\n\n// ── Authority ────────────────────────────────────────────────────────────────\n\n/**\n * Authority level — what a participant is allowed to do.\n *\n * - `admin` — full control: kick, set others' modes, generate share links\n * - `member` — can send messages, set own mode\n * - `guest` — read-only: can catch up and search, but can't send or act\n *\n * Set on join, doesn't change during the session. Orthogonal to engagement mode.\n */\nexport type AuthorityLevel = \"admin\" | \"member\" | \"guest\";\n\n// ── Participant ───────────────────────────────────────────────────────────────\n\n/** Whether a participant is a human or an agent. */\nexport type ParticipantType = \"human\" | \"agent\";\n\n/**\n * A participant in a room.\n *\n * - `id` — stable unique ID across all rooms and sessions\n * - `name` — display name (mutable — participants can rename)\n * - `status` — current presence status (\"online\", \"offline\", etc.)\n * - `type` — \"human\" or \"agent\"\n * - `identifier` — optional stable @-mention slug, e.g. \"my-agent\".\n * Unlike `name`, this never changes on rename.\n * Used for @-mention matching in addition to the display name.\n * Not all participants have one — guests and anonymous users\n * typically don't.\n * - `authority` — optional authority level. Defaults to \"member\" if unset.\n */\nexport interface Participant {\n id: string;\n name: string;\n status: string;\n type: ParticipantType;\n identifier?: string;\n authority?: AuthorityLevel;\n}\n\n// ── Pagination ────────────────────────────────────────────────────────────────\n\n/**\n * A page of results with a cursor for fetching the previous page.\n *\n * All paginated queries return results newest-first. Pass `next_cursor` back\n * to the same query to continue paginating backwards through history.\n *\n * - `items` — results for this page (newest-first)\n * - `next_cursor` — pass this to get the next (older) page; null when exhausted\n * - `has_more` — true if there are older items beyond this page\n */\nexport interface PaginatedResult<T> {\n items: T[];\n next_cursor: string | null;\n has_more: boolean;\n}\n","/**\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 { AuthorityLevel, 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/**\n * A participant was kicked from the room by an admin.\n *\n * Emitted before the channel disconnect so all participants see it.\n * The subsequent `ParticipantLeft` event is suppressed (silent disconnect).\n */\nexport interface ParticipantKickedEvent extends BaseRoomEvent {\n type: \"ParticipantKicked\";\n category: \"PRESENCE\";\n room_id: string;\n /** The kicked participant. */\n participant_id: string;\n timestamp: Date;\n /** Full participant snapshot at kick time. */\n participant: Participant;\n /** Display name of the admin who kicked them. */\n kicked_by: string;\n}\n\n/**\n * A participant's authority level was changed by an admin.\n *\n * Covers mute (→ guest), unmute (→ member), and promotion (→ admin).\n */\nexport interface AuthorityChangedEvent extends BaseRoomEvent {\n type: \"AuthorityChanged\";\n category: \"PRESENCE\";\n room_id: string;\n /** The affected participant. */\n participant_id: string;\n timestamp: Date;\n /** Full participant snapshot (with updated authority). */\n participant: Participant;\n /** The new authority level. */\n new_authority: AuthorityLevel;\n /** Display name of the admin who made the change. */\n changed_by: string;\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 | ParticipantKickedEvent\n | AuthorityChangedEvent\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 ParticipantKicked: \"ambient\",\n AuthorityChanged: \"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,SAAS;AAClB,SAAS,MAAM,cAAc;AAetB,IAAM,gBAAgB;AAAA,EAC3B,SAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,SAAU;AACZ;AAoBO,IAAM,gBAAgB,EAAE,OAAO;AAAA,EACpC,IAAI,EAAE,OAAO,EAAE,QAAQ,MAAM,OAAO,CAAC;AAAA,EACrC,SAAS,EAAE,OAAO;AAAA,EAClB,WAAW,EAAE,OAAO;AAAA,EACpB,aAAa,EAAE,OAAO;AAAA,EACtB,SAAS,EAAE,OAAO;AAAA,EAClB,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,IAAI;AAAA,EAC/C,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,IAAI;AAAA,EAC7C,iBAAiB,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,IAAI;AAAA,EACnD,kBAAkB,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,IAAI;AAAA,EAC1D,WAAW,EAAE,KAAK,EAAE,QAAQ,MAAM,oBAAI,KAAK,CAAC;AAC9C,CAAC;;;ACvCD,SAAS,MAAMA,eAAc;AAyQtB,IAAM,aAAmD;AAAA,EAC9D,aAAmB;AAAA,EACnB,WAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,iBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,kBAAmB;AAAA,EACnB,eAAmB;AAAA,EACnB,kBAAmB;AAAA,EACnB,eAAmB;AAAA,EACnB,gBAAmB;AAAA,EACnB,iBAAmB;AAAA,EACnB,eAAmB;AAAA,EACnB,SAAmB;AAAA,EACnB,UAAmB;AACrB;AAqBO,SAAS,YAAiC,MAAuB;AACtE,SAAO,EAAE,IAAIA,QAAO,GAAG,WAAW,oBAAI,KAAK,GAAG,GAAG,KAAK;AACxD;","names":["uuidv4"]}
|