stoops 0.3.1 → 0.3.3
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 +15 -4
- package/dist/agent/index.d.ts +3 -3
- package/dist/{chunk-TN56PBF3.js → chunk-XEKY3KEU.js} +62 -7
- package/dist/chunk-XEKY3KEU.js.map +1 -0
- package/dist/claude/index.d.ts +2 -2
- package/dist/cli/index.js +66 -14
- package/dist/cli/index.js.map +1 -1
- package/dist/{index-DGncuUqB.d.ts → index-DwVKKxqK.d.ts} +13 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/langgraph/index.d.ts +2 -2
- package/dist/{types-Co2KKpkh.d.ts → types-B9xf8w53.d.ts} +1 -1
- package/package.json +1 -1
- package/dist/chunk-TN56PBF3.js.map +0 -1
package/README.md
CHANGED
|
@@ -14,9 +14,11 @@ Start a server, share a link, anyone joins from their machine with their own age
|
|
|
14
14
|
|
|
15
15
|
https://github.com/user-attachments/assets/b9db9369-352e-4ff8-aea3-6497f7706879
|
|
16
16
|
|
|
17
|
-
## Try it with your
|
|
17
|
+
## Try it with your agents
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Claude and Codex collabing on a feature together!
|
|
20
|
+
|
|
21
|
+
<img width="1869" height="994" alt="Screenshot 2026-03-05 at 2 27 02 AM" src="https://github.com/user-attachments/assets/e724f6d4-ebaf-4bef-99d1-49297c81cacc" />
|
|
20
22
|
|
|
21
23
|
### Quick start (you + an agent)
|
|
22
24
|
|
|
@@ -33,8 +35,8 @@ The server starts and the chat UI opens. You'll see share links printed — copy
|
|
|
33
35
|
**Terminal 2 — launch an agent:**
|
|
34
36
|
|
|
35
37
|
```bash
|
|
36
|
-
npx stoops run claude --name
|
|
37
|
-
npx stoops run codex --name
|
|
38
|
+
npx stoops run claude --name MyClaude # Claude Code
|
|
39
|
+
npx stoops run codex --name MyCodex # OpenAI Codex
|
|
38
40
|
```
|
|
39
41
|
|
|
40
42
|
This opens the agent inside a tmux session with stoops MCP tools attached. Tell the agent:
|
|
@@ -129,6 +131,15 @@ npx stoops run claude [--name <name>] [--admin] [-- <args>] #
|
|
|
129
131
|
npx stoops run codex [--name <name>] [--admin] [-- <args>] # connect Codex as an agent
|
|
130
132
|
```
|
|
131
133
|
|
|
134
|
+
Room state is automatically saved to a temp file on every message. To pick a specific file:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npx stoops --room lobby --save lobby.json # save to a specific file
|
|
138
|
+
npx stoops --room lobby --load lobby.json # load previous session + continue saving
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Load a file and everyone who joins sees the full history.
|
|
142
|
+
|
|
132
143
|
### TUI slash commands
|
|
133
144
|
|
|
134
145
|
| Command | Who | What it does |
|
package/dist/agent/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as ContentPart, R as RoomResolver, b as RoomConnection, c as RoomDataSource, T as ToolHandlerOptions } from '../types-
|
|
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-
|
|
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-
|
|
1
|
+
import { a as ContentPart, R as RoomResolver, b as RoomConnection, c as RoomDataSource, T as ToolHandlerOptions } from '../types-B9xf8w53.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-B9xf8w53.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-DwVKKxqK.js';
|
|
4
4
|
import 'zod';
|
|
5
5
|
|
|
6
6
|
/** Event formatting and mode descriptions for stoops agents. */
|
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
} from "./chunk-PKFZHCQF.js";
|
|
6
6
|
|
|
7
7
|
// src/core/storage.ts
|
|
8
|
+
import { writeFile, readFile } from "fs/promises";
|
|
9
|
+
import { resolve } from "path";
|
|
8
10
|
function paginate(items, limit, cursor, key) {
|
|
9
11
|
let subset;
|
|
10
12
|
if (cursor != null) {
|
|
@@ -69,6 +71,58 @@ var InMemoryStorage = class {
|
|
|
69
71
|
return paginateByIndex(events, limit, cursor);
|
|
70
72
|
}
|
|
71
73
|
};
|
|
74
|
+
var FileBackedStorage = class _FileBackedStorage extends InMemoryStorage {
|
|
75
|
+
_filePath;
|
|
76
|
+
constructor(filePath) {
|
|
77
|
+
super();
|
|
78
|
+
this._filePath = resolve(filePath);
|
|
79
|
+
}
|
|
80
|
+
async addMessage(message) {
|
|
81
|
+
const result = await super.addMessage(message);
|
|
82
|
+
await this._flush();
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
async addEvent(event) {
|
|
86
|
+
await super.addEvent(event);
|
|
87
|
+
await this._flush();
|
|
88
|
+
}
|
|
89
|
+
async _flush() {
|
|
90
|
+
const data = {};
|
|
91
|
+
for (const [roomId, messages] of this._messages) {
|
|
92
|
+
if (!data[roomId]) data[roomId] = { messages: [], events: [] };
|
|
93
|
+
data[roomId].messages = messages;
|
|
94
|
+
}
|
|
95
|
+
for (const [roomId, events] of this._events) {
|
|
96
|
+
if (!data[roomId]) data[roomId] = { messages: [], events: [] };
|
|
97
|
+
data[roomId].events = events;
|
|
98
|
+
}
|
|
99
|
+
await writeFile(this._filePath, JSON.stringify(data, null, 2));
|
|
100
|
+
}
|
|
101
|
+
/** Load an existing file and return a FileBackedStorage that continues saving to it. */
|
|
102
|
+
static async load(filePath) {
|
|
103
|
+
const storage = new _FileBackedStorage(filePath);
|
|
104
|
+
const raw = await readFile(storage._filePath, "utf-8");
|
|
105
|
+
const data = JSON.parse(raw);
|
|
106
|
+
for (const [roomId, { messages, events }] of Object.entries(data)) {
|
|
107
|
+
storage._messages.set(
|
|
108
|
+
roomId,
|
|
109
|
+
messages.map((m) => ({ ...m, timestamp: new Date(m.timestamp) }))
|
|
110
|
+
);
|
|
111
|
+
storage._events.set(
|
|
112
|
+
roomId,
|
|
113
|
+
events.map((e) => rehydrateEvent(e))
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return storage;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
function rehydrateEvent(e) {
|
|
120
|
+
const event = { ...e, timestamp: new Date(e.timestamp) };
|
|
121
|
+
if ("message" in event && event.message) {
|
|
122
|
+
event.message = { ...event.message, timestamp: new Date(event.message.timestamp) };
|
|
123
|
+
}
|
|
124
|
+
return event;
|
|
125
|
+
}
|
|
72
126
|
|
|
73
127
|
// src/core/channel.ts
|
|
74
128
|
var Channel = class {
|
|
@@ -190,21 +244,21 @@ var Channel = class {
|
|
|
190
244
|
if (this._disconnected) {
|
|
191
245
|
return Promise.resolve(null);
|
|
192
246
|
}
|
|
193
|
-
return new Promise((
|
|
247
|
+
return new Promise((resolve2) => {
|
|
194
248
|
let settled = false;
|
|
195
249
|
const waiter = {
|
|
196
250
|
resolve: (event) => {
|
|
197
251
|
if (!settled) {
|
|
198
252
|
settled = true;
|
|
199
253
|
clearTimeout(timer);
|
|
200
|
-
|
|
254
|
+
resolve2(event);
|
|
201
255
|
}
|
|
202
256
|
},
|
|
203
257
|
reject: () => {
|
|
204
258
|
if (!settled) {
|
|
205
259
|
settled = true;
|
|
206
260
|
clearTimeout(timer);
|
|
207
|
-
|
|
261
|
+
resolve2(null);
|
|
208
262
|
}
|
|
209
263
|
}
|
|
210
264
|
};
|
|
@@ -214,7 +268,7 @@ var Channel = class {
|
|
|
214
268
|
settled = true;
|
|
215
269
|
const idx = this._waiters.indexOf(waiter);
|
|
216
270
|
if (idx !== -1) this._waiters.splice(idx, 1);
|
|
217
|
-
|
|
271
|
+
resolve2(null);
|
|
218
272
|
}
|
|
219
273
|
}, timeoutMs);
|
|
220
274
|
});
|
|
@@ -245,9 +299,9 @@ var Channel = class {
|
|
|
245
299
|
done: true
|
|
246
300
|
});
|
|
247
301
|
}
|
|
248
|
-
return new Promise((
|
|
302
|
+
return new Promise((resolve2, reject) => {
|
|
249
303
|
this._waiters.push({
|
|
250
|
-
resolve: (event) =>
|
|
304
|
+
resolve: (event) => resolve2({ value: event, done: false }),
|
|
251
305
|
reject
|
|
252
306
|
});
|
|
253
307
|
});
|
|
@@ -680,9 +734,10 @@ function randomName() {
|
|
|
680
734
|
|
|
681
735
|
export {
|
|
682
736
|
InMemoryStorage,
|
|
737
|
+
FileBackedStorage,
|
|
683
738
|
Channel,
|
|
684
739
|
Room,
|
|
685
740
|
randomRoomName,
|
|
686
741
|
randomName
|
|
687
742
|
};
|
|
688
|
-
//# sourceMappingURL=chunk-
|
|
743
|
+
//# sourceMappingURL=chunk-XEKY3KEU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/storage.ts","../src/core/channel.ts","../src/core/room.ts","../src/core/names.ts"],"sourcesContent":["/**\n * Storage protocol and reference implementations for stoops rooms.\n *\n * # Implementing StorageProtocol\n *\n * Provide your own implementation to persist messages and events to a real\n * database. Pass it to `new Room(roomId, myStorage)`.\n *\n * Pagination contract (applies to all paginated methods):\n * - Results are returned newest-first.\n * - `cursor` is the ID of the last item on the previous page (exclusive).\n * Pass `null` to start from the most recent.\n * - `next_cursor` in the result is the cursor to pass for the next (older) page.\n * - `has_more` is true if there are older items beyond the current page.\n *\n * @example\n * // Minimal Postgres implementation sketch:\n * class PostgresStorage implements StorageProtocol {\n * async addMessage(message) {\n * await db.query(\"INSERT INTO messages ...\", [message]);\n * return message;\n * }\n * async getMessage(room_id, message_id) {\n * return db.query(\"SELECT * FROM messages WHERE id = $1\", [message_id]);\n * }\n * async getMessages(room_id, limit = 30, cursor = null) {\n * // Fetch `limit` messages before `cursor`, newest-first\n * const rows = await db.query(\"...\");\n * return { items: rows, next_cursor: ..., has_more: ... };\n * }\n * async searchMessages(room_id, query, limit = 10, cursor = null) {\n * // Full-text search, newest-first\n * }\n * async addEvent(event) {\n * await db.query(\"INSERT INTO events ...\", [event]);\n * }\n * async getEvents(room_id, category = null, limit = 50, cursor = null) {\n * // Optional category filter, newest-first\n * }\n * }\n */\n\nimport { writeFile, readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\n\nimport type { RoomEvent } from \"./events.js\";\nimport type { EventCategory, Message, PaginatedResult } from \"./types.js\";\n\n// ── StorageProtocol ───────────────────────────────────────────────────────────\n\n/**\n * Persistence interface for a room's messages and events.\n *\n * Implement this to back rooms with a real database. The reference\n * `InMemoryStorage` is suitable for testing and single-process local use.\n *\n * All methods operate on a single `room_id` — one storage instance is shared\n * across all rooms (the `room_id` partitions the data).\n */\nexport interface StorageProtocol {\n /**\n * Persist a message and return it (with any server-assigned fields set).\n * Called automatically by `Channel.sendMessage()`.\n */\n addMessage(message: Message): Promise<Message>;\n\n /**\n * Look up a single message by ID. Returns null if not found.\n * Used by agents when resolving reply context and message refs.\n */\n getMessage(room_id: string, message_id: string): Promise<Message | null>;\n\n /**\n * Paginate messages for a room, newest-first.\n *\n * `cursor` — the `id` of the last message on the previous page (exclusive).\n * Pass null to start from the most recent message.\n */\n getMessages(\n room_id: string,\n limit?: number,\n cursor?: string | null,\n ): Promise<PaginatedResult<Message>>;\n\n /**\n * Full-text search across message content, newest-first.\n *\n * `query` — keyword or phrase to search for (case-insensitive).\n * `cursor` — pagination cursor (same semantics as `getMessages`).\n */\n searchMessages(\n room_id: string,\n query: string,\n limit?: number,\n cursor?: string | null,\n ): Promise<PaginatedResult<Message>>;\n\n /**\n * Persist a room event. Called for every event that passes through the room.\n * Events are append-only — never updated or deleted.\n */\n addEvent(event: RoomEvent): Promise<void>;\n\n /**\n * Paginate events for a room, newest-first.\n *\n * `category` — optional filter (e.g. EventCategory.MESSAGE). Pass null for all.\n * `cursor` — pagination cursor (index-based for events).\n */\n getEvents(\n room_id: string,\n category?: EventCategory | null,\n limit?: number,\n cursor?: string | null,\n ): Promise<PaginatedResult<RoomEvent>>;\n}\n\n// ── Pagination helpers (used by InMemoryStorage) ──────────────────────────────\n\n/**\n * Paginate an array by item ID cursor, returning results newest-first.\n * Items are assumed to be stored oldest-first (append order).\n *\n * @internal\n */\nexport function paginate<T>(\n items: T[],\n limit: number,\n cursor: string | null | undefined,\n key: (item: T) => string,\n): PaginatedResult<T> {\n let subset: T[];\n\n if (cursor != null) {\n const cursorIdx = items.findIndex((item) => key(item) === cursor);\n if (cursorIdx === -1) {\n return { items: [], next_cursor: null, has_more: false };\n }\n subset = items.slice(0, cursorIdx);\n } else {\n subset = items;\n }\n\n const page =\n limit < subset.length ? subset.slice(-limit) : subset.slice();\n page.reverse();\n const has_more = subset.length > limit;\n const next_cursor = has_more && page.length > 0 ? key(page[page.length - 1]) : null;\n\n return { items: page, next_cursor, has_more };\n}\n\n/**\n * Paginate an array by positional index cursor, returning results newest-first.\n * Used for events, which don't have stable IDs suitable for ID-based cursors.\n *\n * @internal\n */\nexport function paginateByIndex<T>(\n items: T[],\n limit: number,\n cursor: string | null | undefined,\n): PaginatedResult<T> {\n const parsedCursor = cursor != null ? parseInt(cursor, 10) : items.length;\n const endIdx = Number.isNaN(parsedCursor) ? items.length : parsedCursor;\n const startIdx = Math.max(0, endIdx - limit);\n const page = items.slice(startIdx, endIdx).reverse();\n const has_more = startIdx > 0;\n const next_cursor = has_more ? String(startIdx) : null;\n\n return { items: page, next_cursor, has_more };\n}\n\n// ── InMemoryStorage ───────────────────────────────────────────────────────────\n\n/**\n * Reference in-memory implementation of `StorageProtocol`.\n *\n * Suitable for tests, development, and single-process local use. All data is\n * lost on process restart — not for production.\n *\n * One instance can serve multiple rooms (data is partitioned by `room_id`).\n */\nexport class InMemoryStorage implements StorageProtocol {\n protected _messages = new Map<string, Message[]>();\n protected _events = new Map<string, RoomEvent[]>();\n\n async addMessage(message: Message): Promise<Message> {\n const list = this._messages.get(message.room_id) ?? [];\n list.push(message);\n this._messages.set(message.room_id, list);\n return message;\n }\n\n async getMessage(room_id: string, message_id: string): Promise<Message | null> {\n const list = this._messages.get(room_id) ?? [];\n return list.find((m) => m.id === message_id) ?? null;\n }\n\n async getMessages(\n room_id: string,\n limit = 30,\n cursor: string | null = null,\n ): Promise<PaginatedResult<Message>> {\n const messages = this._messages.get(room_id) ?? [];\n return paginate(messages, limit, cursor, (m) => m.id);\n }\n\n async searchMessages(\n room_id: string,\n query: string,\n limit = 10,\n cursor: string | null = null,\n ): Promise<PaginatedResult<Message>> {\n const messages = this._messages.get(room_id) ?? [];\n const q = query.toLowerCase();\n const filtered = messages.filter((m) =>\n m.content.toLowerCase().includes(q),\n );\n return paginate(filtered, limit, cursor, (m) => m.id);\n }\n\n async addEvent(event: RoomEvent): Promise<void> {\n const list = this._events.get(event.room_id) ?? [];\n list.push(event);\n this._events.set(event.room_id, list);\n }\n\n async getEvents(\n room_id: string,\n category: EventCategory | null = null,\n limit = 50,\n cursor: string | null = null,\n ): Promise<PaginatedResult<RoomEvent>> {\n let events = this._events.get(room_id) ?? [];\n if (category != null) {\n events = events.filter((e) => e.category === category);\n }\n return paginateByIndex(events, limit, cursor);\n }\n}\n\n// ── FileBackedStorage ─────────────────────────────────────────────────────────\n\n/**\n * In-memory storage that persists to a JSON file on every write.\n *\n * Use `FileBackedStorage.load(path)` to restore from an existing file,\n * or `new FileBackedStorage(path)` to start fresh and save to that path.\n */\nexport class FileBackedStorage extends InMemoryStorage {\n private _filePath: string;\n\n constructor(filePath: string) {\n super();\n this._filePath = resolve(filePath);\n }\n\n async addMessage(message: Message): Promise<Message> {\n const result = await super.addMessage(message);\n await this._flush();\n return result;\n }\n\n async addEvent(event: RoomEvent): Promise<void> {\n await super.addEvent(event);\n await this._flush();\n }\n\n private async _flush(): Promise<void> {\n const data: Record<string, { messages: Message[]; events: RoomEvent[] }> = {};\n for (const [roomId, messages] of this._messages) {\n if (!data[roomId]) data[roomId] = { messages: [], events: [] };\n data[roomId].messages = messages;\n }\n for (const [roomId, events] of this._events) {\n if (!data[roomId]) data[roomId] = { messages: [], events: [] };\n data[roomId].events = events;\n }\n await writeFile(this._filePath, JSON.stringify(data, null, 2));\n }\n\n /** Load an existing file and return a FileBackedStorage that continues saving to it. */\n static async load(filePath: string): Promise<FileBackedStorage> {\n const storage = new FileBackedStorage(filePath);\n const raw = await readFile(storage._filePath, \"utf-8\");\n const data = JSON.parse(raw) as Record<string, { messages: Message[]; events: RoomEvent[] }>;\n\n for (const [roomId, { messages, events }] of Object.entries(data)) {\n storage._messages.set(\n roomId,\n messages.map((m) => ({ ...m, timestamp: new Date(m.timestamp) })),\n );\n storage._events.set(\n roomId,\n events.map((e) => rehydrateEvent(e)),\n );\n }\n\n return storage;\n }\n}\n\n/** Rehydrate Date fields that became ISO strings during JSON serialization. */\nfunction rehydrateEvent(e: RoomEvent): RoomEvent {\n const event = { ...e, timestamp: new Date(e.timestamp) } as RoomEvent;\n // Events that embed a full Message need their nested timestamp rehydrated too\n if (\"message\" in event && event.message) {\n event.message = { ...event.message, timestamp: new Date(event.message.timestamp) };\n }\n return event;\n}\n","/**\n * Channel — a participant's bidirectional connection to a room.\n *\n * Created by `Room.connect()`. Never instantiated directly.\n *\n * # Sending\n * - `sendMessage()` — persist and broadcast a chat message\n * - `emit()` — push non-message events (tool use, mode changes, etc.)\n *\n * # Receiving\n * Channels are async-iterable — use `for await (const event of channel)` to\n * consume events. Only events in the channel's `subscriptions` set are\n * delivered. Alternatively, use `receive(timeoutMs)` for polling with a\n * timeout (used by EventMultiplexer).\n *\n * # Lifecycle\n * - `updateSubscriptions()` — change which EventCategories are delivered\n * - `disconnect(silent?)` — leave the room; pass `true` to suppress the\n * ParticipantLeft broadcast\n */\n\nimport type { RoomEvent } from \"./events.js\";\nimport type { EventCategory, Message } from \"./types.js\";\nimport { MessageSchema } from \"./types.js\";\nimport type { Room } from \"./room.js\";\n\ninterface Waiter {\n resolve: (event: RoomEvent) => void;\n reject: (err: Error) => void;\n}\n\nexport class Channel {\n readonly participantId: string;\n readonly participantName: string;\n subscriptions: Set<EventCategory>;\n\n private _room: Room;\n private _queue: RoomEvent[] = [];\n private _waiters: Waiter[] = [];\n private _disconnected = false;\n\n constructor(\n room: Room,\n participantId: string,\n participantName: string,\n subscriptions: Set<EventCategory>,\n ) {\n this._room = room;\n this.participantId = participantId;\n this.participantName = participantName;\n this.subscriptions = subscriptions;\n }\n\n get roomId(): string {\n return this._room.roomId;\n }\n\n /**\n * Send a chat message from this participant.\n *\n * Persists the message to storage, broadcasts a `MessageSentEvent` to all\n * participants (including the sender), and fires `MentionedEvent` for any\n * `@name` or `@identifier` patterns found in the content.\n *\n * @param content — message text (may be empty if image is provided)\n * @param replyToId — ID of the message being replied to (optional)\n * @param image — optional image attachment\n */\n async sendMessage(\n content: string,\n replyToId?: string | null,\n image?: {\n url: string;\n mimeType: string;\n sizeBytes: number;\n } | null,\n ): Promise<Message> {\n if (this._disconnected) {\n throw new Error(\"Channel is disconnected\");\n }\n const message = MessageSchema.parse({\n room_id: this._room.roomId,\n sender_id: this.participantId,\n sender_name: this.participantName,\n content,\n reply_to_id: replyToId ?? null,\n image_url: image?.url ?? null,\n image_mime_type: image?.mimeType ?? null,\n image_size_bytes: image?.sizeBytes ?? null,\n });\n await this._room._handleMessage(message);\n return message;\n }\n\n /**\n * Emit a non-message activity event to the room.\n *\n * Use this for platform events: tool use indicators, mode changes, compaction\n * notices, etc. The event is persisted and broadcast to all subscribed\n * participants.\n */\n async emit(event: RoomEvent): Promise<void> {\n if (this._disconnected) {\n throw new Error(\"Channel is disconnected\");\n }\n await this._room._handleEvent(event);\n }\n\n /**\n * Change which event categories this channel receives.\n * Takes effect immediately — buffered events from unsubscribed categories\n * are not retroactively removed.\n */\n updateSubscriptions(categories: Set<EventCategory>): void {\n this.subscriptions = categories;\n }\n\n /**\n * Leave the room.\n *\n * @param silent — if true, suppresses the `ParticipantLeft` broadcast.\n * Agents disconnect silently to avoid chat noise.\n */\n async disconnect(silent = false): Promise<void> {\n if (!this._disconnected) {\n this._disconnected = true;\n // Wake pending waiters so async iterators exit cleanly\n const waiters = this._waiters;\n this._waiters = [];\n for (const w of waiters) {\n w.reject(new Error(\"Channel disconnected\"));\n }\n await this._room._disconnectChannel(this, silent);\n }\n }\n\n /** @internal Called by Room to mark this channel as disconnected without removing from room maps. */\n _markDisconnected(): void {\n if (!this._disconnected) {\n this._disconnected = true;\n const waiters = this._waiters;\n this._waiters = [];\n for (const w of waiters) {\n w.reject(new Error(\"Channel disconnected\"));\n }\n }\n }\n\n /** @internal Called by Room to deliver an incoming event. Filters by subscription. */\n _deliver(event: RoomEvent): void {\n if (this._disconnected) return;\n if (!this.subscriptions.has(event.category)) return;\n\n if (this._waiters.length > 0) {\n const waiter = this._waiters.shift()!;\n waiter.resolve(event);\n } else {\n this._queue.push(event);\n }\n }\n\n /**\n * Receive the next event, waiting up to `timeoutMs`.\n *\n * Returns null if no event arrives within the timeout. Drains buffered events\n * before waiting. Used by `EventMultiplexer` to fan-in events from multiple\n * rooms into a single stream.\n */\n receive(timeoutMs: number): Promise<RoomEvent | null> {\n if (this._queue.length > 0) {\n return Promise.resolve(this._queue.shift()!);\n }\n if (this._disconnected) {\n return Promise.resolve(null);\n }\n\n return new Promise<RoomEvent | null>((resolve) => {\n let settled = false;\n const waiter: Waiter = {\n resolve: (event) => {\n if (!settled) {\n settled = true;\n clearTimeout(timer);\n resolve(event);\n }\n },\n reject: () => {\n if (!settled) {\n settled = true;\n clearTimeout(timer);\n resolve(null);\n }\n },\n };\n this._waiters.push(waiter);\n\n const timer = setTimeout(() => {\n if (!settled) {\n settled = true;\n const idx = this._waiters.indexOf(waiter);\n if (idx !== -1) this._waiters.splice(idx, 1);\n resolve(null);\n }\n }, timeoutMs);\n });\n }\n\n /**\n * Async iterator — yields events as they arrive.\n *\n * Used by `EventMultiplexer` to fan-in all room channels into a single stream.\n * The iterator completes when the channel is disconnected.\n *\n * @example\n * for await (const event of channel) {\n * console.log(event.type);\n * }\n */\n [Symbol.asyncIterator](): AsyncIterator<RoomEvent> {\n return {\n next: (): Promise<IteratorResult<RoomEvent>> => {\n if (this._queue.length > 0) {\n return Promise.resolve({\n value: this._queue.shift()!,\n done: false,\n });\n }\n\n if (this._disconnected) {\n return Promise.resolve({\n value: undefined as unknown as RoomEvent,\n done: true,\n });\n }\n\n return new Promise<IteratorResult<RoomEvent>>((resolve, reject) => {\n this._waiters.push({\n resolve: (event) => resolve({ value: event, done: false }),\n reject,\n });\n });\n },\n };\n }\n}\n","/**\n * Room — a shared chat space where humans and agents are all just participants.\n *\n * Transport-agnostic: no WebSockets, no HTTP. The caller owns the transport\n * and passes messages/events in via channels. This means the same Room works\n * identically in a CLI, a web server, or a test.\n *\n * # Connecting\n * Participants connect via `room.connect()`, which returns a `Channel`. The\n * channel is their bidirectional connection: they send messages and receive\n * events through it.\n *\n * # Observing\n * Call `room.observe()` to get a read-only-style channel that receives every\n * event in the room — including targeted @mention events directed at other\n * participants. Observers are NOT participants: they don't appear in\n * `listParticipants()` and don't trigger join/leave events.\n *\n * # @mention detection\n * When a message is sent, the Room scans its content for `@token` patterns and\n * fires a `MentionedEvent` for any participant whose `identifier` or display\n * `name` matches the token (case-insensitive). The mention event is delivered\n * to the mentioned participant AND to all observers.\n *\n * @example\n * const storage = new InMemoryStorage();\n * const room = new Room(\"room-1\", storage);\n *\n * const aliceChannel = await room.connect(\"alice-id\", \"Alice\");\n * const agentChannel = await room.connect(\"agent-id\", \"Agent\", \"agent\", \"my-agent\");\n * const observer = room.observe();\n *\n * await aliceChannel.sendMessage(\"hey @my-agent what do you think?\");\n * // → MessageSentEvent broadcast to all participants + observer\n * // → MentionedEvent delivered to agentChannel + observer\n */\n\nimport { Channel } from \"./channel.js\";\nimport { createEvent } from \"./events.js\";\nimport type {\n MentionedEvent,\n MessageSentEvent,\n ParticipantJoinedEvent,\n ParticipantLeftEvent,\n RoomEvent,\n} from \"./events.js\";\nimport { InMemoryStorage, type StorageProtocol } from \"./storage.js\";\nimport { EventCategory, type AuthorityLevel, type Message, type PaginatedResult, type Participant, type ParticipantType } from \"./types.js\";\n\nconst ALL_CATEGORIES = new Set<EventCategory>([\n EventCategory.MESSAGE,\n EventCategory.PRESENCE,\n EventCategory.ACTIVITY,\n EventCategory.MENTION,\n]);\n\nexport class Room {\n readonly roomId: string;\n /** Direct access to the underlying storage. Useful for bulk reads. */\n readonly storage: StorageProtocol;\n private _channels = new Map<string, Channel>();\n private _participants = new Map<string, Participant>();\n private _observers = new Set<Channel>();\n private _nextObserverId = 0;\n\n /**\n * @param roomId — stable identifier for this room (e.g. a UUID or slug)\n * @param storage — storage backend; defaults to `InMemoryStorage`\n */\n constructor(roomId: string, storage?: StorageProtocol) {\n this.roomId = roomId;\n this.storage = storage ?? new InMemoryStorage();\n }\n\n /**\n * Connect a participant and return their channel.\n */\n async connect(\n participantId: string,\n name: string,\n options?: {\n type?: ParticipantType;\n identifier?: string;\n subscribe?: Set<EventCategory>;\n silent?: boolean;\n authority?: AuthorityLevel;\n },\n ): Promise<Channel> {\n const type = options?.type ?? \"human\";\n const identifier = options?.identifier;\n const subscribe = options?.subscribe;\n const silent = options?.silent ?? false;\n const authority = options?.authority;\n const participant: Participant = {\n id: participantId, name, status: \"online\", type,\n ...(identifier ? { identifier } : {}),\n ...(authority ? { authority } : {}),\n };\n this._participants.set(participantId, participant);\n\n // If already connected, disconnect the old channel first\n const existingChannel = this._channels.get(participantId);\n if (existingChannel) {\n existingChannel._markDisconnected();\n }\n\n const subscriptions = subscribe ?? new Set(ALL_CATEGORIES);\n const channel = new Channel(this, participantId, name, subscriptions);\n this._channels.set(participantId, channel);\n\n if (!silent) {\n const event = createEvent<ParticipantJoinedEvent>({\n type: \"ParticipantJoined\",\n category: \"PRESENCE\",\n room_id: this.roomId,\n participant_id: participantId,\n participant,\n });\n await this._storeAndBroadcast(event, participantId);\n }\n\n return channel;\n }\n\n /**\n * Observe all room events without being a participant.\n *\n * Returns a channel that receives every event — broadcasts AND targeted\n * @mention events directed at other participants. Observers do NOT appear\n * in `listParticipants()` and do not emit join/leave presence events,\n * since they are not participants.\n *\n * Disconnect via `observer.disconnect()` when done.\n *\n * @example\n * const observer = room.observe();\n * for await (const event of observer) {\n * // sees everything, including mentions for other participants\n * }\n */\n observe(): Channel {\n const id = `__obs_${this.roomId}_${this._nextObserverId++}`;\n const channel = new Channel(this, id, \"__observer__\", new Set(ALL_CATEGORIES));\n this._observers.add(channel);\n return channel;\n }\n\n // ── Read methods ───────────────────────────────────────────────────────────\n\n /**\n * Paginate messages, newest-first. Pass the returned `next_cursor` to get\n * the next (older) page.\n */\n async listMessages(\n limit = 30,\n cursor: string | null = null,\n ): Promise<PaginatedResult<Message>> {\n return this.storage.getMessages(this.roomId, limit, cursor);\n }\n\n /**\n * Full-text search across message content, newest-first.\n * `query` is matched case-insensitively against message content.\n */\n async searchMessages(\n query: string,\n limit = 10,\n cursor: string | null = null,\n ): Promise<PaginatedResult<Message>> {\n return this.storage.searchMessages(this.roomId, query, limit, cursor);\n }\n\n /** All currently connected participants (including agents). Observers excluded. */\n listParticipants(): Participant[] {\n return [...this._participants.values()];\n }\n\n /**\n * Paginate room events, newest-first.\n * `category` optionally filters to one EventCategory.\n */\n async listEvents(\n category: EventCategory | null = null,\n limit = 50,\n cursor: string | null = null,\n ): Promise<PaginatedResult<RoomEvent>> {\n return this.storage.getEvents(this.roomId, category, limit, cursor);\n }\n\n /** Look up a single message by ID. Returns null if not found. */\n async getMessage(id: string): Promise<Message | null> {\n return this.storage.getMessage(this.roomId, id);\n }\n\n /** Update a participant's authority level at runtime. */\n setParticipantAuthority(participantId: string, authority: AuthorityLevel): boolean {\n const participant = this._participants.get(participantId);\n if (!participant) return false;\n participant.authority = authority;\n return true;\n }\n\n // ── Internal methods (called by Channel) ──────────────────────────────────\n\n /**\n * @internal\n * Store a message, broadcast MessageSentEvent, and fire MentionedEvents.\n *\n * @mention scanning: looks for `@token` patterns in content and matches\n * against each connected participant's `identifier` and display `name`\n * (case-insensitive). Fires a `MentionedEvent` for each match, delivered\n * to the mentioned participant AND all observers.\n */\n async _handleMessage(message: Message): Promise<void> {\n await this.storage.addMessage(message);\n\n const event = createEvent<MessageSentEvent>({\n type: \"MessageSent\",\n category: \"MESSAGE\",\n room_id: this.roomId,\n participant_id: message.sender_id,\n message,\n });\n await this._storeAndBroadcast(event);\n\n const mentions = this._detectMentions(message.content);\n for (const mentionedId of mentions) {\n const ch = this._channels.get(mentionedId);\n if (ch) {\n const mentionEvent = createEvent<MentionedEvent>({\n type: \"Mentioned\",\n category: \"MENTION\",\n room_id: this.roomId,\n participant_id: mentionedId,\n message,\n });\n await this.storage.addEvent(mentionEvent);\n ch._deliver(mentionEvent);\n // Deliver mentions to all observers too\n for (const observer of this._observers) {\n observer._deliver(mentionEvent);\n }\n }\n }\n }\n\n /** @internal Store and broadcast an activity event. */\n async _handleEvent(event: RoomEvent): Promise<void> {\n await this._storeAndBroadcast(event, event.participant_id);\n }\n\n /** @internal Remove a channel and optionally broadcast ParticipantLeftEvent. */\n async _disconnectChannel(channel: Channel, silent = false): Promise<void> {\n // Observer channels are not participants — just remove from observer set\n if (this._observers.delete(channel)) {\n return;\n }\n\n const pid = channel.participantId;\n const participant = this._participants.get(pid);\n this._channels.delete(pid);\n this._participants.delete(pid);\n\n if (!silent && participant) {\n const event = createEvent<ParticipantLeftEvent>({\n type: \"ParticipantLeft\",\n category: \"PRESENCE\",\n room_id: this.roomId,\n participant_id: pid,\n participant,\n });\n await this._storeAndBroadcast(event);\n }\n }\n\n private async _storeAndBroadcast(\n event: RoomEvent,\n exclude?: string,\n ): Promise<void> {\n await this.storage.addEvent(event);\n this._broadcast(event, exclude);\n }\n\n private _broadcast(event: RoomEvent, exclude?: string): void {\n for (const [pid, channel] of this._channels) {\n if (pid !== exclude) {\n channel._deliver(event);\n }\n }\n for (const observer of this._observers) {\n observer._deliver(event);\n }\n }\n\n /**\n * Scan message content for `@token` patterns and return matching participant IDs.\n * Matches against both `identifier` (e.g. `@my-agent`) and display `name` (e.g. `@Alice`).\n * Case-insensitive. Deduplicates — each participant appears at most once.\n */\n private _detectMentions(content: string): string[] {\n const mentionedIds: string[] = [];\n const pattern = /@([a-zA-Z0-9_-]+)/g;\n let match;\n while ((match = pattern.exec(content)) !== null) {\n const token = match[1].toLowerCase();\n for (const [pid, participant] of this._participants) {\n const matchesId = participant.identifier?.toLowerCase() === token;\n const matchesName = participant.name.toLowerCase() === token;\n if ((matchesId || matchesName) && !mentionedIds.includes(pid)) {\n mentionedIds.push(pid);\n }\n }\n }\n return mentionedIds;\n }\n}\n","/** Random display name generation for participants and rooms. */\n\nconst PLACES = [\n \"bay\", \"cove\", \"glen\", \"moor\", \"fjord\", \"cape\", \"crag\", \"bluff\", \"cliff\", \"ridge\",\n \"peak\", \"mesa\", \"butte\", \"canyon\", \"gorge\", \"ravine\", \"gulch\", \"dell\", \"dune\", \"plain\",\n \"heath\", \"fell\", \"bog\", \"marsh\", \"pond\", \"lake\", \"tarn\", \"pool\", \"harbor\", \"haven\",\n \"inlet\", \"gulf\", \"sound\", \"strait\", \"channel\", \"delta\", \"lagoon\", \"atoll\", \"shoal\", \"shore\",\n \"coast\", \"isle\", \"forest\", \"grove\", \"copse\", \"glade\", \"meadow\", \"field\", \"valley\", \"hollow\",\n \"nook\", \"ford\", \"falls\", \"spring\", \"well\", \"crest\", \"knoll\", \"summit\", \"slope\", \"basin\",\n \"bank\", \"strand\", \"loch\", \"steppe\", \"tundra\", \"prairie\", \"savanna\", \"jungle\", \"desert\",\n \"highland\", \"estuary\", \"bight\", \"spit\", \"islet\", \"island\", \"tor\", \"vale\", \"brook\", \"creek\",\n \"river\", \"weir\", \"cascade\", \"scarp\", \"tower\", \"plateau\", \"upland\", \"lowland\",\n];\n\n/** Generate a random room name like \"Glen-4827\". */\nexport function randomRoomName(): string {\n const place = PLACES[Math.floor(Math.random() * PLACES.length)];\n const digits = String(Math.floor(Math.random() * 9000) + 1000);\n return `${place[0].toUpperCase()}${place.slice(1)}-${digits}`;\n}\n\nconst NAMES = [\n \"ash\", \"kai\", \"sol\", \"pip\", \"kit\", \"zev\", \"bly\", \"rue\", \"dex\", \"nix\",\n \"wren\", \"gray\", \"clay\", \"reed\", \"roux\", \"roan\", \"jade\", \"max\", \"val\", \"xen\",\n \"zen\", \"pax\", \"jude\", \"finn\", \"sage\", \"remy\", \"nico\", \"noel\", \"lumi\", \"jules\",\n \"hero\", \"eden\", \"blake\", \"bram\", \"clem\", \"flint\", \"nox\", \"oak\", \"moss\", \"bryn\",\n \"lyra\", \"mars\", \"neve\", \"onyx\", \"sable\", \"thea\", \"koa\", \"ren\", \"ora\", \"lev\",\n \"tru\", \"vox\", \"quinn\", \"rowan\", \"avery\", \"cass\", \"greer\", \"holt\", \"arlo\", \"drew\",\n \"emery\", \"finley\", \"harley\", \"harper\", \"jamie\", \"vesper\", \"west\", \"wynne\", \"yael\",\n \"zion\", \"sawyer\", \"scout\", \"tatum\", \"toby\", \"toni\", \"riley\", \"reese\", \"morgan\",\n \"micah\", \"logan\", \"lane\", \"jordan\", \"perry\", \"piper\", \"erin\", \"dylan\", \"camden\",\n \"seren\", \"elio\", \"cael\", \"davi\", \"lyric\", \"kiran\", \"arrow\", \"riven\", \"cleo\",\n \"sora\", \"tae\", \"cade\", \"milo\",\n];\n\n/** Generate a random display name like \"Wren-4827\". */\nexport function randomName(): string {\n const name = NAMES[Math.floor(Math.random() * NAMES.length)];\n const digits = String(Math.floor(Math.random() * 9000) + 1000);\n return `${name[0].toUpperCase()}${name.slice(1)}-${digits}`;\n}\n"],"mappings":";;;;;;;AA0CA,SAAS,WAAW,gBAAgB;AACpC,SAAS,eAAe;AAkFjB,SAAS,SACd,OACA,OACA,QACA,KACoB;AACpB,MAAI;AAEJ,MAAI,UAAU,MAAM;AAClB,UAAM,YAAY,MAAM,UAAU,CAAC,SAAS,IAAI,IAAI,MAAM,MAAM;AAChE,QAAI,cAAc,IAAI;AACpB,aAAO,EAAE,OAAO,CAAC,GAAG,aAAa,MAAM,UAAU,MAAM;AAAA,IACzD;AACA,aAAS,MAAM,MAAM,GAAG,SAAS;AAAA,EACnC,OAAO;AACL,aAAS;AAAA,EACX;AAEA,QAAM,OACJ,QAAQ,OAAO,SAAS,OAAO,MAAM,CAAC,KAAK,IAAI,OAAO,MAAM;AAC9D,OAAK,QAAQ;AACb,QAAM,WAAW,OAAO,SAAS;AACjC,QAAM,cAAc,YAAY,KAAK,SAAS,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,IAAI;AAE/E,SAAO,EAAE,OAAO,MAAM,aAAa,SAAS;AAC9C;AAQO,SAAS,gBACd,OACA,OACA,QACoB;AACpB,QAAM,eAAe,UAAU,OAAO,SAAS,QAAQ,EAAE,IAAI,MAAM;AACnE,QAAM,SAAS,OAAO,MAAM,YAAY,IAAI,MAAM,SAAS;AAC3D,QAAM,WAAW,KAAK,IAAI,GAAG,SAAS,KAAK;AAC3C,QAAM,OAAO,MAAM,MAAM,UAAU,MAAM,EAAE,QAAQ;AACnD,QAAM,WAAW,WAAW;AAC5B,QAAM,cAAc,WAAW,OAAO,QAAQ,IAAI;AAElD,SAAO,EAAE,OAAO,MAAM,aAAa,SAAS;AAC9C;AAYO,IAAM,kBAAN,MAAiD;AAAA,EAC5C,YAAY,oBAAI,IAAuB;AAAA,EACvC,UAAU,oBAAI,IAAyB;AAAA,EAEjD,MAAM,WAAW,SAAoC;AACnD,UAAM,OAAO,KAAK,UAAU,IAAI,QAAQ,OAAO,KAAK,CAAC;AACrD,SAAK,KAAK,OAAO;AACjB,SAAK,UAAU,IAAI,QAAQ,SAAS,IAAI;AACxC,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,WAAW,SAAiB,YAA6C;AAC7E,UAAM,OAAO,KAAK,UAAU,IAAI,OAAO,KAAK,CAAC;AAC7C,WAAO,KAAK,KAAK,CAAC,MAAM,EAAE,OAAO,UAAU,KAAK;AAAA,EAClD;AAAA,EAEA,MAAM,YACJ,SACA,QAAQ,IACR,SAAwB,MACW;AACnC,UAAM,WAAW,KAAK,UAAU,IAAI,OAAO,KAAK,CAAC;AACjD,WAAO,SAAS,UAAU,OAAO,QAAQ,CAAC,MAAM,EAAE,EAAE;AAAA,EACtD;AAAA,EAEA,MAAM,eACJ,SACA,OACA,QAAQ,IACR,SAAwB,MACW;AACnC,UAAM,WAAW,KAAK,UAAU,IAAI,OAAO,KAAK,CAAC;AACjD,UAAM,IAAI,MAAM,YAAY;AAC5B,UAAM,WAAW,SAAS;AAAA,MAAO,CAAC,MAChC,EAAE,QAAQ,YAAY,EAAE,SAAS,CAAC;AAAA,IACpC;AACA,WAAO,SAAS,UAAU,OAAO,QAAQ,CAAC,MAAM,EAAE,EAAE;AAAA,EACtD;AAAA,EAEA,MAAM,SAAS,OAAiC;AAC9C,UAAM,OAAO,KAAK,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AACjD,SAAK,KAAK,KAAK;AACf,SAAK,QAAQ,IAAI,MAAM,SAAS,IAAI;AAAA,EACtC;AAAA,EAEA,MAAM,UACJ,SACA,WAAiC,MACjC,QAAQ,IACR,SAAwB,MACa;AACrC,QAAI,SAAS,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC;AAC3C,QAAI,YAAY,MAAM;AACpB,eAAS,OAAO,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ;AAAA,IACvD;AACA,WAAO,gBAAgB,QAAQ,OAAO,MAAM;AAAA,EAC9C;AACF;AAUO,IAAM,oBAAN,MAAM,2BAA0B,gBAAgB;AAAA,EAC7C;AAAA,EAER,YAAY,UAAkB;AAC5B,UAAM;AACN,SAAK,YAAY,QAAQ,QAAQ;AAAA,EACnC;AAAA,EAEA,MAAM,WAAW,SAAoC;AACnD,UAAM,SAAS,MAAM,MAAM,WAAW,OAAO;AAC7C,UAAM,KAAK,OAAO;AAClB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,SAAS,OAAiC;AAC9C,UAAM,MAAM,SAAS,KAAK;AAC1B,UAAM,KAAK,OAAO;AAAA,EACpB;AAAA,EAEA,MAAc,SAAwB;AACpC,UAAM,OAAqE,CAAC;AAC5E,eAAW,CAAC,QAAQ,QAAQ,KAAK,KAAK,WAAW;AAC/C,UAAI,CAAC,KAAK,MAAM,EAAG,MAAK,MAAM,IAAI,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,EAAE;AAC7D,WAAK,MAAM,EAAE,WAAW;AAAA,IAC1B;AACA,eAAW,CAAC,QAAQ,MAAM,KAAK,KAAK,SAAS;AAC3C,UAAI,CAAC,KAAK,MAAM,EAAG,MAAK,MAAM,IAAI,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,EAAE;AAC7D,WAAK,MAAM,EAAE,SAAS;AAAA,IACxB;AACA,UAAM,UAAU,KAAK,WAAW,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,EAC/D;AAAA;AAAA,EAGA,aAAa,KAAK,UAA8C;AAC9D,UAAM,UAAU,IAAI,mBAAkB,QAAQ;AAC9C,UAAM,MAAM,MAAM,SAAS,QAAQ,WAAW,OAAO;AACrD,UAAM,OAAO,KAAK,MAAM,GAAG;AAE3B,eAAW,CAAC,QAAQ,EAAE,UAAU,OAAO,CAAC,KAAK,OAAO,QAAQ,IAAI,GAAG;AACjE,cAAQ,UAAU;AAAA,QAChB;AAAA,QACA,SAAS,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,WAAW,IAAI,KAAK,EAAE,SAAS,EAAE,EAAE;AAAA,MAClE;AACA,cAAQ,QAAQ;AAAA,QACd;AAAA,QACA,OAAO,IAAI,CAAC,MAAM,eAAe,CAAC,CAAC;AAAA,MACrC;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AAGA,SAAS,eAAe,GAAyB;AAC/C,QAAM,QAAQ,EAAE,GAAG,GAAG,WAAW,IAAI,KAAK,EAAE,SAAS,EAAE;AAEvD,MAAI,aAAa,SAAS,MAAM,SAAS;AACvC,UAAM,UAAU,EAAE,GAAG,MAAM,SAAS,WAAW,IAAI,KAAK,MAAM,QAAQ,SAAS,EAAE;AAAA,EACnF;AACA,SAAO;AACT;;;ACxRO,IAAM,UAAN,MAAc;AAAA,EACV;AAAA,EACA;AAAA,EACT;AAAA,EAEQ;AAAA,EACA,SAAsB,CAAC;AAAA,EACvB,WAAqB,CAAC;AAAA,EACtB,gBAAgB;AAAA,EAExB,YACE,MACA,eACA,iBACA,eACA;AACA,SAAK,QAAQ;AACb,SAAK,gBAAgB;AACrB,SAAK,kBAAkB;AACvB,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,IAAI,SAAiB;AACnB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,YACJ,SACA,WACA,OAKkB;AAClB,QAAI,KAAK,eAAe;AACtB,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AACA,UAAM,UAAU,cAAc,MAAM;AAAA,MAClC,SAAS,KAAK,MAAM;AAAA,MACpB,WAAW,KAAK;AAAA,MAChB,aAAa,KAAK;AAAA,MAClB;AAAA,MACA,aAAa,aAAa;AAAA,MAC1B,WAAW,OAAO,OAAO;AAAA,MACzB,iBAAiB,OAAO,YAAY;AAAA,MACpC,kBAAkB,OAAO,aAAa;AAAA,IACxC,CAAC;AACD,UAAM,KAAK,MAAM,eAAe,OAAO;AACvC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,KAAK,OAAiC;AAC1C,QAAI,KAAK,eAAe;AACtB,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AACA,UAAM,KAAK,MAAM,aAAa,KAAK;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,oBAAoB,YAAsC;AACxD,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,WAAW,SAAS,OAAsB;AAC9C,QAAI,CAAC,KAAK,eAAe;AACvB,WAAK,gBAAgB;AAErB,YAAM,UAAU,KAAK;AACrB,WAAK,WAAW,CAAC;AACjB,iBAAW,KAAK,SAAS;AACvB,UAAE,OAAO,IAAI,MAAM,sBAAsB,CAAC;AAAA,MAC5C;AACA,YAAM,KAAK,MAAM,mBAAmB,MAAM,MAAM;AAAA,IAClD;AAAA,EACF;AAAA;AAAA,EAGA,oBAA0B;AACxB,QAAI,CAAC,KAAK,eAAe;AACvB,WAAK,gBAAgB;AACrB,YAAM,UAAU,KAAK;AACrB,WAAK,WAAW,CAAC;AACjB,iBAAW,KAAK,SAAS;AACvB,UAAE,OAAO,IAAI,MAAM,sBAAsB,CAAC;AAAA,MAC5C;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,SAAS,OAAwB;AAC/B,QAAI,KAAK,cAAe;AACxB,QAAI,CAAC,KAAK,cAAc,IAAI,MAAM,QAAQ,EAAG;AAE7C,QAAI,KAAK,SAAS,SAAS,GAAG;AAC5B,YAAM,SAAS,KAAK,SAAS,MAAM;AACnC,aAAO,QAAQ,KAAK;AAAA,IACtB,OAAO;AACL,WAAK,OAAO,KAAK,KAAK;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,QAAQ,WAA8C;AACpD,QAAI,KAAK,OAAO,SAAS,GAAG;AAC1B,aAAO,QAAQ,QAAQ,KAAK,OAAO,MAAM,CAAE;AAAA,IAC7C;AACA,QAAI,KAAK,eAAe;AACtB,aAAO,QAAQ,QAAQ,IAAI;AAAA,IAC7B;AAEA,WAAO,IAAI,QAA0B,CAACA,aAAY;AAChD,UAAI,UAAU;AACd,YAAM,SAAiB;AAAA,QACrB,SAAS,CAAC,UAAU;AAClB,cAAI,CAAC,SAAS;AACZ,sBAAU;AACV,yBAAa,KAAK;AAClB,YAAAA,SAAQ,KAAK;AAAA,UACf;AAAA,QACF;AAAA,QACA,QAAQ,MAAM;AACZ,cAAI,CAAC,SAAS;AACZ,sBAAU;AACV,yBAAa,KAAK;AAClB,YAAAA,SAAQ,IAAI;AAAA,UACd;AAAA,QACF;AAAA,MACF;AACA,WAAK,SAAS,KAAK,MAAM;AAEzB,YAAM,QAAQ,WAAW,MAAM;AAC7B,YAAI,CAAC,SAAS;AACZ,oBAAU;AACV,gBAAM,MAAM,KAAK,SAAS,QAAQ,MAAM;AACxC,cAAI,QAAQ,GAAI,MAAK,SAAS,OAAO,KAAK,CAAC;AAC3C,UAAAA,SAAQ,IAAI;AAAA,QACd;AAAA,MACF,GAAG,SAAS;AAAA,IACd,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,CAAC,OAAO,aAAa,IAA8B;AACjD,WAAO;AAAA,MACL,MAAM,MAA0C;AAC9C,YAAI,KAAK,OAAO,SAAS,GAAG;AAC1B,iBAAO,QAAQ,QAAQ;AAAA,YACrB,OAAO,KAAK,OAAO,MAAM;AAAA,YACzB,MAAM;AAAA,UACR,CAAC;AAAA,QACH;AAEA,YAAI,KAAK,eAAe;AACtB,iBAAO,QAAQ,QAAQ;AAAA,YACrB,OAAO;AAAA,YACP,MAAM;AAAA,UACR,CAAC;AAAA,QACH;AAEA,eAAO,IAAI,QAAmC,CAACA,UAAS,WAAW;AACjE,eAAK,SAAS,KAAK;AAAA,YACjB,SAAS,CAAC,UAAUA,SAAQ,EAAE,OAAO,OAAO,MAAM,MAAM,CAAC;AAAA,YACzD;AAAA,UACF,CAAC;AAAA,QACH,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;;;ACnMA,IAAM,iBAAiB,oBAAI,IAAmB;AAAA,EAC5C,cAAc;AAAA,EACd,cAAc;AAAA,EACd,cAAc;AAAA,EACd,cAAc;AAChB,CAAC;AAEM,IAAM,OAAN,MAAW;AAAA,EACP;AAAA;AAAA,EAEA;AAAA,EACD,YAAY,oBAAI,IAAqB;AAAA,EACrC,gBAAgB,oBAAI,IAAyB;AAAA,EAC7C,aAAa,oBAAI,IAAa;AAAA,EAC9B,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA,EAM1B,YAAY,QAAgB,SAA2B;AACrD,SAAK,SAAS;AACd,SAAK,UAAU,WAAW,IAAI,gBAAgB;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QACJ,eACA,MACA,SAOkB;AAClB,UAAM,OAAO,SAAS,QAAQ;AAC9B,UAAM,aAAa,SAAS;AAC5B,UAAM,YAAY,SAAS;AAC3B,UAAM,SAAS,SAAS,UAAU;AAClC,UAAM,YAAY,SAAS;AAC3B,UAAM,cAA2B;AAAA,MAC/B,IAAI;AAAA,MAAe;AAAA,MAAM,QAAQ;AAAA,MAAU;AAAA,MAC3C,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,MACnC,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,IACnC;AACA,SAAK,cAAc,IAAI,eAAe,WAAW;AAGjD,UAAM,kBAAkB,KAAK,UAAU,IAAI,aAAa;AACxD,QAAI,iBAAiB;AACnB,sBAAgB,kBAAkB;AAAA,IACpC;AAEA,UAAM,gBAAgB,aAAa,IAAI,IAAI,cAAc;AACzD,UAAM,UAAU,IAAI,QAAQ,MAAM,eAAe,MAAM,aAAa;AACpE,SAAK,UAAU,IAAI,eAAe,OAAO;AAEzC,QAAI,CAAC,QAAQ;AACX,YAAM,QAAQ,YAAoC;AAAA,QAChD,MAAM;AAAA,QACN,UAAU;AAAA,QACV,SAAS,KAAK;AAAA,QACd,gBAAgB;AAAA,QAChB;AAAA,MACF,CAAC;AACD,YAAM,KAAK,mBAAmB,OAAO,aAAa;AAAA,IACpD;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,UAAmB;AACjB,UAAM,KAAK,SAAS,KAAK,MAAM,IAAI,KAAK,iBAAiB;AACzD,UAAM,UAAU,IAAI,QAAQ,MAAM,IAAI,gBAAgB,IAAI,IAAI,cAAc,CAAC;AAC7E,SAAK,WAAW,IAAI,OAAO;AAC3B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,aACJ,QAAQ,IACR,SAAwB,MACW;AACnC,WAAO,KAAK,QAAQ,YAAY,KAAK,QAAQ,OAAO,MAAM;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,eACJ,OACA,QAAQ,IACR,SAAwB,MACW;AACnC,WAAO,KAAK,QAAQ,eAAe,KAAK,QAAQ,OAAO,OAAO,MAAM;AAAA,EACtE;AAAA;AAAA,EAGA,mBAAkC;AAChC,WAAO,CAAC,GAAG,KAAK,cAAc,OAAO,CAAC;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,WACJ,WAAiC,MACjC,QAAQ,IACR,SAAwB,MACa;AACrC,WAAO,KAAK,QAAQ,UAAU,KAAK,QAAQ,UAAU,OAAO,MAAM;AAAA,EACpE;AAAA;AAAA,EAGA,MAAM,WAAW,IAAqC;AACpD,WAAO,KAAK,QAAQ,WAAW,KAAK,QAAQ,EAAE;AAAA,EAChD;AAAA;AAAA,EAGA,wBAAwB,eAAuB,WAAoC;AACjF,UAAM,cAAc,KAAK,cAAc,IAAI,aAAa;AACxD,QAAI,CAAC,YAAa,QAAO;AACzB,gBAAY,YAAY;AACxB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,eAAe,SAAiC;AACpD,UAAM,KAAK,QAAQ,WAAW,OAAO;AAErC,UAAM,QAAQ,YAA8B;AAAA,MAC1C,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS,KAAK;AAAA,MACd,gBAAgB,QAAQ;AAAA,MACxB;AAAA,IACF,CAAC;AACD,UAAM,KAAK,mBAAmB,KAAK;AAEnC,UAAM,WAAW,KAAK,gBAAgB,QAAQ,OAAO;AACrD,eAAW,eAAe,UAAU;AAClC,YAAM,KAAK,KAAK,UAAU,IAAI,WAAW;AACzC,UAAI,IAAI;AACN,cAAM,eAAe,YAA4B;AAAA,UAC/C,MAAM;AAAA,UACN,UAAU;AAAA,UACV,SAAS,KAAK;AAAA,UACd,gBAAgB;AAAA,UAChB;AAAA,QACF,CAAC;AACD,cAAM,KAAK,QAAQ,SAAS,YAAY;AACxC,WAAG,SAAS,YAAY;AAExB,mBAAW,YAAY,KAAK,YAAY;AACtC,mBAAS,SAAS,YAAY;AAAA,QAChC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,aAAa,OAAiC;AAClD,UAAM,KAAK,mBAAmB,OAAO,MAAM,cAAc;AAAA,EAC3D;AAAA;AAAA,EAGA,MAAM,mBAAmB,SAAkB,SAAS,OAAsB;AAExE,QAAI,KAAK,WAAW,OAAO,OAAO,GAAG;AACnC;AAAA,IACF;AAEA,UAAM,MAAM,QAAQ;AACpB,UAAM,cAAc,KAAK,cAAc,IAAI,GAAG;AAC9C,SAAK,UAAU,OAAO,GAAG;AACzB,SAAK,cAAc,OAAO,GAAG;AAE7B,QAAI,CAAC,UAAU,aAAa;AAC1B,YAAM,QAAQ,YAAkC;AAAA,QAC9C,MAAM;AAAA,QACN,UAAU;AAAA,QACV,SAAS,KAAK;AAAA,QACd,gBAAgB;AAAA,QAChB;AAAA,MACF,CAAC;AACD,YAAM,KAAK,mBAAmB,KAAK;AAAA,IACrC;AAAA,EACF;AAAA,EAEA,MAAc,mBACZ,OACA,SACe;AACf,UAAM,KAAK,QAAQ,SAAS,KAAK;AACjC,SAAK,WAAW,OAAO,OAAO;AAAA,EAChC;AAAA,EAEQ,WAAW,OAAkB,SAAwB;AAC3D,eAAW,CAAC,KAAK,OAAO,KAAK,KAAK,WAAW;AAC3C,UAAI,QAAQ,SAAS;AACnB,gBAAQ,SAAS,KAAK;AAAA,MACxB;AAAA,IACF;AACA,eAAW,YAAY,KAAK,YAAY;AACtC,eAAS,SAAS,KAAK;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,gBAAgB,SAA2B;AACjD,UAAM,eAAyB,CAAC;AAChC,UAAM,UAAU;AAChB,QAAI;AACJ,YAAQ,QAAQ,QAAQ,KAAK,OAAO,OAAO,MAAM;AAC/C,YAAM,QAAQ,MAAM,CAAC,EAAE,YAAY;AACnC,iBAAW,CAAC,KAAK,WAAW,KAAK,KAAK,eAAe;AACnD,cAAM,YAAY,YAAY,YAAY,YAAY,MAAM;AAC5D,cAAM,cAAc,YAAY,KAAK,YAAY,MAAM;AACvD,aAAK,aAAa,gBAAgB,CAAC,aAAa,SAAS,GAAG,GAAG;AAC7D,uBAAa,KAAK,GAAG;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;;;ACzTA,IAAM,SAAS;AAAA,EACb;AAAA,EAAO;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAS;AAAA,EAC1E;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAU;AAAA,EAAS;AAAA,EAAU;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAC/E;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAU;AAAA,EAC3E;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAU;AAAA,EAAW;AAAA,EAAS;AAAA,EAAU;AAAA,EAAS;AAAA,EAAS;AAAA,EACpF;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAU;AAAA,EAAS;AAAA,EAAS;AAAA,EAAS;AAAA,EAAU;AAAA,EAAS;AAAA,EAAU;AAAA,EACnF;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAU;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAS;AAAA,EAAU;AAAA,EAAS;AAAA,EAChF;AAAA,EAAQ;AAAA,EAAU;AAAA,EAAQ;AAAA,EAAU;AAAA,EAAU;AAAA,EAAW;AAAA,EAAW;AAAA,EAAU;AAAA,EAC9E;AAAA,EAAY;AAAA,EAAW;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAU;AAAA,EAAO;AAAA,EAAQ;AAAA,EAAS;AAAA,EACnF;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAW;AAAA,EAAS;AAAA,EAAS;AAAA,EAAW;AAAA,EAAU;AACrE;AAGO,SAAS,iBAAyB;AACvC,QAAM,QAAQ,OAAO,KAAK,MAAM,KAAK,OAAO,IAAI,OAAO,MAAM,CAAC;AAC9D,QAAM,SAAS,OAAO,KAAK,MAAM,KAAK,OAAO,IAAI,GAAI,IAAI,GAAI;AAC7D,SAAO,GAAG,MAAM,CAAC,EAAE,YAAY,CAAC,GAAG,MAAM,MAAM,CAAC,CAAC,IAAI,MAAM;AAC7D;AAEA,IAAM,QAAQ;AAAA,EACZ;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAC/D;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAO;AAAA,EACtE;AAAA,EAAO;AAAA,EAAO;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EACtE;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAO;AAAA,EAAO;AAAA,EAAQ;AAAA,EACxE;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EACtE;AAAA,EAAO;AAAA,EAAO;AAAA,EAAS;AAAA,EAAS;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAC1E;AAAA,EAAS;AAAA,EAAU;AAAA,EAAU;AAAA,EAAU;AAAA,EAAS;AAAA,EAAU;AAAA,EAAQ;AAAA,EAAS;AAAA,EAC3E;AAAA,EAAQ;AAAA,EAAU;AAAA,EAAS;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAS;AAAA,EACtE;AAAA,EAAS;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAU;AAAA,EAAS;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAS;AAAA,EACvE;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAS;AAAA,EAAS;AAAA,EAAS;AAAA,EACrE;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAQ;AACzB;AAGO,SAAS,aAAqB;AACnC,QAAM,OAAO,MAAM,KAAK,MAAM,KAAK,OAAO,IAAI,MAAM,MAAM,CAAC;AAC3D,QAAM,SAAS,OAAO,KAAK,MAAM,KAAK,OAAO,IAAI,GAAI,IAAI,GAAI;AAC7D,SAAO,GAAG,KAAK,CAAC,EAAE,YAAY,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC,IAAI,MAAM;AAC3D;","names":["resolve"]}
|
package/dist/claude/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { I as ILLMSession, R as RoomResolver, C as ClaudeSessionOptions, a as ContentPart } from '../types-
|
|
2
|
-
import '../index-
|
|
1
|
+
import { I as ILLMSession, R as RoomResolver, C as ClaudeSessionOptions, a as ContentPart } from '../types-B9xf8w53.js';
|
|
2
|
+
import '../index-DwVKKxqK.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/** Claude Agent SDK session backend for stoops agents. */
|
package/dist/cli/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
FileBackedStorage,
|
|
4
4
|
Room,
|
|
5
5
|
randomName,
|
|
6
6
|
randomRoomName
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-XEKY3KEU.js";
|
|
8
8
|
import {
|
|
9
9
|
EventProcessor,
|
|
10
10
|
RemoteRoomDataSource,
|
|
@@ -20,11 +20,16 @@ import {
|
|
|
20
20
|
formatTimestamp
|
|
21
21
|
} from "../chunk-TGA24MC3.js";
|
|
22
22
|
|
|
23
|
+
// src/cli/index.ts
|
|
24
|
+
import { createRequire as createRequire2 } from "module";
|
|
25
|
+
|
|
23
26
|
// src/cli/serve.ts
|
|
24
27
|
import { createServer } from "http";
|
|
25
28
|
import { spawn, execFileSync } from "child_process";
|
|
26
29
|
import { randomUUID } from "crypto";
|
|
27
30
|
import { createRequire } from "module";
|
|
31
|
+
import { tmpdir } from "os";
|
|
32
|
+
import { join as pathJoin } from "path";
|
|
28
33
|
|
|
29
34
|
// src/cli/auth.ts
|
|
30
35
|
import { randomBytes } from "crypto";
|
|
@@ -123,7 +128,24 @@ async function serve(options) {
|
|
|
123
128
|
} : logServer;
|
|
124
129
|
let publicUrl = serverUrl;
|
|
125
130
|
let tunnelProcess = null;
|
|
126
|
-
const
|
|
131
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
132
|
+
const savePath = options.save ?? options.load ?? pathJoin(tmpdir(), `stoops-${roomName}-${timestamp}.json`);
|
|
133
|
+
let storage;
|
|
134
|
+
if (options.load) {
|
|
135
|
+
try {
|
|
136
|
+
storage = await FileBackedStorage.load(options.load);
|
|
137
|
+
log(`loaded room state from ${options.load}`);
|
|
138
|
+
} catch (err) {
|
|
139
|
+
if (err.code === "ENOENT") {
|
|
140
|
+
storage = new FileBackedStorage(options.load);
|
|
141
|
+
log(`no existing file at ${options.load}, starting fresh`);
|
|
142
|
+
} else {
|
|
143
|
+
throw err;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
storage = new FileBackedStorage(savePath);
|
|
148
|
+
}
|
|
127
149
|
const room = new Room(roomName, storage);
|
|
128
150
|
const tokens = new TokenManager();
|
|
129
151
|
const participants = /* @__PURE__ */ new Map();
|
|
@@ -513,7 +535,7 @@ Port ${port} is already in use. Another stoops instance may be running.`);
|
|
|
513
535
|
const adminToken = tokens.generateShareToken("admin", "admin");
|
|
514
536
|
const memberToken = tokens.generateShareToken("admin", "member");
|
|
515
537
|
if (options.headless) {
|
|
516
|
-
process.stdout.write(JSON.stringify({ serverUrl, publicUrl, roomName, adminToken, memberToken }) + "\n");
|
|
538
|
+
process.stdout.write(JSON.stringify({ serverUrl, publicUrl, roomName, adminToken, memberToken, savePath }) + "\n");
|
|
517
539
|
} else if (!options.quiet) {
|
|
518
540
|
let version = process.env.npm_package_version ?? "";
|
|
519
541
|
if (!version) {
|
|
@@ -533,6 +555,7 @@ Port ${port} is already in use. Another stoops instance may be running.`);
|
|
|
533
555
|
Room: ${roomName}
|
|
534
556
|
Server: ${serverUrl}${publicUrl !== serverUrl ? `
|
|
535
557
|
Tunnel: ${publicUrl}` : ""}
|
|
558
|
+
Saving: ${savePath}
|
|
536
559
|
|
|
537
560
|
Join: stoops join ${joinUrl}
|
|
538
561
|
Admin: stoops join ${adminUrl}
|
|
@@ -565,7 +588,7 @@ Port ${port} is already in use. Another stoops instance may be running.`);
|
|
|
565
588
|
};
|
|
566
589
|
process.on("SIGINT", shutdown);
|
|
567
590
|
process.on("SIGTERM", shutdown);
|
|
568
|
-
return { serverUrl, publicUrl, roomName, adminToken, memberToken };
|
|
591
|
+
return { serverUrl, publicUrl, roomName, adminToken, memberToken, savePath };
|
|
569
592
|
}
|
|
570
593
|
function logServer(message) {
|
|
571
594
|
console.log(` [${formatTimestamp(/* @__PURE__ */ new Date())}] ${message}`);
|
|
@@ -779,7 +802,9 @@ function App({
|
|
|
779
802
|
onCtrlC,
|
|
780
803
|
onReady,
|
|
781
804
|
readOnly,
|
|
782
|
-
isAdmin
|
|
805
|
+
isAdmin,
|
|
806
|
+
version,
|
|
807
|
+
savePath
|
|
783
808
|
}) {
|
|
784
809
|
const [events, setEvents] = useState([]);
|
|
785
810
|
const [agentNames, setAgentNames] = useState([]);
|
|
@@ -915,10 +940,18 @@ function App({
|
|
|
915
940
|
if (!entry.event) {
|
|
916
941
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 2, paddingTop: 1, paddingBottom: 1, children: [
|
|
917
942
|
BANNER_LINES.map((line, i) => /* @__PURE__ */ jsx(Text, { color: GRADIENT[i], children: line }, i)),
|
|
943
|
+
version && /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
|
|
944
|
+
" v",
|
|
945
|
+
version
|
|
946
|
+
] }),
|
|
918
947
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
919
948
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
920
949
|
/* @__PURE__ */ jsx(Text, { color: C.dim, children: " room " }),
|
|
921
950
|
/* @__PURE__ */ jsx(Text, { color: C.cyan, bold: true, children: roomName })
|
|
951
|
+
] }),
|
|
952
|
+
savePath && /* @__PURE__ */ jsxs(Text, { children: [
|
|
953
|
+
/* @__PURE__ */ jsx(Text, { color: C.dim, children: " saved " }),
|
|
954
|
+
/* @__PURE__ */ jsx(Text, { color: C.secondary, children: savePath })
|
|
922
955
|
] })
|
|
923
956
|
] }, entry.id);
|
|
924
957
|
}
|
|
@@ -986,7 +1019,9 @@ function startTUI(opts) {
|
|
|
986
1019
|
onCtrlC: opts.onCtrlC,
|
|
987
1020
|
onReady,
|
|
988
1021
|
readOnly: opts.readOnly,
|
|
989
|
-
isAdmin: opts.isAdmin
|
|
1022
|
+
isAdmin: opts.isAdmin,
|
|
1023
|
+
version: opts.version,
|
|
1024
|
+
savePath: opts.savePath
|
|
990
1025
|
}
|
|
991
1026
|
),
|
|
992
1027
|
{ exitOnCtrlC: false }
|
|
@@ -1379,6 +1414,8 @@ ${lines.join("\n")}`);
|
|
|
1379
1414
|
roomName,
|
|
1380
1415
|
readOnly: isReadOnly,
|
|
1381
1416
|
isAdmin: authority === "admin",
|
|
1417
|
+
version: options.version,
|
|
1418
|
+
savePath: options.savePath,
|
|
1382
1419
|
onSend: isReadOnly ? void 0 : async (content) => {
|
|
1383
1420
|
if (content.startsWith("/")) {
|
|
1384
1421
|
await handleSlashCommand(content);
|
|
@@ -1574,7 +1611,7 @@ function toDisplayEvent(event, selfId, participantTypes) {
|
|
|
1574
1611
|
// src/cli/claude/run.ts
|
|
1575
1612
|
import { writeFileSync, mkdtempSync, rmSync, chmodSync } from "fs";
|
|
1576
1613
|
import { join as join2 } from "path";
|
|
1577
|
-
import { tmpdir } from "os";
|
|
1614
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
1578
1615
|
|
|
1579
1616
|
// src/cli/tmux.ts
|
|
1580
1617
|
import { execFileSync as execFileSync2, spawn as spawn2 } from "child_process";
|
|
@@ -2178,7 +2215,7 @@ async function runClaude(options) {
|
|
|
2178
2215
|
process.exit(1);
|
|
2179
2216
|
}
|
|
2180
2217
|
const setup = await setupAgentRuntime({ ...options, joinUrls: void 0 });
|
|
2181
|
-
const tmpDir = mkdtempSync(join2(
|
|
2218
|
+
const tmpDir = mkdtempSync(join2(tmpdir2(), "stoops_agent_"));
|
|
2182
2219
|
const bridgePath = join2(tmpDir, "mcp-bridge.cjs");
|
|
2183
2220
|
writeFileSync(bridgePath, MCP_STDIO_BRIDGE);
|
|
2184
2221
|
chmodSync(bridgePath, 493);
|
|
@@ -2375,7 +2412,7 @@ async function pollForReady(url, timeoutMs) {
|
|
|
2375
2412
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
2376
2413
|
import { writeFileSync as writeFileSync2, mkdtempSync as mkdtempSync2, mkdirSync, rmSync as rmSync2 } from "fs";
|
|
2377
2414
|
import { join as join3 } from "path";
|
|
2378
|
-
import { tmpdir as
|
|
2415
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
2379
2416
|
|
|
2380
2417
|
// src/cli/codex/tmux-bridge.ts
|
|
2381
2418
|
var SPINNER_CHARS2 = "\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F";
|
|
@@ -2588,7 +2625,7 @@ async function runCodex(options) {
|
|
|
2588
2625
|
process.exit(1);
|
|
2589
2626
|
}
|
|
2590
2627
|
const setup = await setupAgentRuntime({ ...options, joinUrls: void 0 });
|
|
2591
|
-
const tmpDir = mkdtempSync2(join3(
|
|
2628
|
+
const tmpDir = mkdtempSync2(join3(tmpdir3(), "stoops_codex_"));
|
|
2592
2629
|
const mcpPort = new URL(setup.mcpServer.url).port;
|
|
2593
2630
|
const mcpUrl = `http://127.0.0.1:${mcpPort}/mcp`;
|
|
2594
2631
|
const codexConfigDir = join3(tmpDir, ".codex");
|
|
@@ -2641,6 +2678,15 @@ async function runCodex(options) {
|
|
|
2641
2678
|
}
|
|
2642
2679
|
|
|
2643
2680
|
// src/cli/index.ts
|
|
2681
|
+
function getVersion() {
|
|
2682
|
+
try {
|
|
2683
|
+
const require2 = createRequire2(import.meta.url);
|
|
2684
|
+
const pkg = require2("../../package.json");
|
|
2685
|
+
return pkg.version ?? "unknown";
|
|
2686
|
+
} catch {
|
|
2687
|
+
return "unknown";
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2644
2690
|
var args = process.argv.slice(2);
|
|
2645
2691
|
function getFlag(name, arr = args) {
|
|
2646
2692
|
const idx = arr.indexOf(`--${name}`);
|
|
@@ -2721,7 +2767,9 @@ async function main() {
|
|
|
2721
2767
|
room: getFlag("room"),
|
|
2722
2768
|
port,
|
|
2723
2769
|
share: args.includes("--share"),
|
|
2724
|
-
headless: args.includes("--headless")
|
|
2770
|
+
headless: args.includes("--headless"),
|
|
2771
|
+
save: getFlag("save"),
|
|
2772
|
+
load: getFlag("load")
|
|
2725
2773
|
});
|
|
2726
2774
|
return;
|
|
2727
2775
|
}
|
|
@@ -2736,7 +2784,9 @@ async function main() {
|
|
|
2736
2784
|
room: getFlag("room"),
|
|
2737
2785
|
port,
|
|
2738
2786
|
share: args.includes("--share"),
|
|
2739
|
-
quiet: true
|
|
2787
|
+
quiet: true,
|
|
2788
|
+
save: getFlag("save"),
|
|
2789
|
+
load: getFlag("load")
|
|
2740
2790
|
});
|
|
2741
2791
|
const adminJoinUrl = buildShareUrl(result.serverUrl, result.adminToken);
|
|
2742
2792
|
const participantShareUrl = buildShareUrl(
|
|
@@ -2746,7 +2796,9 @@ async function main() {
|
|
|
2746
2796
|
await join({
|
|
2747
2797
|
server: adminJoinUrl,
|
|
2748
2798
|
name: getFlag("name"),
|
|
2749
|
-
shareUrl: participantShareUrl
|
|
2799
|
+
shareUrl: participantShareUrl,
|
|
2800
|
+
version: getVersion(),
|
|
2801
|
+
savePath: result.savePath
|
|
2750
2802
|
});
|
|
2751
2803
|
return;
|
|
2752
2804
|
}
|