stoops 0.1.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/README.md +208 -4
- package/dist/agent/index.d.ts +553 -0
- package/dist/agent/index.js +41 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/chunk-5ADJGMXQ.js +27 -0
- package/dist/chunk-5ADJGMXQ.js.map +1 -0
- package/dist/chunk-66EFQ2XO.js +706 -0
- package/dist/chunk-66EFQ2XO.js.map +1 -0
- package/dist/chunk-B4LBE5QS.js +115 -0
- package/dist/chunk-B4LBE5QS.js.map +1 -0
- package/dist/chunk-EPLQQF6S.js +27 -0
- package/dist/chunk-EPLQQF6S.js.map +1 -0
- package/dist/chunk-HKFCJO7V.js +690 -0
- package/dist/chunk-HKFCJO7V.js.map +1 -0
- package/dist/chunk-OA3CODNP.js +968 -0
- package/dist/chunk-OA3CODNP.js.map +1 -0
- package/dist/claude/index.d.ts +26 -0
- package/dist/claude/index.js +215 -0
- package/dist/claude/index.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +2490 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index-ByKHLUOe.d.ts +674 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +23 -2
- package/dist/index.js.map +1 -0
- package/dist/langgraph/index.d.ts +30 -0
- package/dist/langgraph/index.js +250 -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-9iTDVOJG.d.ts +201 -0
- package/package.json +69 -11
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
import { a as ContentPart, R as RoomResolver, b as RoomConnection, c as RoomDataSource, T as ToolHandlerOptions } from '../types-9iTDVOJG.js';
|
|
2
|
+
export { A as AgentIdentity, C as ClaudeSessionOptions, I as ILLMSession, d as LLMQueryStats, e as LLMSessionOptions, L as LangGraphSessionOptions, f as LocalRoomDataSource, P as ProcessorBridge, Q as QueryTurn, S as SessionCallbacks } from '../types-9iTDVOJG.js';
|
|
3
|
+
import { R as RoomEvent, P as Participant, c as ParticipantType, C as Channel, b as Room, M as Message, a as PaginatedResult, E as EventCategory } from '../index-ByKHLUOe.js';
|
|
4
|
+
import 'zod';
|
|
5
|
+
|
|
6
|
+
/** Event formatting and mode descriptions for stoops agents. */
|
|
7
|
+
|
|
8
|
+
declare function getSystemPreamble(identifier?: string, personParticipantId?: string): string;
|
|
9
|
+
/** Short 4-char ref for a message ID, used in transcripts. */
|
|
10
|
+
declare function messageRef(messageId: string): string;
|
|
11
|
+
/** Format a participant as a labeled name: "[human] Alice" or "[agent] Quinn". */
|
|
12
|
+
declare function participantLabel(p: Participant | null, fallback?: string): string;
|
|
13
|
+
/** Convert ContentPart[] back to a plain string (for trace logs and stats). */
|
|
14
|
+
declare function contentPartsToString(parts: ContentPart[]): string;
|
|
15
|
+
/**
|
|
16
|
+
* Format a typed event as ContentPart[] for the LLM session.
|
|
17
|
+
* Returns null for events that shouldn't be sent to the LLM (noise).
|
|
18
|
+
*
|
|
19
|
+
* Compact one-liner format:
|
|
20
|
+
* Messages: [14:23:01] #3847 [lobby] Alice: hey everyone
|
|
21
|
+
* Replies: [14:23:01] #9102 [lobby] Alice (→ #3847 Bob): good point
|
|
22
|
+
* Mentions: [14:23:01] #5521 [lobby] ⚡ Alice: @bot what do you think?
|
|
23
|
+
* Joined: [14:23:01] [lobby] + Alice joined
|
|
24
|
+
* Left: [14:23:15] [lobby] - Bob left
|
|
25
|
+
* Reactions: [14:23:20] [lobby] Alice reacted ❤️ to #3847
|
|
26
|
+
*/
|
|
27
|
+
declare function formatEvent(event: RoomEvent, resolveParticipant: (id: string) => Participant | null, replyContext?: {
|
|
28
|
+
senderName: string;
|
|
29
|
+
content: string;
|
|
30
|
+
} | null, roomLabel?: string, reactionTarget?: {
|
|
31
|
+
senderName: string;
|
|
32
|
+
content: string;
|
|
33
|
+
isSelf: boolean;
|
|
34
|
+
} | null, assignRef?: (messageId: string) => string): ContentPart[] | null;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Engagement — controls which room events trigger LLM evaluation.
|
|
38
|
+
*
|
|
39
|
+
* # Overview
|
|
40
|
+
*
|
|
41
|
+
* Every event that reaches an agent is classified into one of three dispositions:
|
|
42
|
+
* - "trigger" — evaluate now (start an LLM call)
|
|
43
|
+
* - "content" — buffer as context; deliver to LLM on the next trigger
|
|
44
|
+
* - "drop" — ignore entirely (not delivered to LLM)
|
|
45
|
+
*
|
|
46
|
+
* The `EngagementStrategy` interface defines this contract. Implement it to
|
|
47
|
+
* customize when your agent responds. The built-in `StoopsEngagement` provides
|
|
48
|
+
* an 8-mode system; `classifyEvent()` is a standalone convenience function
|
|
49
|
+
* using the same logic.
|
|
50
|
+
*
|
|
51
|
+
* # Built-in modes (StoopsEngagement / classifyEvent)
|
|
52
|
+
*
|
|
53
|
+
* Active modes (agent evaluates on matching messages):
|
|
54
|
+
* - "everyone" — all messages trigger evaluation
|
|
55
|
+
* - "people" — only messages from human participants trigger evaluation
|
|
56
|
+
* - "agents" — only messages from other agents trigger evaluation
|
|
57
|
+
* - "me" — only messages from the agent's designated owner ("person") trigger
|
|
58
|
+
*
|
|
59
|
+
* Standby modes (agent only wakes on @mentions):
|
|
60
|
+
* - "standby-everyone" — any @mention wakes the agent
|
|
61
|
+
* - "standby-people" — only @mentions from humans wake the agent
|
|
62
|
+
* - "standby-agents" — only @mentions from other agents wake the agent
|
|
63
|
+
* - "standby-me" — only an @mention from the agent's owner wakes them
|
|
64
|
+
*
|
|
65
|
+
* # Classification rules (in order)
|
|
66
|
+
*
|
|
67
|
+
* 1. Internal events (bookkeeping: edits, deletes, status changes, agent
|
|
68
|
+
* activity) → always drop
|
|
69
|
+
* 2. Self-sent events → drop (the agent ignores its own activity).
|
|
70
|
+
* Exception: mentions are not self-dropped — a standby agent should wake
|
|
71
|
+
* if it is @mentioned, even if the mention event's participant_id is itself.
|
|
72
|
+
* 3. Standby modes: only @mentions directed at the agent from a matching
|
|
73
|
+
* sender → trigger; everything else → drop.
|
|
74
|
+
* 4. Active modes, @mention → drop (the MessageSent event already carries the
|
|
75
|
+
* @mention text; delivering both would be redundant).
|
|
76
|
+
* 5. Active modes, message from matching sender → trigger
|
|
77
|
+
* 6. Active modes, message from non-matching sender → content (buffered context)
|
|
78
|
+
* 7. Active modes, ambient event (join/leave/reaction) → content
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The outcome of classifying an event for a given agent.
|
|
83
|
+
*
|
|
84
|
+
* - "trigger" — run an LLM evaluation now
|
|
85
|
+
* - "content" — buffer as context; include with the next evaluation
|
|
86
|
+
* - "drop" — discard; never shown to the LLM
|
|
87
|
+
*/
|
|
88
|
+
type EventDisposition = "trigger" | "content" | "drop";
|
|
89
|
+
/**
|
|
90
|
+
* Engagement strategy — decides which events trigger LLM evaluation.
|
|
91
|
+
*
|
|
92
|
+
* Implement this to customize when your agent responds. The runtime calls
|
|
93
|
+
* `classify()` for every incoming event and acts on the returned disposition.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* // Respond to everything:
|
|
97
|
+
* class AlwaysEngage implements EngagementStrategy {
|
|
98
|
+
* classify(event, roomId, selfId) {
|
|
99
|
+
* if (event.participant_id === selfId) return "drop";
|
|
100
|
+
* return "trigger";
|
|
101
|
+
* }
|
|
102
|
+
* }
|
|
103
|
+
*/
|
|
104
|
+
interface EngagementStrategy {
|
|
105
|
+
/**
|
|
106
|
+
* Classify a room event for this agent.
|
|
107
|
+
*
|
|
108
|
+
* @param event — the room event to classify
|
|
109
|
+
* @param roomId — the room the event came from
|
|
110
|
+
* @param selfId — the agent's own participant ID (to drop self-events)
|
|
111
|
+
* @param senderType — "human" or "agent" for the event's sender
|
|
112
|
+
* @param senderId — participant ID of the sender. For `Mentioned` events,
|
|
113
|
+
* pass `event.message.sender_id` (who wrote the mention),
|
|
114
|
+
* not `event.participant_id` (who was mentioned).
|
|
115
|
+
*/
|
|
116
|
+
classify(event: RoomEvent, roomId: string, selfId: string, senderType: ParticipantType, senderId: string): EventDisposition;
|
|
117
|
+
/**
|
|
118
|
+
* Return the current engagement mode for a room.
|
|
119
|
+
*
|
|
120
|
+
* Optional — strategies that don't use named modes may omit this.
|
|
121
|
+
* The runtime falls back to `"everyone"` when absent.
|
|
122
|
+
*/
|
|
123
|
+
getMode?(roomId: string): EngagementMode;
|
|
124
|
+
/**
|
|
125
|
+
* Update the engagement mode for a room.
|
|
126
|
+
*
|
|
127
|
+
* Optional — called by the runtime when a room connects with an initial mode
|
|
128
|
+
* or when the user changes the mode at runtime. Strategies that don't use
|
|
129
|
+
* named modes may omit this; the call will be silently ignored.
|
|
130
|
+
*/
|
|
131
|
+
setMode?(roomId: string, mode: EngagementMode): void;
|
|
132
|
+
/**
|
|
133
|
+
* Called when a room is disconnected from the runtime.
|
|
134
|
+
*
|
|
135
|
+
* Optional — use this for cleanup, e.g. removing per-room state that is
|
|
136
|
+
* no longer needed. Strategies with no per-room state may omit this.
|
|
137
|
+
*/
|
|
138
|
+
onRoomDisconnected?(roomId: string): void;
|
|
139
|
+
}
|
|
140
|
+
type EngagementMode = "me" | "people" | "agents" | "everyone" | "standby-me" | "standby-people" | "standby-agents" | "standby-everyone";
|
|
141
|
+
/**
|
|
142
|
+
* StoopsEngagement — the built-in engagement strategy.
|
|
143
|
+
*
|
|
144
|
+
* Implements the 8-mode system: 4 active modes (everyone/people/agents/me)
|
|
145
|
+
* and 4 standby modes that only wake on @mentions. Maintains per-room mode
|
|
146
|
+
* state internally.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* const engagement = new StoopsEngagement("people", personId);
|
|
150
|
+
* engagement.setMode("room-1", "me");
|
|
151
|
+
* engagement.classify(event, "room-1", selfId, "human", senderId);
|
|
152
|
+
*/
|
|
153
|
+
declare class StoopsEngagement implements EngagementStrategy {
|
|
154
|
+
private _modes;
|
|
155
|
+
private _defaultMode;
|
|
156
|
+
private _personParticipantId?;
|
|
157
|
+
constructor(defaultMode: EngagementMode, personParticipantId?: string);
|
|
158
|
+
/** Get the engagement mode for a room. Falls back to the default mode. */
|
|
159
|
+
getMode(roomId: string): EngagementMode;
|
|
160
|
+
/** Set the engagement mode for a room. */
|
|
161
|
+
setMode(roomId: string, mode: EngagementMode): void;
|
|
162
|
+
/** Called when a room is disconnected. Removes the room's mode so it doesn't linger. */
|
|
163
|
+
onRoomDisconnected(roomId: string): void;
|
|
164
|
+
classify(event: RoomEvent, roomId: string, selfId: string, senderType: ParticipantType, senderId: string): EventDisposition;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Classify a room event for an agent with the given engagement mode.
|
|
168
|
+
*
|
|
169
|
+
* Pure function — no state, no side effects, no SDK dependency.
|
|
170
|
+
* Uses the same classification logic as `StoopsEngagement` but takes all
|
|
171
|
+
* parameters explicitly. Useful for one-off classification or testing.
|
|
172
|
+
*
|
|
173
|
+
* @param event — the event to classify
|
|
174
|
+
* @param mode — the agent's current engagement mode for this room
|
|
175
|
+
* @param selfId — the agent's own participant ID (to drop self-events)
|
|
176
|
+
* @param senderType — type of the participant who caused the event
|
|
177
|
+
* @param senderId — participant ID of the sender.
|
|
178
|
+
* For `Mentioned` events, pass `event.message.sender_id`
|
|
179
|
+
* (who wrote the mention), not `event.participant_id`
|
|
180
|
+
* (who was mentioned).
|
|
181
|
+
* @param personParticipantId — the agent's owner's participant ID, used in
|
|
182
|
+
* "me" and "standby-me" modes
|
|
183
|
+
*/
|
|
184
|
+
declare function classifyEvent(event: RoomEvent, mode: EngagementMode, selfId: string, senderType: ParticipantType, senderId: string, personParticipantId?: string): EventDisposition;
|
|
185
|
+
|
|
186
|
+
/** EventMultiplexer — merges N channel async streams into one labeled stream. */
|
|
187
|
+
|
|
188
|
+
interface LabeledEvent {
|
|
189
|
+
roomId: string;
|
|
190
|
+
roomName: string;
|
|
191
|
+
event: RoomEvent;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Merges events from multiple channels into a single async iterable stream.
|
|
195
|
+
* Each event is labeled with its source room's ID and name.
|
|
196
|
+
*
|
|
197
|
+
* Channels can be added/removed while the multiplexer is running.
|
|
198
|
+
*/
|
|
199
|
+
declare class EventMultiplexer {
|
|
200
|
+
private _queue;
|
|
201
|
+
private _waiters;
|
|
202
|
+
private _channels;
|
|
203
|
+
private _closed;
|
|
204
|
+
private _closeResolve;
|
|
205
|
+
addChannel(roomId: string, roomName: string, channel: Channel): void;
|
|
206
|
+
removeChannel(roomId: string): void;
|
|
207
|
+
close(): void;
|
|
208
|
+
private _listenLoop;
|
|
209
|
+
private _push;
|
|
210
|
+
[Symbol.asyncIterator](): AsyncIterator<LabeledEvent>;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* EventProcessor — core event loop for stoops agents.
|
|
215
|
+
*
|
|
216
|
+
* Owns: engagement classification, content buffering, event formatting,
|
|
217
|
+
* ref map, room connections, mode management. Does NOT own: LLM sessions,
|
|
218
|
+
* MCP servers, compaction hooks, stats tracking.
|
|
219
|
+
*
|
|
220
|
+
* Delivery is pluggable — pass a `deliver` callback to `run()`.
|
|
221
|
+
* The callback receives formatted ContentPart[] and does whatever the
|
|
222
|
+
* consumer needs (Claude SDK query, LangGraph injection, tmux send-keys).
|
|
223
|
+
*/
|
|
224
|
+
|
|
225
|
+
interface EventProcessorOptions {
|
|
226
|
+
/** Engagement mode when no per-room override is set. */
|
|
227
|
+
defaultMode?: EngagementMode;
|
|
228
|
+
/** Custom engagement strategy. Defaults to StoopsEngagement. */
|
|
229
|
+
engagement?: EngagementStrategy;
|
|
230
|
+
/** The agent owner's participant ID (for "me" / "standby-me" modes). */
|
|
231
|
+
personParticipantId?: string;
|
|
232
|
+
/** The agent's own stable identifier slug (e.g. "my-agent"). */
|
|
233
|
+
selfIdentifier?: string;
|
|
234
|
+
/** Called when engagement mode changes for a room. */
|
|
235
|
+
onModeChange?: (roomId: string, roomName: string, mode: EngagementMode) => void;
|
|
236
|
+
/** Called before each delivery. Return false to skip. */
|
|
237
|
+
preQuery?: () => Promise<boolean>;
|
|
238
|
+
}
|
|
239
|
+
declare class EventProcessor implements RoomResolver {
|
|
240
|
+
private _participantId;
|
|
241
|
+
private _participantName;
|
|
242
|
+
private _options;
|
|
243
|
+
private _engagement;
|
|
244
|
+
private _deliver;
|
|
245
|
+
private _multiplexer;
|
|
246
|
+
private _registry;
|
|
247
|
+
private _buffer;
|
|
248
|
+
private _tracker;
|
|
249
|
+
private _processing;
|
|
250
|
+
private _eventQueue;
|
|
251
|
+
private _stopped;
|
|
252
|
+
private _currentContextRoomId;
|
|
253
|
+
private _log;
|
|
254
|
+
private _refMap;
|
|
255
|
+
private _injectBuffer;
|
|
256
|
+
/** Per-room participant IDs (for multi-server CLI agents where each server assigns a different ID). */
|
|
257
|
+
private _roomSelfIds;
|
|
258
|
+
constructor(participantId: string, participantName: string, options?: EventProcessorOptions);
|
|
259
|
+
get participantId(): string;
|
|
260
|
+
set participantId(id: string);
|
|
261
|
+
get participantName(): string;
|
|
262
|
+
get currentContextRoomId(): string | null;
|
|
263
|
+
/** Set a room-specific participant ID (for multi-server CLI agents). */
|
|
264
|
+
setRoomParticipantId(roomId: string, participantId: string): void;
|
|
265
|
+
/** Get the effective selfId for a room — room-specific if set, otherwise the global one. */
|
|
266
|
+
getSelfIdForRoom(roomId: string): string;
|
|
267
|
+
assignRef(messageId: string): string;
|
|
268
|
+
resolveRef(ref: string): string | undefined;
|
|
269
|
+
isEventSeen(eventId: string): boolean;
|
|
270
|
+
markEventsSeen(eventIds: string[]): void;
|
|
271
|
+
drainInjectBuffer(): ContentPart[][] | null;
|
|
272
|
+
/**
|
|
273
|
+
* Called by the consumer when context was compacted.
|
|
274
|
+
* Clears seen-event cache and ref map so catch_up returns full history.
|
|
275
|
+
*/
|
|
276
|
+
onContextCompacted(): void;
|
|
277
|
+
/**
|
|
278
|
+
* Called by the consumer when a tool call starts or completes.
|
|
279
|
+
* Routes ToolUseEvent to the room that triggered the current evaluation.
|
|
280
|
+
*/
|
|
281
|
+
emitToolUse(toolName: string, status: "started" | "completed"): void;
|
|
282
|
+
resolve(roomName: string): RoomConnection | null;
|
|
283
|
+
listAll(): Array<{
|
|
284
|
+
name: string;
|
|
285
|
+
roomId: string;
|
|
286
|
+
identifier?: string;
|
|
287
|
+
mode: string;
|
|
288
|
+
participantCount: number;
|
|
289
|
+
lastMessage?: string;
|
|
290
|
+
}>;
|
|
291
|
+
connectRoom(room: Room, roomName: string, mode?: EngagementMode, identifier?: string): Promise<void>;
|
|
292
|
+
/**
|
|
293
|
+
* Connect a remote room via a RoomDataSource (no local Room/Channel).
|
|
294
|
+
*
|
|
295
|
+
* Used by the client-side agent runtime to register rooms that are
|
|
296
|
+
* accessed over HTTP. Events come from an external source (SSE multiplexer)
|
|
297
|
+
* passed to run(), not from the internal EventMultiplexer.
|
|
298
|
+
*/
|
|
299
|
+
connectRemoteRoom(dataSource: RoomDataSource, roomName: string, mode?: EngagementMode, identifier?: string): void;
|
|
300
|
+
/** Disconnect a remote room (by room ID). */
|
|
301
|
+
disconnectRemoteRoom(roomId: string): void;
|
|
302
|
+
disconnectRoom(roomId: string): Promise<void>;
|
|
303
|
+
getModeForRoom(roomId: string): EngagementMode;
|
|
304
|
+
setModeForRoom(roomId: string, mode: EngagementMode, notifyAgent?: boolean): void;
|
|
305
|
+
getLog(): RoomEvent[];
|
|
306
|
+
/**
|
|
307
|
+
* Start the event loop.
|
|
308
|
+
*
|
|
309
|
+
* @param deliver — callback that receives formatted content and delivers
|
|
310
|
+
* it to the agent. This is the consumer's responsibility. The function
|
|
311
|
+
* should block until delivery is complete (e.g., LLM evaluation finished).
|
|
312
|
+
* @param eventSource — optional external event source (e.g. SseMultiplexer).
|
|
313
|
+
* If provided, events are consumed from this instead of the internal
|
|
314
|
+
* EventMultiplexer. Used by the client-side agent runtime.
|
|
315
|
+
* @param initialParts — optional content to deliver before entering the
|
|
316
|
+
* event loop. Used by the runtime to deliver auto-join confirmation.
|
|
317
|
+
*/
|
|
318
|
+
run(deliver: (parts: ContentPart[]) => Promise<void>, eventSource?: AsyncIterable<LabeledEvent>, initialParts?: ContentPart[]): Promise<void>;
|
|
319
|
+
stop(): Promise<void>;
|
|
320
|
+
buildFullCatchUp(): Promise<ContentPart[]>;
|
|
321
|
+
private _buildFullCatchUp;
|
|
322
|
+
private _handleLabeledEvent;
|
|
323
|
+
private _processTrigger;
|
|
324
|
+
private _resolveParticipantForRoom;
|
|
325
|
+
private _resolveReplyContext;
|
|
326
|
+
private _resolveReactionTarget;
|
|
327
|
+
private _formatForLLM;
|
|
328
|
+
private _processRaw;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Full MCP server — for embedded/API agents without filesystem access.
|
|
333
|
+
*
|
|
334
|
+
* Creates a proper MCP server using @modelcontextprotocol/sdk with
|
|
335
|
+
* StreamableHTTP transport on a random localhost port.
|
|
336
|
+
*
|
|
337
|
+
* 4 tools: catch_up, search_by_text, search_by_message, send_message.
|
|
338
|
+
*
|
|
339
|
+
* Returns { url, instance, stop } where:
|
|
340
|
+
* url — http://127.0.0.1:PORT/mcp (for any MCP-capable client)
|
|
341
|
+
* instance — McpServer instance (for Claude SDK in-process shortcut)
|
|
342
|
+
* stop — shuts down the HTTP listener
|
|
343
|
+
*/
|
|
344
|
+
|
|
345
|
+
interface StoopsMcpServer {
|
|
346
|
+
/** HTTP URL for URL-based MCP clients (e.g. LangGraph, external tools). */
|
|
347
|
+
url: string;
|
|
348
|
+
/**
|
|
349
|
+
* Raw McpServer instance — passed to Claude SDK as
|
|
350
|
+
* `{ type: 'sdk', name: 'stoops_tools', instance }` to avoid HTTP overhead.
|
|
351
|
+
*/
|
|
352
|
+
instance: any;
|
|
353
|
+
/** Shut down the HTTP listener. */
|
|
354
|
+
stop: () => Promise<void>;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Start a full stoops MCP server (all 4 tools).
|
|
358
|
+
* Call once per session start; call stop() on session stop.
|
|
359
|
+
*/
|
|
360
|
+
declare function createFullMcpServer(resolver: RoomResolver, options: ToolHandlerOptions): Promise<StoopsMcpServer>;
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Runtime MCP server — local MCP proxy for the client-side agent runtime.
|
|
364
|
+
*
|
|
365
|
+
* Claude Code / OpenCode connects to this local server. Tool calls are routed
|
|
366
|
+
* to the right stoop server via the RoomResolver (which maps room names to
|
|
367
|
+
* RemoteRoomDataSource instances).
|
|
368
|
+
*
|
|
369
|
+
* Tools:
|
|
370
|
+
* Always present:
|
|
371
|
+
* stoops__catch_up(room?) — with room: room catch-up. Without: list rooms + pending invites.
|
|
372
|
+
* stoops__search_by_text(room, query, count?, cursor?)
|
|
373
|
+
* stoops__search_by_message(room, ref, direction?, count?)
|
|
374
|
+
* stoops__send_message(room, content, reply_to?)
|
|
375
|
+
* stoops__set_mode(room, mode)
|
|
376
|
+
* stoops__join_room(url, alias?)
|
|
377
|
+
* stoops__leave_room(room)
|
|
378
|
+
*
|
|
379
|
+
* With --admin flag:
|
|
380
|
+
* stoops__admin__set_mode_for(room, participant, mode)
|
|
381
|
+
* stoops__admin__kick(room, participant)
|
|
382
|
+
*/
|
|
383
|
+
|
|
384
|
+
interface JoinRoomResult {
|
|
385
|
+
success: boolean;
|
|
386
|
+
error?: string;
|
|
387
|
+
roomName?: string;
|
|
388
|
+
agentName?: string;
|
|
389
|
+
authority?: string;
|
|
390
|
+
mode?: string;
|
|
391
|
+
personName?: string;
|
|
392
|
+
participants?: Array<{
|
|
393
|
+
name: string;
|
|
394
|
+
authority: string;
|
|
395
|
+
}>;
|
|
396
|
+
recentLines?: string[];
|
|
397
|
+
}
|
|
398
|
+
interface RuntimeMcpServerOptions {
|
|
399
|
+
resolver: RoomResolver;
|
|
400
|
+
toolOptions: ToolHandlerOptions;
|
|
401
|
+
admin?: boolean;
|
|
402
|
+
/** Called when the agent requests joining a new room mid-session. */
|
|
403
|
+
onJoinRoom?: (url: string, alias?: string) => Promise<JoinRoomResult>;
|
|
404
|
+
/** Called when the agent requests leaving a room. */
|
|
405
|
+
onLeaveRoom?: (room: string) => Promise<{
|
|
406
|
+
success: boolean;
|
|
407
|
+
error?: string;
|
|
408
|
+
}>;
|
|
409
|
+
/** Called when the agent changes its own mode. */
|
|
410
|
+
onSetMode?: (room: string, mode: string) => Promise<{
|
|
411
|
+
success: boolean;
|
|
412
|
+
error?: string;
|
|
413
|
+
}>;
|
|
414
|
+
/** Called for admin set-mode-for. */
|
|
415
|
+
onAdminSetModeFor?: (room: string, participant: string, mode: string) => Promise<{
|
|
416
|
+
success: boolean;
|
|
417
|
+
error?: string;
|
|
418
|
+
}>;
|
|
419
|
+
/** Called for admin kick. */
|
|
420
|
+
onAdminKick?: (room: string, participant: string) => Promise<{
|
|
421
|
+
success: boolean;
|
|
422
|
+
error?: string;
|
|
423
|
+
}>;
|
|
424
|
+
/** Called for admin mute (demote to observer). */
|
|
425
|
+
onAdminMute?: (room: string, participant: string) => Promise<{
|
|
426
|
+
success: boolean;
|
|
427
|
+
error?: string;
|
|
428
|
+
}>;
|
|
429
|
+
/** Called for admin unmute (restore to participant). */
|
|
430
|
+
onAdminUnmute?: (room: string, participant: string) => Promise<{
|
|
431
|
+
success: boolean;
|
|
432
|
+
error?: string;
|
|
433
|
+
}>;
|
|
434
|
+
}
|
|
435
|
+
interface RuntimeMcpServer {
|
|
436
|
+
url: string;
|
|
437
|
+
stop: () => Promise<void>;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Create a runtime MCP server on a random localhost port.
|
|
441
|
+
* Returns the URL for --mcp-config and a stop function.
|
|
442
|
+
*/
|
|
443
|
+
declare function createRuntimeMcpServer(opts: RuntimeMcpServerOptions): Promise<RuntimeMcpServer>;
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* RefMap — bidirectional map between short decimal refs and full message UUIDs.
|
|
447
|
+
*
|
|
448
|
+
* Why 4 digits: a ref like #3847 tokenizes to 1–2 tokens in most LLMs, while a
|
|
449
|
+
* raw UUID (a1b2c3d4-e5f6-...) burns 8–12 tokens for no benefit. Since refs
|
|
450
|
+
* appear on every message in transcripts and tool output, the savings compound.
|
|
451
|
+
*
|
|
452
|
+
* 10,000 unique refs per cycle is far more than an LLM context window can hold
|
|
453
|
+
* (a 200k-token window fits ~2000 messages at most). The map is cleared on every
|
|
454
|
+
* context compaction, so overflow is essentially impossible in practice. If it
|
|
455
|
+
* does happen, the fallback to a UUID-derived hash still works — just less tidy.
|
|
456
|
+
*
|
|
457
|
+
* Uses a linear congruential generator (n * 6337 mod 10000) to produce
|
|
458
|
+
* non-sequential refs — gcd(6337, 10000) = 1 guarantees a full cycle through
|
|
459
|
+
* all 10,000 values before any collision.
|
|
460
|
+
*
|
|
461
|
+
* @example
|
|
462
|
+
* const refs = new RefMap();
|
|
463
|
+
* const ref = refs.assign("msg-uuid-abc"); // "3847"
|
|
464
|
+
* refs.assign("msg-uuid-abc"); // "3847" (idempotent)
|
|
465
|
+
* refs.resolve("3847"); // "msg-uuid-abc"
|
|
466
|
+
* refs.clear(); // reset on compaction
|
|
467
|
+
*/
|
|
468
|
+
declare class RefMap {
|
|
469
|
+
private _counter;
|
|
470
|
+
private _refToId;
|
|
471
|
+
private _idToRef;
|
|
472
|
+
/** Assign a short ref to a message ID. Returns existing ref if already assigned. */
|
|
473
|
+
assign(messageId: string): string;
|
|
474
|
+
/** Resolve a ref back to the full message UUID. Returns undefined if unknown. */
|
|
475
|
+
resolve(ref: string): string | undefined;
|
|
476
|
+
/** Clear all mappings and reset the counter. Called on context compaction. */
|
|
477
|
+
clear(): void;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* RemoteRoomDataSource — HTTP-backed RoomDataSource.
|
|
482
|
+
*
|
|
483
|
+
* Implements the RoomDataSource interface by making HTTP calls to a stoop
|
|
484
|
+
* server. Used by the client-side agent runtime when connecting to remote
|
|
485
|
+
* stoops.
|
|
486
|
+
*
|
|
487
|
+
* Participant list is cached locally and updated by the agent runtime
|
|
488
|
+
* when it processes ParticipantJoined/Left events from the SSE stream.
|
|
489
|
+
*/
|
|
490
|
+
|
|
491
|
+
declare class RemoteRoomDataSource implements RoomDataSource {
|
|
492
|
+
private _serverUrl;
|
|
493
|
+
private _sessionToken;
|
|
494
|
+
private _roomId;
|
|
495
|
+
private _participants;
|
|
496
|
+
private _selfId;
|
|
497
|
+
private _selfName;
|
|
498
|
+
constructor(_serverUrl: string, _sessionToken: string, _roomId: string);
|
|
499
|
+
/** Set own identity for populating outgoing message stubs. */
|
|
500
|
+
setSelf(id: string, name: string): void;
|
|
501
|
+
get roomId(): string;
|
|
502
|
+
get serverUrl(): string;
|
|
503
|
+
get sessionToken(): string;
|
|
504
|
+
/** Set the initial participant list (from join response). */
|
|
505
|
+
setParticipants(participants: Participant[]): void;
|
|
506
|
+
/** Add a participant (on ParticipantJoined event). */
|
|
507
|
+
addParticipant(participant: Participant): void;
|
|
508
|
+
/** Remove a participant (on ParticipantLeft event). */
|
|
509
|
+
removeParticipant(participantId: string): void;
|
|
510
|
+
listParticipants(): Participant[];
|
|
511
|
+
getMessage(id: string): Promise<Message | null>;
|
|
512
|
+
searchMessages(query: string, limit?: number, cursor?: string | null): Promise<PaginatedResult<Message>>;
|
|
513
|
+
getMessages(limit?: number, cursor?: string | null): Promise<PaginatedResult<Message>>;
|
|
514
|
+
getEvents(category?: EventCategory | null, limit?: number, cursor?: string | null): Promise<PaginatedResult<RoomEvent>>;
|
|
515
|
+
sendMessage(content: string, replyToId?: string, image?: {
|
|
516
|
+
url: string;
|
|
517
|
+
mimeType: string;
|
|
518
|
+
sizeBytes: number;
|
|
519
|
+
} | null): Promise<Message>;
|
|
520
|
+
emitEvent(event: RoomEvent): Promise<void>;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* SseMultiplexer — merges N SSE connections into one labeled event stream.
|
|
525
|
+
*
|
|
526
|
+
* Each connection is an HTTP fetch to a stoop server's /events endpoint.
|
|
527
|
+
* Events are parsed from the SSE `data:` lines and wrapped as LabeledEvents
|
|
528
|
+
* (same shape as EventMultiplexer output so EventProcessor can consume either).
|
|
529
|
+
*
|
|
530
|
+
* Connections can be added/removed while the multiplexer is running.
|
|
531
|
+
* Each connection has its own AbortController for independent lifecycle.
|
|
532
|
+
*/
|
|
533
|
+
|
|
534
|
+
declare class SseMultiplexer {
|
|
535
|
+
private _queue;
|
|
536
|
+
private _waiters;
|
|
537
|
+
private _connections;
|
|
538
|
+
private _closed;
|
|
539
|
+
/**
|
|
540
|
+
* Add an SSE connection to a stoop server.
|
|
541
|
+
* Starts streaming events immediately.
|
|
542
|
+
*/
|
|
543
|
+
addConnection(serverUrl: string, sessionToken: string, roomName: string, roomId: string): void;
|
|
544
|
+
/** Remove a connection by room ID. */
|
|
545
|
+
removeConnection(roomId: string): void;
|
|
546
|
+
/** Close all connections and signal the iterator to finish. */
|
|
547
|
+
close(): void;
|
|
548
|
+
private _sseLoop;
|
|
549
|
+
private _push;
|
|
550
|
+
[Symbol.asyncIterator](): AsyncIterator<LabeledEvent>;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export { ContentPart, type EngagementMode, type EngagementStrategy, type EventDisposition, EventMultiplexer, EventProcessor, type EventProcessorOptions, type LabeledEvent, RefMap, RemoteRoomDataSource, RoomConnection, RoomDataSource, RoomResolver, type RuntimeMcpServer, type RuntimeMcpServerOptions, SseMultiplexer, StoopsEngagement, type StoopsMcpServer, ToolHandlerOptions, classifyEvent, contentPartsToString, createFullMcpServer, createRuntimeMcpServer, formatEvent, getSystemPreamble, messageRef, participantLabel };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EventMultiplexer,
|
|
3
|
+
EventProcessor,
|
|
4
|
+
LocalRoomDataSource,
|
|
5
|
+
RefMap,
|
|
6
|
+
RemoteRoomDataSource,
|
|
7
|
+
SseMultiplexer
|
|
8
|
+
} from "../chunk-OA3CODNP.js";
|
|
9
|
+
import "../chunk-5ADJGMXQ.js";
|
|
10
|
+
import {
|
|
11
|
+
createFullMcpServer
|
|
12
|
+
} from "../chunk-B4LBE5QS.js";
|
|
13
|
+
import {
|
|
14
|
+
StoopsEngagement,
|
|
15
|
+
classifyEvent,
|
|
16
|
+
contentPartsToString,
|
|
17
|
+
createRuntimeMcpServer,
|
|
18
|
+
formatEvent,
|
|
19
|
+
getSystemPreamble,
|
|
20
|
+
messageRef,
|
|
21
|
+
participantLabel
|
|
22
|
+
} from "../chunk-66EFQ2XO.js";
|
|
23
|
+
import "../chunk-EPLQQF6S.js";
|
|
24
|
+
export {
|
|
25
|
+
EventMultiplexer,
|
|
26
|
+
EventProcessor,
|
|
27
|
+
LocalRoomDataSource,
|
|
28
|
+
RefMap,
|
|
29
|
+
RemoteRoomDataSource,
|
|
30
|
+
SseMultiplexer,
|
|
31
|
+
StoopsEngagement,
|
|
32
|
+
classifyEvent,
|
|
33
|
+
contentPartsToString,
|
|
34
|
+
createFullMcpServer,
|
|
35
|
+
createRuntimeMcpServer,
|
|
36
|
+
formatEvent,
|
|
37
|
+
getSystemPreamble,
|
|
38
|
+
messageRef,
|
|
39
|
+
participantLabel
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
export {
|
|
24
|
+
EventCategory,
|
|
25
|
+
MessageSchema
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=chunk-5ADJGMXQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/types.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 * - `participant` — can send messages, set own mode\n * - `observer` — 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\" | \"participant\" | \"observer\";\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 \"participant\" 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"],"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;","names":[]}
|