@rool-dev/sdk 0.1.0
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 +1070 -0
- package/dist/apps.d.ts +29 -0
- package/dist/apps.d.ts.map +1 -0
- package/dist/apps.js +88 -0
- package/dist/apps.js.map +1 -0
- package/dist/auth-browser.d.ts +80 -0
- package/dist/auth-browser.d.ts.map +1 -0
- package/dist/auth-browser.js +370 -0
- package/dist/auth-browser.js.map +1 -0
- package/dist/auth-node.d.ts +46 -0
- package/dist/auth-node.d.ts.map +1 -0
- package/dist/auth-node.js +316 -0
- package/dist/auth-node.js.map +1 -0
- package/dist/auth.d.ts +56 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +96 -0
- package/dist/auth.js.map +1 -0
- package/dist/client.d.ts +202 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +472 -0
- package/dist/client.js.map +1 -0
- package/dist/event-emitter.d.ts +38 -0
- package/dist/event-emitter.d.ts.map +1 -0
- package/dist/event-emitter.js +80 -0
- package/dist/event-emitter.js.map +1 -0
- package/dist/graphql.d.ts +71 -0
- package/dist/graphql.d.ts.map +1 -0
- package/dist/graphql.js +487 -0
- package/dist/graphql.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/jsonld.d.ts +47 -0
- package/dist/jsonld.d.ts.map +1 -0
- package/dist/jsonld.js +137 -0
- package/dist/jsonld.js.map +1 -0
- package/dist/media.d.ts +52 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +173 -0
- package/dist/media.js.map +1 -0
- package/dist/space.d.ts +358 -0
- package/dist/space.d.ts.map +1 -0
- package/dist/space.js +1121 -0
- package/dist/space.js.map +1 -0
- package/dist/subscription.d.ts +57 -0
- package/dist/subscription.d.ts.map +1 -0
- package/dist/subscription.js +296 -0
- package/dist/subscription.js.map +1 -0
- package/dist/types.d.ts +409 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,1070 @@
|
|
|
1
|
+
# Rool SDK
|
|
2
|
+
|
|
3
|
+
The TypeScript SDK for Rool Spaces, a persistent and collaborative environment for organizing objects and their relationships.
|
|
4
|
+
|
|
5
|
+
Rool Spaces enables you to build applications where AI operates on a structured world model rather than a text conversation. The context for all AI operations is the full object graph, allowing the system to reason about, update, and expand the state of your application directly.
|
|
6
|
+
|
|
7
|
+
Use Rool to programmatically instruct agents to generate content, research topics, or reorganize data. The client manages authentication, real-time synchronization, and media storage, supporting both single-user and multi-user workflows.
|
|
8
|
+
|
|
9
|
+
**Core primitives:**
|
|
10
|
+
- **Objects** — Key-value records with any fields you define
|
|
11
|
+
- **Relations** — Directional links between objects (e.g., `earth` → `orbits` → `sun`)
|
|
12
|
+
- **AI operations** — Create, update, or query objects using natural language and `{{placeholders}}`
|
|
13
|
+
|
|
14
|
+
See [Patterns & Examples](#patterns--examples) for what you can build.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @rool-dev/sdk
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Configuration
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { RoolClient } from '@rool-dev/sdk';
|
|
26
|
+
|
|
27
|
+
const client = new RoolClient({
|
|
28
|
+
baseUrl: 'https://api.dev.rool.dev',
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### RoolClientConfig
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
interface RoolClientConfig {
|
|
36
|
+
baseUrl: string; // Base URL (e.g., 'https://api.dev.rool.dev')
|
|
37
|
+
graphqlUrl?: string; // Override GraphQL endpoint (default: {baseUrl}/graphql)
|
|
38
|
+
mediaUrl?: string; // Override media endpoint (default: {baseUrl}/media)
|
|
39
|
+
authUrl?: string; // Override auth endpoint (default: {baseUrl}/auth)
|
|
40
|
+
authProvider?: AuthProvider; // Optional, defaults to browser auth
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Base URLs
|
|
45
|
+
|
|
46
|
+
| Environment | URL |
|
|
47
|
+
|-------------|-----|
|
|
48
|
+
| Development | `https://api.dev.rool.dev` |
|
|
49
|
+
| Production | `https://api.rool.dev` |
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { RoolClient } from '@rool-dev/sdk';
|
|
55
|
+
|
|
56
|
+
// Create and initialize client
|
|
57
|
+
const client = new RoolClient({
|
|
58
|
+
baseUrl: 'https://api.dev.rool.dev',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Process auth callbacks if we are returning from the login page
|
|
62
|
+
client.initialize();
|
|
63
|
+
|
|
64
|
+
if (!await client.isAuthenticated()) {
|
|
65
|
+
client.login('My App'); // Redirects to auth page, shows "Sign in to My App"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Open an existing space
|
|
69
|
+
const space = await client.openSpace('abc1234');
|
|
70
|
+
|
|
71
|
+
// Create a new space
|
|
72
|
+
const newSpace = await client.createSpace('My New Space');
|
|
73
|
+
|
|
74
|
+
// Listen for changes (real-time updates are automatic)
|
|
75
|
+
space.on('objectCreated', ({ objectId, object, source }) => {
|
|
76
|
+
console.log('New object:', objectId, object, 'from', source);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
space.on('linked', ({ sourceId, relation, targetId, source }) => {
|
|
80
|
+
console.log('New link:', sourceId, '->', targetId, `[${relation}]`, 'from', source);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Create objects with AI-generated content.
|
|
84
|
+
// Field names are yours to define — only 'id' is reserved.
|
|
85
|
+
const { object: sun } = await space.createObject({
|
|
86
|
+
data: {
|
|
87
|
+
type: 'star',
|
|
88
|
+
name: 'Sun',
|
|
89
|
+
mass: '{{mass in solar masses}}',
|
|
90
|
+
radius: '{{radius in km}}',
|
|
91
|
+
temperature: '{{surface temperature}}'
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const { object: earth } = await space.createObject({
|
|
96
|
+
data: {
|
|
97
|
+
type: 'planet',
|
|
98
|
+
name: 'Earth',
|
|
99
|
+
mass: '{{mass in Earth masses}}',
|
|
100
|
+
radius: '{{radius in km}}',
|
|
101
|
+
orbitalPeriod: '{{orbital period in days}}'
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Create an undo checkpoint
|
|
106
|
+
await space.checkpoint('before linking');
|
|
107
|
+
|
|
108
|
+
// Link them together
|
|
109
|
+
await space.link(earth.id, 'orbits', sun.id);
|
|
110
|
+
|
|
111
|
+
await space.undo(); // The link created above is now gone
|
|
112
|
+
await space.redo(); // The link is back ...
|
|
113
|
+
|
|
114
|
+
// Stop receiving events for this space and free resources
|
|
115
|
+
space.close();
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Core Concepts
|
|
119
|
+
|
|
120
|
+
### Objects & Relations
|
|
121
|
+
|
|
122
|
+
**Objects** are plain key-value records. The `id` field is reserved; everything else is application-defined.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
{ id: 'abc123', type: 'article', title: 'Hello World', status: 'draft' }
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Relations** connect objects directionally through named links. Each relation name represents a multi-valued reference set on the source object.
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
await space.link(earth.id, 'orbits', sun.id);
|
|
132
|
+
// Reads as: "earth.orbits includes sun"
|
|
133
|
+
|
|
134
|
+
const orbited = space.getChildren(earth.id, 'orbits'); // [sun] - what earth links TO
|
|
135
|
+
const orbiters = space.getParents(sun.id, 'orbits'); // [earth] - what links TO sun
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Relations are indexed and idempotent — creating the same link twice has no effect.
|
|
139
|
+
|
|
140
|
+
**Notes:**
|
|
141
|
+
- Relation names are application-defined strings
|
|
142
|
+
- Relations are not stored in object data fields
|
|
143
|
+
- Only relations created via `link()` participate in traversal (`getParents`, `getChildren`) and indexing
|
|
144
|
+
- Storing object IDs inside regular object fields is possible, but those references are opaque to the system
|
|
145
|
+
|
|
146
|
+
### AI Placeholder Pattern
|
|
147
|
+
|
|
148
|
+
Use `{{description}}` in field values to have AI generate content:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// Create with AI-generated content
|
|
152
|
+
await space.createObject({
|
|
153
|
+
data: {
|
|
154
|
+
type: 'article',
|
|
155
|
+
headline: '{{catchy headline about coffee}}',
|
|
156
|
+
body: '{{informative paragraph}}'
|
|
157
|
+
},
|
|
158
|
+
prompt: 'Write about specialty coffee brewing'
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Update existing content with AI
|
|
162
|
+
await space.updateObject('abc123', {
|
|
163
|
+
prompt: 'Make the body shorter and more casual'
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Add new AI-generated field to existing object
|
|
167
|
+
await space.updateObject('abc123', {
|
|
168
|
+
data: { summary: '{{one-sentence summary}}' }
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
When resolving placeholders, the agent has access to the full object data and the surrounding space context (except for `_`-prefixed fields). Placeholders are instructions, not templates, and do not need to repeat information already present in other fields.
|
|
173
|
+
|
|
174
|
+
Placeholders are resolved by the AI during the mutation and replaced with concrete values. The `{{...}}` syntax is never stored — it only guides the agent while creating or updating the object.
|
|
175
|
+
|
|
176
|
+
### Checkpoints & Undo/Redo
|
|
177
|
+
|
|
178
|
+
Undo/redo works on **checkpoints**, not individual operations. Call `checkpoint()` before making changes to create a restore point.
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// Create a checkpoint before user action
|
|
182
|
+
await space.checkpoint('Delete object');
|
|
183
|
+
await space.deleteObjects([objectId]);
|
|
184
|
+
|
|
185
|
+
// User can now undo back to the checkpoint
|
|
186
|
+
if (await space.canUndo()) {
|
|
187
|
+
await space.undo(); // Restores the deleted object
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Redo reapplies the undone action
|
|
191
|
+
if (await space.canRedo()) {
|
|
192
|
+
await space.redo(); // Deletes the object again
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Without a checkpoint, `undo()` has nothing to restore to. Undo always restores the space to the last checkpoint, regardless of how many changes were made since.
|
|
197
|
+
|
|
198
|
+
In collaborative scenarios, conflicting changes (modified by others since your checkpoint) are silently skipped.
|
|
199
|
+
|
|
200
|
+
### Hidden Fields
|
|
201
|
+
|
|
202
|
+
Fields starting with `_` (e.g., `_ui`, `_cache`) are hidden from AI but otherwise behave like normal fields — they sync in real-time, persist to the server, support undo/redo, and are visible to all users of the Space. Use them for UI state, positions, or other data the AI shouldn't see or modify:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
await space.createObject({
|
|
206
|
+
data: {
|
|
207
|
+
title: 'My Article',
|
|
208
|
+
author: "John Doe",
|
|
209
|
+
_ui: { x: 100, y: 200, collapsed: false }
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Real-time Sync
|
|
215
|
+
|
|
216
|
+
Events fire for both local and remote changes. The `source` field indicates origin:
|
|
217
|
+
|
|
218
|
+
- `local_user` — This client made the change
|
|
219
|
+
- `remote_user` — Another user/client made the change
|
|
220
|
+
- `remote_agent` — AI agent made the change
|
|
221
|
+
- `system` — Resync after error
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
// All UI updates happen in one place, regardless of change source
|
|
225
|
+
space.on('objectUpdated', ({ objectId, object, source }) => {
|
|
226
|
+
renderObject(objectId, object);
|
|
227
|
+
if (source === 'remote_agent') {
|
|
228
|
+
doLayout(); // AI might have added content
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Caller just makes the change - event handler does the UI work
|
|
233
|
+
space.updateObject(objectId, { prompt: 'expand this' });
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Custom Object IDs
|
|
237
|
+
|
|
238
|
+
By default, `createObject` generates a 6-character alphanumeric ID. Provide your own via `data.id`:
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
await space.createObject({ data: { id: 'article-42', title: 'The Meaning of Life' } });
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Why use custom IDs?**
|
|
245
|
+
- **Fire-and-forget creation** — Know the ID immediately without awaiting the response. You can create an object and link to it in parallel; the sync happens in the background.
|
|
246
|
+
- **Meaningful IDs** — Use domain-specific IDs like `user-123` or `doc-abc` for easier debugging and external references.
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// Fire-and-forget: create and link without waiting
|
|
250
|
+
const id = RoolClient.generateId();
|
|
251
|
+
space.createObject({ data: { id, type: 'note', text: '{{expand this idea}}' } });
|
|
252
|
+
space.link(parentId, 'hasNote', id); // Can link immediately
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Constraints:**
|
|
256
|
+
- Must contain only alphanumeric characters, hyphens (`-`), and underscores (`_`)
|
|
257
|
+
- Must be unique within the space (throws if ID exists)
|
|
258
|
+
- Cannot be changed after creation (immutable)
|
|
259
|
+
|
|
260
|
+
Use `RoolClient.generateId()` when you need an ID before calling `createObject` but don't need it to be meaningful — it gives you a valid random ID without writing your own generator.
|
|
261
|
+
|
|
262
|
+
## Authentication
|
|
263
|
+
|
|
264
|
+
### Browser (Default)
|
|
265
|
+
|
|
266
|
+
No configuration needed. Uses localStorage for tokens, redirects to login page.
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
const client = new RoolClient({ baseUrl: 'https://api.rool.dev' });
|
|
270
|
+
client.initialize(); // Process auth callbacks if this is a callback from the auth page
|
|
271
|
+
|
|
272
|
+
if (!await client.isAuthenticated()) {
|
|
273
|
+
client.login('My App'); // Redirect to the auth page
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Node.js
|
|
278
|
+
|
|
279
|
+
For CLI tools and scripts. Stores credentials in `~/.config/rool/`, opens browser for login.
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { NodeAuthProvider } from '@rool-dev/sdk/node';
|
|
283
|
+
|
|
284
|
+
const client = new RoolClient({
|
|
285
|
+
baseUrl: 'https://api.rool.dev',
|
|
286
|
+
authProvider: new NodeAuthProvider()
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
if (!await client.isAuthenticated()) {
|
|
290
|
+
await client.login('My CLI Tool'); // Open auth page in system browser, await callback
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Auth Methods
|
|
295
|
+
|
|
296
|
+
| Method | Description |
|
|
297
|
+
|--------|-------------|
|
|
298
|
+
| `initialize(): boolean` | **Call on app startup if running in browser.** Processes auth callback from URL, sets up token refresh. |
|
|
299
|
+
| `login(appName): void` | Redirect to login page. The app name is displayed on the auth page ("Sign in to {appName}"). |
|
|
300
|
+
| `logout(): void` | Clear tokens and state |
|
|
301
|
+
| `isAuthenticated(): Promise<boolean>` | Check auth status (validates token) |
|
|
302
|
+
| `getToken(): Promise<string \| undefined>` | Get current access token |
|
|
303
|
+
| `getAuthUser(): AuthUser` | Get auth identity from JWT (`{ email, name }`) |
|
|
304
|
+
|
|
305
|
+
## AI Agent
|
|
306
|
+
|
|
307
|
+
The `prompt()` method is the primary way to invoke the AI agent. The agent has editor-level capabilities — it can create, modify, delete, link, and research — but cannot see or modify `_`-prefixed fields.
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
const { message, objects } = await space.prompt(
|
|
311
|
+
"Create a topic node for the solar system, then child nodes for each planet."
|
|
312
|
+
);
|
|
313
|
+
console.log(`AI: ${message}`);
|
|
314
|
+
console.log(`Modified ${objects.length} objects:`, objects);
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Use `checkpoint()` before prompting to make operations undoable.
|
|
318
|
+
|
|
319
|
+
### Method Signature
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
prompt(text: string, options?: PromptOptions): Promise<{ message: string; objects: RoolObject[] }>
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Returns a message (the AI's response) and the list of objects that were created or modified.
|
|
326
|
+
|
|
327
|
+
### Options
|
|
328
|
+
|
|
329
|
+
| Option | Description |
|
|
330
|
+
|--------|-------------|
|
|
331
|
+
| `objectIds` | Limit context to specific objects |
|
|
332
|
+
| `responseSchema` | Request structured JSON instead of text summary |
|
|
333
|
+
| `effort` | Effort level: `'QUICK'`, `'STANDARD'` (default), `'REASONING'`, or `'RESEARCH'` |
|
|
334
|
+
| `ephemeral` | If true, don't record in conversation history (useful for tab completion) |
|
|
335
|
+
| `readOnly` | If true, disable mutation tools (create, update, link, unlink). Use for questions. |
|
|
336
|
+
|
|
337
|
+
### Effort Levels
|
|
338
|
+
|
|
339
|
+
| Level | Description |
|
|
340
|
+
|-------|-------------|
|
|
341
|
+
| `QUICK` | Fast, lightweight model. Best for simple questions. |
|
|
342
|
+
| `STANDARD` | Default behavior with balanced capabilities. |
|
|
343
|
+
| `REASONING` | Extended reasoning for complex tasks. |
|
|
344
|
+
| `RESEARCH` | Pre-analysis and context gathering (reserved for future use). |
|
|
345
|
+
|
|
346
|
+
### Examples
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
// Reorganize and link existing objects
|
|
350
|
+
const { objects } = await space.prompt(
|
|
351
|
+
"Group these notes by topic and create a parent node for each group."
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
// Work with specific objects
|
|
355
|
+
const result = await space.prompt(
|
|
356
|
+
"Summarize these articles",
|
|
357
|
+
{ objectIds: ['article-1', 'article-2'] }
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
// Quick question without mutations (fast model + read-only)
|
|
361
|
+
const { message } = await space.prompt(
|
|
362
|
+
"What topics are covered?",
|
|
363
|
+
{ effort: 'QUICK', readOnly: true }
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
// Complex analysis with extended reasoning
|
|
367
|
+
await space.prompt(
|
|
368
|
+
"Analyze relationships and reorganize",
|
|
369
|
+
{ effort: 'REASONING' }
|
|
370
|
+
);
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Structured Responses
|
|
374
|
+
|
|
375
|
+
Use `responseSchema` to get structured JSON instead of a text message:
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
const { message } = await space.prompt("Categorize these items", {
|
|
379
|
+
objectIds: ['item-1', 'item-2', 'item-3'],
|
|
380
|
+
responseSchema: {
|
|
381
|
+
type: 'object',
|
|
382
|
+
properties: {
|
|
383
|
+
categories: {
|
|
384
|
+
type: 'array',
|
|
385
|
+
items: { type: 'string' }
|
|
386
|
+
},
|
|
387
|
+
summary: { type: 'string' }
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const result = JSON.parse(message);
|
|
393
|
+
console.log(result.categories, result.summary);
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Context Flow
|
|
397
|
+
|
|
398
|
+
AI operations automatically receive context:
|
|
399
|
+
- **Interaction history** — Previous interactions and their results from this conversation
|
|
400
|
+
- **Recently modified objects** — Objects created or changed recently
|
|
401
|
+
- **Selected objects** — Objects passed via `objectIds` are given primary focus
|
|
402
|
+
|
|
403
|
+
This context flows automatically — no configuration needed. The AI sees enough history to maintain coherent interactions while respecting the `_`-prefixed field hiding rules.
|
|
404
|
+
|
|
405
|
+
## Collaboration
|
|
406
|
+
|
|
407
|
+
### Adding Users to a Space
|
|
408
|
+
|
|
409
|
+
To add a user to a space, you need their user ID. Use `searchUser()` to find them by email:
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
// Find the user by email
|
|
413
|
+
const user = await client.searchUser('colleague@example.com');
|
|
414
|
+
if (!user) {
|
|
415
|
+
throw new Error('User not found');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Add them to the space
|
|
419
|
+
await space.addUser(user.id, 'editor');
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Roles
|
|
423
|
+
|
|
424
|
+
| Role | Capabilities |
|
|
425
|
+
|------|--------------|
|
|
426
|
+
| `owner` | Full control, can delete space and manage users |
|
|
427
|
+
| `editor` | Can create, modify, delete objects and links |
|
|
428
|
+
| `viewer` | Read-only access |
|
|
429
|
+
|
|
430
|
+
### Space Collaboration Methods
|
|
431
|
+
|
|
432
|
+
| Method | Description |
|
|
433
|
+
|--------|-------------|
|
|
434
|
+
| `listUsers(): Promise<SpaceMember[]>` | List users with access |
|
|
435
|
+
| `addUser(userId, role): Promise<void>` | Add user to space |
|
|
436
|
+
| `removeUser(userId): Promise<void>` | Remove user from space |
|
|
437
|
+
|
|
438
|
+
### Client User Methods
|
|
439
|
+
|
|
440
|
+
| Method | Description |
|
|
441
|
+
|--------|-------------|
|
|
442
|
+
| `getCurrentUser(): Promise<CurrentUser>` | Get current Rool user (id, email, name, plan, credits, createdAt, lastActivity, processedAt, storage) |
|
|
443
|
+
| `searchUser(email): Promise<UserResult \| null>` | Find user by exact email address (no partial matching) |
|
|
444
|
+
|
|
445
|
+
### Real-time Collaboration
|
|
446
|
+
|
|
447
|
+
When multiple users have a space open, changes sync in real-time. The `source` field in events tells you who made the change:
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
space.on('objectUpdated', ({ objectId, object, source }) => {
|
|
451
|
+
if (source === 'remote_user') {
|
|
452
|
+
// Another user made this change
|
|
453
|
+
showCollaboratorActivity(object);
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
See [Real-time Sync](#real-time-sync) for more on event sources.
|
|
459
|
+
|
|
460
|
+
## RoolClient API
|
|
461
|
+
|
|
462
|
+
### Space Lifecycle
|
|
463
|
+
|
|
464
|
+
| Method | Description |
|
|
465
|
+
|--------|-------------|
|
|
466
|
+
| `listSpaces(): Promise<RoolSpaceInfo[]>` | List available spaces |
|
|
467
|
+
| `openSpace(id, options?): Promise<RoolSpace>` | Open a space for editing. Options: `{ conversationId?: string }` |
|
|
468
|
+
| `createSpace(name?, options?): Promise<RoolSpace>` | Create a new space. Options: `{ conversationId?: string }` |
|
|
469
|
+
| `deleteSpace(id): Promise<void>` | Permanently delete a space (cannot be undone) |
|
|
470
|
+
|
|
471
|
+
### User Storage
|
|
472
|
+
|
|
473
|
+
Server-side key-value storage for user preferences, UI state, and other persistent data. Replaces browser localStorage with cross-device, server-synced storage.
|
|
474
|
+
|
|
475
|
+
**Features:**
|
|
476
|
+
- Sync reads from local cache (available immediately, even before auth)
|
|
477
|
+
- Automatic sync to server and across tabs/devices via SSE
|
|
478
|
+
- `userStorageChanged` event fires on all changes (local or remote)
|
|
479
|
+
- Total storage limited to 10MB per user
|
|
480
|
+
|
|
481
|
+
| Method | Description |
|
|
482
|
+
|--------|-------------|
|
|
483
|
+
| `getUserStorage<T>(key): T \| undefined` | Get a value (sync, from local cache) |
|
|
484
|
+
| `setUserStorage(key, value): void` | Set a value (updates cache, syncs to server) |
|
|
485
|
+
| `getAllUserStorage(): Record<string, unknown>` | Get all stored data (sync, from local cache) |
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
// Sync read at startup (before auth completes)
|
|
489
|
+
const theme = client.getUserStorage<string>('theme');
|
|
490
|
+
applyTheme(theme ?? 'light');
|
|
491
|
+
|
|
492
|
+
// Write - updates immediately, syncs to server in background
|
|
493
|
+
client.setUserStorage('theme', 'dark');
|
|
494
|
+
client.setUserStorage('sidebar', { collapsed: true, width: 280 });
|
|
495
|
+
|
|
496
|
+
// Delete a key
|
|
497
|
+
client.setUserStorage('theme', null);
|
|
498
|
+
|
|
499
|
+
// The cache may be stale from a previous session — listen for updates
|
|
500
|
+
// to apply fresh values once sync completes (or when other tabs/devices change values)
|
|
501
|
+
client.on('userStorageChanged', ({ key, value, source }) => {
|
|
502
|
+
// source: 'local' (this client) or 'remote' (server/other client)
|
|
503
|
+
if (key === 'theme') applyTheme(value as string);
|
|
504
|
+
});
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### Utilities
|
|
508
|
+
|
|
509
|
+
| Method | Description |
|
|
510
|
+
|--------|-------------|
|
|
511
|
+
| `RoolClient.generateId(): string` | Generate 6-char alphanumeric ID (static) |
|
|
512
|
+
| `graphql<T>(query, variables?): Promise<T>` | Execute raw GraphQL |
|
|
513
|
+
| `destroy(): void` | Clean up resources |
|
|
514
|
+
|
|
515
|
+
### Client Events
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
client.on('authStateChanged', (authenticated: boolean) => void)
|
|
519
|
+
client.on('spaceCreated', (space: RoolSpaceInfo) => void)
|
|
520
|
+
client.on('spaceDeleted', (spaceId: string) => void)
|
|
521
|
+
client.on('spaceRenamed', (spaceId: string, newName: string) => void)
|
|
522
|
+
client.on('userStorageChanged', ({ key, value, source }: UserStorageChangedEvent) => void)
|
|
523
|
+
client.on('connectionStateChanged', (state: 'connected' | 'disconnected' | 'reconnecting') => void)
|
|
524
|
+
client.on('error', (error: Error, context?: string) => void)
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
## RoolSpace API
|
|
528
|
+
|
|
529
|
+
Spaces are first-class objects with built-in undo/redo, event emission, and real-time sync.
|
|
530
|
+
|
|
531
|
+
### Properties
|
|
532
|
+
|
|
533
|
+
| Property | Description |
|
|
534
|
+
|----------|-------------|
|
|
535
|
+
| `id: string` | Space ID |
|
|
536
|
+
| `name: string` | Space name |
|
|
537
|
+
| `role: RoolUserRole` | User's role (`'owner' \| 'editor' \| 'viewer'`) |
|
|
538
|
+
| `userId: string` | Current user's ID |
|
|
539
|
+
| `conversationId: string` | ID for interaction history (tracks AI context). Writable — set to switch conversations. |
|
|
540
|
+
| `isReadOnly(): boolean` | True if viewer role |
|
|
541
|
+
|
|
542
|
+
### Lifecycle
|
|
543
|
+
|
|
544
|
+
| Method | Description |
|
|
545
|
+
|--------|-------------|
|
|
546
|
+
| `close(): void` | Clean up resources and stop receiving updates |
|
|
547
|
+
| `rename(newName): Promise<void>` | Rename the space |
|
|
548
|
+
|
|
549
|
+
### Object Operations
|
|
550
|
+
|
|
551
|
+
Objects are plain key/value records. `id` is the only reserved field; everything else is application-defined.
|
|
552
|
+
|
|
553
|
+
| Method | Description |
|
|
554
|
+
|--------|-------------|
|
|
555
|
+
| `getObject(objectId): Promise<RoolObject \| undefined>` | Get object data, or undefined if not found. |
|
|
556
|
+
| `stat(objectId): Promise<RoolObjectStat \| undefined>` | Get object stat (audit info: modifiedAt, modifiedBy, modifiedByName), or undefined if not found. |
|
|
557
|
+
| `findObjects(options): Promise<{ objects, message }>` | Find objects using structured filters and natural language. Results sorted by modifiedAt (desc by default). |
|
|
558
|
+
| `getObjectIds(options?): string[]` | Get all object IDs. Sorted by modifiedAt (desc by default). Options: `{ limit?, order? }`. |
|
|
559
|
+
| `createObject(options): Promise<{ object, message }>` | Create a new object. Returns the object (with AI-filled content) and message. |
|
|
560
|
+
| `updateObject(objectId, options): Promise<{ object, message }>` | Update an existing object. Returns the updated object and message. |
|
|
561
|
+
| `deleteObjects(objectIds): Promise<void>` | Delete objects. Outbound links are removed automatically. |
|
|
562
|
+
|
|
563
|
+
#### createObject / updateObject Options
|
|
564
|
+
|
|
565
|
+
Both methods accept an options object:
|
|
566
|
+
|
|
567
|
+
| Option | Description |
|
|
568
|
+
|--------|-------------|
|
|
569
|
+
| `data` | Object data fields (any key-value pairs). Include `id` to use a custom ID (createObject only). Use `{{placeholder}}` for AI-generated content. Using `null`/`undefined` deletes a field. Fields prefixed with `_` are hidden from AI. Required for `createObject`, optional for `updateObject`. |
|
|
570
|
+
| `prompt` | Natural language instruction for AI to generate or modify content. |
|
|
571
|
+
| `ephemeral` | If true, the operation won't be recorded in conversation history. Useful for transient operations. |
|
|
572
|
+
|
|
573
|
+
#### findObjects Options
|
|
574
|
+
|
|
575
|
+
Find objects using structured filters, semantic matching, and natural language. All queries are executed server-side.
|
|
576
|
+
|
|
577
|
+
| Option | Description |
|
|
578
|
+
|--------|-------------|
|
|
579
|
+
| `where` | Structured field requirements. Static values = exact match. `{{placeholder}}` values = semantic match by AI. |
|
|
580
|
+
| `prompt` | Natural language query for additional filtering. |
|
|
581
|
+
| `limit` | Maximum number of results to return. |
|
|
582
|
+
| `objectIds` | Scope search to specific objects (like `prompt()`). |
|
|
583
|
+
| `order` | Sort order by modifiedAt: `'asc'` or `'desc'` (default: `'desc'`). |
|
|
584
|
+
| `ephemeral` | If true, the query won't be recorded in conversation history. Useful for responsive search. |
|
|
585
|
+
|
|
586
|
+
**Examples:**
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
// Exact field matching (no AI needed)
|
|
590
|
+
const { objects } = await space.findObjects({
|
|
591
|
+
where: { type: 'article', status: 'published' }
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
// Pure natural language query (AI interprets)
|
|
595
|
+
const { objects, message } = await space.findObjects({
|
|
596
|
+
prompt: 'articles about space exploration published this year'
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
// Semantic field matching with {{...}} placeholders
|
|
600
|
+
const { objects } = await space.findObjects({
|
|
601
|
+
where: {
|
|
602
|
+
type: 'product',
|
|
603
|
+
category: '{{something edible}}' // AI interprets this
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
// Combined: structured + semantic + natural language
|
|
608
|
+
const { objects } = await space.findObjects({
|
|
609
|
+
where: {
|
|
610
|
+
type: 'article',
|
|
611
|
+
topic: '{{related to climate}}'
|
|
612
|
+
},
|
|
613
|
+
prompt: 'that discuss solutions positively',
|
|
614
|
+
limit: 10
|
|
615
|
+
});
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
The AI has access to the full object graph context (except `_`-prefixed fields) when evaluating queries. The returned `message` explains why objects matched the criteria.
|
|
619
|
+
|
|
620
|
+
### Relations
|
|
621
|
+
|
|
622
|
+
| Method | Description |
|
|
623
|
+
|--------|-------------|
|
|
624
|
+
| `link(sourceId, relation, targetId): Promise<void>` | Add target to the named relation on source. Reads as "source.relation includes target". |
|
|
625
|
+
| `unlink(sourceId, relation?, targetId?): Promise<boolean>` | Remove relations. Three forms: `(source, relation, target)` removes one link, `(source, relation)` clears all targets for that relation, `(source)` clears all relations on source. |
|
|
626
|
+
| `getParents(objectId, relation?, options?): Promise<RoolObject[]>` | Get objects that link TO this object. Sorted by modifiedAt (desc by default). Options: `{ limit?, order? }`. |
|
|
627
|
+
| `getChildren(objectId, relation?, options?): Promise<RoolObject[]>` | Get objects this object links TO. Sorted by modifiedAt (desc by default). Options: `{ limit?, order? }`. |
|
|
628
|
+
|
|
629
|
+
**Examples:**
|
|
630
|
+
|
|
631
|
+
```typescript
|
|
632
|
+
// Create relations
|
|
633
|
+
await space.link(earth.id, 'orbits', sun.id);
|
|
634
|
+
await space.link(sun.id, 'hasPlanet', earth.id);
|
|
635
|
+
|
|
636
|
+
// This reads as:
|
|
637
|
+
// "earth.orbits includes sun"
|
|
638
|
+
// "sun.hasPlanet includes earth"
|
|
639
|
+
|
|
640
|
+
// Remove one specific link
|
|
641
|
+
await space.unlink(earth.id, 'orbits', sun.id);
|
|
642
|
+
|
|
643
|
+
// Clear all targets for a relation
|
|
644
|
+
await space.unlink(earth.id, 'orbits');
|
|
645
|
+
|
|
646
|
+
// Clear ALL relations on an object
|
|
647
|
+
await space.unlink(earth.id);
|
|
648
|
+
|
|
649
|
+
// Query relations
|
|
650
|
+
const planets = space.getChildren(sun.id, 'hasPlanet');
|
|
651
|
+
const stars = space.getParents(earth.id, 'orbits');
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### Undo/Redo
|
|
655
|
+
|
|
656
|
+
| Method | Description |
|
|
657
|
+
|--------|-------------|
|
|
658
|
+
| `checkpoint(label?): Promise<string>` | Call before mutations. Saves current state for undo. |
|
|
659
|
+
| `canUndo(): Promise<boolean>` | Check if undo available |
|
|
660
|
+
| `canRedo(): Promise<boolean>` | Check if redo available |
|
|
661
|
+
| `undo(): Promise<boolean>` | Undo to previous checkpoint |
|
|
662
|
+
| `redo(): Promise<boolean>` | Redo undone action |
|
|
663
|
+
| `clearHistory(): Promise<void>` | Clear undo/redo stack |
|
|
664
|
+
|
|
665
|
+
See [Checkpoints & Undo/Redo](#checkpoints--undoredo) for semantics.
|
|
666
|
+
|
|
667
|
+
### Space Metadata
|
|
668
|
+
|
|
669
|
+
Store arbitrary data alongside the Space without it being part of the graph content (e.g., viewport state, user preferences).
|
|
670
|
+
|
|
671
|
+
| Method | Description |
|
|
672
|
+
|--------|-------------|
|
|
673
|
+
| `setMetadata(key, value): void` | Set space-level metadata |
|
|
674
|
+
| `getMetadata(key): unknown` | Get metadata value, or undefined if key not set |
|
|
675
|
+
| `getAllMetadata(): Record<string, unknown>` | Get all metadata |
|
|
676
|
+
|
|
677
|
+
### Media
|
|
678
|
+
|
|
679
|
+
Media URLs in object fields are visible to AI. Both uploaded and AI-generated media work the same way — use `fetchMedia` to retrieve them for display.
|
|
680
|
+
|
|
681
|
+
| Method | Description |
|
|
682
|
+
|--------|-------------|
|
|
683
|
+
| `uploadMedia(file): Promise<string>` | Upload file, returns URL |
|
|
684
|
+
| `fetchMedia(url): Promise<MediaResponse>` | Fetch any URL, returns headers and blob() method (adds auth for backend URLs, works for external URLs too) |
|
|
685
|
+
| `deleteMedia(url): Promise<void>` | Delete media file by URL |
|
|
686
|
+
| `listMedia(): Promise<MediaInfo[]>` | List all media with metadata |
|
|
687
|
+
|
|
688
|
+
```typescript
|
|
689
|
+
// Upload an image
|
|
690
|
+
const url = await space.uploadMedia(file);
|
|
691
|
+
await space.createObject({ data: { title: 'Photo', image: url } });
|
|
692
|
+
|
|
693
|
+
// Or let AI generate one using a placeholder
|
|
694
|
+
await space.createObject({
|
|
695
|
+
data: { title: 'Mascot', image: '{{generate an image of a flying tortoise}}' }
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
// Display media (handles auth automatically)
|
|
699
|
+
const response = await space.fetchMedia(object.image);
|
|
700
|
+
if (response.contentType.startsWith('image/')) {
|
|
701
|
+
const blob = await response.blob();
|
|
702
|
+
img.src = URL.createObjectURL(blob);
|
|
703
|
+
}
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
### Import/Export
|
|
707
|
+
|
|
708
|
+
Export space data as JSON-LD for backup, portability, or migration:
|
|
709
|
+
|
|
710
|
+
| Method | Description |
|
|
711
|
+
|--------|-------------|
|
|
712
|
+
| `export(): JsonLdDocument` | Export all objects and relations as JSON-LD |
|
|
713
|
+
| `import(data): Promise<void>` | Import JSON-LD into empty space |
|
|
714
|
+
| `exportArchive(): Promise<Blob>` | Export objects, relations, and media as a zip archive |
|
|
715
|
+
| `importArchive(archive): Promise<void>` | Import from a zip archive into empty space |
|
|
716
|
+
|
|
717
|
+
**Export (data only):**
|
|
718
|
+
```typescript
|
|
719
|
+
const jsonld = space.export();
|
|
720
|
+
const json = JSON.stringify(jsonld, null, 2);
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
**Export (with media):**
|
|
724
|
+
```typescript
|
|
725
|
+
const archive = await space.exportArchive();
|
|
726
|
+
// Save as .zip file
|
|
727
|
+
const url = URL.createObjectURL(archive);
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
**Import (data only):**
|
|
731
|
+
```typescript
|
|
732
|
+
// Space must be empty
|
|
733
|
+
const newSpace = await client.createSpace('Imported Data');
|
|
734
|
+
await newSpace.import(jsonld);
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
**Import (with media):**
|
|
738
|
+
```typescript
|
|
739
|
+
// Space must be empty
|
|
740
|
+
const newSpace = await client.createSpace('Imported Data');
|
|
741
|
+
await newSpace.importArchive(archiveBlob);
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
The JSON-LD format follows W3C standards. The archive format bundles `data.json` (JSON-LD with media URLs rewritten to relative paths) and a `media/` folder containing the actual files. Space metadata and interaction history are not included in either format.
|
|
745
|
+
|
|
746
|
+
### Space Events
|
|
747
|
+
|
|
748
|
+
Semantic events describe what changed. Events fire for both local changes and remote changes.
|
|
749
|
+
|
|
750
|
+
```typescript
|
|
751
|
+
// source indicates origin:
|
|
752
|
+
// - 'local_user': This client made the change
|
|
753
|
+
// - 'remote_user': Another user/client made the change
|
|
754
|
+
// - 'remote_agent': AI agent made the change
|
|
755
|
+
// - 'system': Resync after error
|
|
756
|
+
|
|
757
|
+
// Object events
|
|
758
|
+
space.on('objectCreated', ({ objectId, object, source }) => void)
|
|
759
|
+
space.on('objectUpdated', ({ objectId, object, source }) => void)
|
|
760
|
+
space.on('objectDeleted', ({ objectId, source }) => void)
|
|
761
|
+
|
|
762
|
+
// Link events
|
|
763
|
+
space.on('linked', ({ sourceId, relation, targetId, source }) => void)
|
|
764
|
+
space.on('unlinked', ({ sourceId, relation, targetId, source }) => void)
|
|
765
|
+
|
|
766
|
+
// Space metadata
|
|
767
|
+
space.on('metadataUpdated', ({ metadata, source }) => void)
|
|
768
|
+
|
|
769
|
+
// Conversation updated (fetch with getInteractions())
|
|
770
|
+
space.on('conversationUpdated', ({ conversationId, source }) => void)
|
|
771
|
+
|
|
772
|
+
// Full state replacement (undo/redo, resync after error)
|
|
773
|
+
space.on('reset', ({ source }) => void)
|
|
774
|
+
|
|
775
|
+
// ConversationId was changed on the space
|
|
776
|
+
space.on('conversationIdChanged', ({ previousConversationId, newConversationId }) => void)
|
|
777
|
+
|
|
778
|
+
// Sync error occurred, space resynced from server
|
|
779
|
+
space.on('syncError', (error: Error) => void)
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
### Error Handling
|
|
783
|
+
|
|
784
|
+
AI operations may fail due to rate limiting or other transient errors. Check `error.message` for user-friendly error text:
|
|
785
|
+
|
|
786
|
+
```typescript
|
|
787
|
+
try {
|
|
788
|
+
await space.updateObject(objectId, { prompt: 'expand this' });
|
|
789
|
+
} catch (error) {
|
|
790
|
+
if (error.message.includes('temporarily unavailable')) {
|
|
791
|
+
showToast('Service busy, please try again in a moment');
|
|
792
|
+
} else {
|
|
793
|
+
showToast(error.message);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
### Internal / Advanced
|
|
799
|
+
|
|
800
|
+
| Method | Description |
|
|
801
|
+
|--------|-------------|
|
|
802
|
+
| `getData(): RoolSpaceData` | Get full space data (internal format) |
|
|
803
|
+
|
|
804
|
+
## Interaction History
|
|
805
|
+
|
|
806
|
+
Each `RoolSpace` instance has a `conversationId` that tracks interaction history for that space. The history records all meaningful interactions (prompts, object changes, links) as self-contained entries, each capturing the request and its result. History is stored in the space data itself and syncs in real-time to all clients.
|
|
807
|
+
|
|
808
|
+
### What the AI Receives
|
|
809
|
+
|
|
810
|
+
AI operations (`prompt`, `createObject`, `updateObject`, `findObjects`) automatically receive:
|
|
811
|
+
|
|
812
|
+
- **Interaction history** — Previous interactions and their results from this conversation
|
|
813
|
+
- **Recently modified objects** — Objects in the space recently created or changed
|
|
814
|
+
- **Selected objects** — Objects passed via `objectIds` are given primary focus
|
|
815
|
+
|
|
816
|
+
This context flows automatically — no configuration needed. The AI sees enough history to maintain coherent interactions while respecting the `_`-prefixed field hiding rules.
|
|
817
|
+
|
|
818
|
+
### Accessing History
|
|
819
|
+
|
|
820
|
+
```typescript
|
|
821
|
+
// Get interactions for the current conversationId
|
|
822
|
+
const interactions = space.getInteractions();
|
|
823
|
+
// Returns: Interaction[]
|
|
824
|
+
|
|
825
|
+
// Get interactions for a specific conversation ID
|
|
826
|
+
const interactions = space.getInteractionsById('other-conversation-id');
|
|
827
|
+
// Returns: Interaction[]
|
|
828
|
+
|
|
829
|
+
// List all conversation IDs that have interactions
|
|
830
|
+
const conversationIds = space.getConversationIds();
|
|
831
|
+
// Returns: string[]
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
### Conversation Access Methods
|
|
835
|
+
|
|
836
|
+
| Method | Description |
|
|
837
|
+
|--------|-------------|
|
|
838
|
+
| `getInteractions(): Interaction[]` | Get interactions for the current conversationId |
|
|
839
|
+
| `getInteractionsById(id): Interaction[]` | Get interactions for a specific conversation ID |
|
|
840
|
+
| `getConversationIds(): string[]` | List all conversation IDs that have conversations |
|
|
841
|
+
| `deleteConversation(conversationId?): Promise<void>` | Delete a conversation and its history. Defaults to current conversation. |
|
|
842
|
+
| `renameConversation(id, name): Promise<void>` | Rename a conversation. Creates it if it doesn't exist. |
|
|
843
|
+
| `listConversations(): Promise<ConversationInfo[]>` | List all conversations with summary info. |
|
|
844
|
+
|
|
845
|
+
### Listening for Updates
|
|
846
|
+
|
|
847
|
+
```typescript
|
|
848
|
+
space.on('conversationUpdated', ({ conversationId, source }) => {
|
|
849
|
+
// Conversation changed - refresh if needed
|
|
850
|
+
const interactions = space.getInteractions();
|
|
851
|
+
renderInteractions(interactions);
|
|
852
|
+
});
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
### Conversation Isolation
|
|
856
|
+
|
|
857
|
+
By default, each call to `openSpace()` or `createSpace()` generates a new `conversationId`. This means:
|
|
858
|
+
- Opening a space twice gives you two independent AI conversation histories
|
|
859
|
+
- Closing and reopening a space starts fresh
|
|
860
|
+
|
|
861
|
+
### Switching Conversations
|
|
862
|
+
|
|
863
|
+
Set `conversationId` to switch conversations without reopening the space:
|
|
864
|
+
|
|
865
|
+
```typescript
|
|
866
|
+
const space = await client.openSpace('abc123');
|
|
867
|
+
|
|
868
|
+
// User clicks "Research" thread in sidebar
|
|
869
|
+
space.conversationId = 'research-thread';
|
|
870
|
+
await space.prompt("Analyze this data");
|
|
871
|
+
|
|
872
|
+
// User clicks "Main" thread
|
|
873
|
+
space.conversationId = 'main-thread';
|
|
874
|
+
await space.prompt("Summarize findings");
|
|
875
|
+
|
|
876
|
+
// Listen for conversation switches
|
|
877
|
+
space.on('conversationIdChanged', ({ previousConversationId, newConversationId }) => {
|
|
878
|
+
// Re-render chat UI with new conversation's history
|
|
879
|
+
renderChat(space.getInteractions());
|
|
880
|
+
});
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
### Resuming Conversations
|
|
884
|
+
|
|
885
|
+
Pass a `conversationId` at open time to start with a specific conversation:
|
|
886
|
+
|
|
887
|
+
```typescript
|
|
888
|
+
// Resume a known conversation when opening
|
|
889
|
+
const space = await client.openSpace('abc123', { conversationId: 'research-thread' });
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
**Use cases:**
|
|
893
|
+
- **Page refresh** — Store `conversationId` in localStorage to maintain context across reloads
|
|
894
|
+
- **Multiple conversations** — Switch between different conversation contexts using the setter
|
|
895
|
+
- **Collaborative conversations** — Share a `conversationId` between users to enable shared AI conversation history
|
|
896
|
+
|
|
897
|
+
**Tip:** Use the user's id as `conversationId` to share context across tabs/devices, or a fixed string like `'shared'` to share context across all users.
|
|
898
|
+
|
|
899
|
+
Note: Interaction history is truncated to the most recent 50 entries to manage space size.
|
|
900
|
+
|
|
901
|
+
### The ai Field
|
|
902
|
+
|
|
903
|
+
The `ai` field in interactions distinguishes AI-generated responses from synthetic confirmations:
|
|
904
|
+
- `ai: true` — AI processed this operation (prompt, or createObject/updateObject with placeholders)
|
|
905
|
+
- `ai: false` — System confirmation only (e.g., "Linked X to Y", "Created object abc123")
|
|
906
|
+
|
|
907
|
+
### Tool Calls
|
|
908
|
+
|
|
909
|
+
The `toolCalls` array captures what the AI agent did during execution. Use it to build responsive UIs that show progress while the agent works — the `conversationUpdated` event fires as each tool completes, letting you display status updates or hints in real-time.
|
|
910
|
+
|
|
911
|
+
## Data Types
|
|
912
|
+
|
|
913
|
+
### Space Data
|
|
914
|
+
|
|
915
|
+
```typescript
|
|
916
|
+
// RoolObject represents the object data you work with
|
|
917
|
+
// Always contains `id`, plus any additional fields
|
|
918
|
+
// Fields prefixed with _ are hidden from AI
|
|
919
|
+
interface RoolObject {
|
|
920
|
+
id: string;
|
|
921
|
+
[key: string]: unknown;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// Object stat - audit information returned by space.stat()
|
|
925
|
+
interface RoolObjectStat {
|
|
926
|
+
modifiedAt: number;
|
|
927
|
+
modifiedBy: string;
|
|
928
|
+
modifiedByName: string | null;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// Conversation container with metadata
|
|
932
|
+
interface Conversation {
|
|
933
|
+
name?: string; // Conversation name (optional)
|
|
934
|
+
createdAt?: number; // Timestamp when conversation was created
|
|
935
|
+
interactions: Interaction[]; // Interaction history
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// Conversation summary info (returned by listConversations)
|
|
939
|
+
interface ConversationInfo {
|
|
940
|
+
id: string;
|
|
941
|
+
name: string | null;
|
|
942
|
+
createdAt: number | null;
|
|
943
|
+
interactionCount: number;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// Internal space data structure
|
|
947
|
+
interface RoolSpaceData {
|
|
948
|
+
version: number; // Monotonically increasing version for sync consistency
|
|
949
|
+
objects: Record<string, RoolObjectEntry>;
|
|
950
|
+
meta: Record<string, unknown>; // Space-level metadata
|
|
951
|
+
conversations?: Record<string, Conversation>; // Conversations keyed by conversationId
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// Full stored object structure (for advanced use with getData())
|
|
955
|
+
interface RoolObjectEntry {
|
|
956
|
+
links: Record<string, string[]>; // relation -> [targetId1, targetId2, ...]
|
|
957
|
+
data: RoolObject; // The actual object data
|
|
958
|
+
modifiedAt: number; // Timestamp of last modification
|
|
959
|
+
modifiedBy: string; // User ID who last modified
|
|
960
|
+
modifiedByName: string | null; // Display name at time of modification
|
|
961
|
+
}
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
### Interaction Types
|
|
965
|
+
|
|
966
|
+
```typescript
|
|
967
|
+
interface ToolCall {
|
|
968
|
+
name: string; // Tool name (e.g., "create_object", "link", "search_web")
|
|
969
|
+
input: unknown; // Arguments passed to the tool
|
|
970
|
+
result: string; // Truncated result (max 500 chars)
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
interface Interaction {
|
|
974
|
+
id: string; // Unique ID for this interaction
|
|
975
|
+
timestamp: number;
|
|
976
|
+
userId: string; // Who performed this interaction
|
|
977
|
+
userName?: string | null; // Display name at time of interaction
|
|
978
|
+
operation: 'prompt' | 'createObject' | 'updateObject' | 'link' | 'unlink' | 'deleteObjects';
|
|
979
|
+
input: string; // What the user did: prompt text or action description
|
|
980
|
+
output: string | null; // Result: AI response or confirmation message (null while in-progress)
|
|
981
|
+
ai: boolean; // Whether AI was invoked (vs synthetic confirmation)
|
|
982
|
+
modifiedObjectIds: string[]; // Objects affected by this interaction
|
|
983
|
+
toolCalls: ToolCall[]; // Tools called during this interaction (for AI prompts)
|
|
984
|
+
}
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
### Info Types
|
|
988
|
+
|
|
989
|
+
```typescript
|
|
990
|
+
type RoolUserRole = 'owner' | 'editor' | 'viewer';
|
|
991
|
+
|
|
992
|
+
interface RoolSpaceInfo { id: string; name: string; role: RoolUserRole; ownerId: string; size: number; createdAt: string; updatedAt: string; }
|
|
993
|
+
interface SpaceMember { id: string; email: string; role: RoolUserRole; }
|
|
994
|
+
interface UserResult { id: string; email: string; name: string | null; }
|
|
995
|
+
interface CurrentUser { id: string; email: string; name: string | null; plan: string; creditsBalance: number; createdAt: string; lastActivity: string; processedAt: string; storage: Record<string, unknown>; }
|
|
996
|
+
interface MediaInfo { uuid: string; url: string; contentType: string; size: number; createdAt: string; }
|
|
997
|
+
interface MediaResponse { contentType: string; size: number | null; blob(): Promise<Blob>; }
|
|
998
|
+
type ChangeSource = 'local_user' | 'remote_user' | 'remote_agent' | 'system';
|
|
999
|
+
```
|
|
1000
|
+
|
|
1001
|
+
### Prompt Options
|
|
1002
|
+
|
|
1003
|
+
```typescript
|
|
1004
|
+
type PromptEffort = 'QUICK' | 'STANDARD' | 'REASONING' | 'RESEARCH';
|
|
1005
|
+
|
|
1006
|
+
interface PromptOptions {
|
|
1007
|
+
objectIds?: string[]; // Scope to specific objects
|
|
1008
|
+
responseSchema?: Record<string, unknown>;
|
|
1009
|
+
effort?: PromptEffort; // Effort level (default: 'STANDARD')
|
|
1010
|
+
ephemeral?: boolean; // Don't record in conversation history
|
|
1011
|
+
readOnly?: boolean; // Disable mutation tools (default: false)
|
|
1012
|
+
}
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
## Patterns & Examples
|
|
1016
|
+
|
|
1017
|
+
A Rool Space is a persistent, shared world model. Applications project different interaction patterns onto the same core primitives:
|
|
1018
|
+
|
|
1019
|
+
- **Objects and relations** store durable state
|
|
1020
|
+
- **Interaction history** tracks what happened (requests, results, modified objects)
|
|
1021
|
+
- **Events** describe what changed in real-time
|
|
1022
|
+
|
|
1023
|
+
Below are a few representative patterns.
|
|
1024
|
+
|
|
1025
|
+
### Chat With Generated Artifacts
|
|
1026
|
+
|
|
1027
|
+
- **Space**: documents, notes, images, tasks as objects
|
|
1028
|
+
- **Interaction history**: prompts and AI responses stored in space, synced across clients
|
|
1029
|
+
- **UI**: renders interactions from `getInteractions()` as chat; derives artifact lists from object events
|
|
1030
|
+
|
|
1031
|
+
**Pattern**
|
|
1032
|
+
- Interaction history syncs in real-time; UI renders entries as chat bubbles
|
|
1033
|
+
- Artifacts are persistent objects
|
|
1034
|
+
- Listen to `conversationUpdated` to update chat UI
|
|
1035
|
+
- Selecting objects defines the AI working set via `objectIds`
|
|
1036
|
+
|
|
1037
|
+
### Multi-User World / Text Adventure
|
|
1038
|
+
|
|
1039
|
+
- **Space**: rooms, items, NPCs, players as objects
|
|
1040
|
+
- **Relations**: navigation, containment, location
|
|
1041
|
+
- **Conversation**: player commands and narrative continuity
|
|
1042
|
+
|
|
1043
|
+
**Pattern**
|
|
1044
|
+
- The space is the shared world state
|
|
1045
|
+
- Objects can be created dynamically as the world expands
|
|
1046
|
+
- AI generates descriptions and events using `{{placeholders}}`
|
|
1047
|
+
|
|
1048
|
+
### Collaborative Knowledge Graph
|
|
1049
|
+
|
|
1050
|
+
- **Space**: concepts, sources, hypotheses as objects
|
|
1051
|
+
- **Relations**: semantic links between them
|
|
1052
|
+
- **Conversation**: exploratory analysis and questioning
|
|
1053
|
+
|
|
1054
|
+
**Pattern**
|
|
1055
|
+
- Graph structure lives in relations
|
|
1056
|
+
- AI operates on selected subgraphs via `objectIds`
|
|
1057
|
+
- Analysis results are stored; reasoning steps are transient
|
|
1058
|
+
|
|
1059
|
+
### Common Design Invariants
|
|
1060
|
+
|
|
1061
|
+
- Durable content lives in space objects and relations
|
|
1062
|
+
- Interaction history lives in space conversations (persistent, synced, truncated to 50 entries)
|
|
1063
|
+
- UI state lives in the client, space metadata, or `_`-prefixed fields
|
|
1064
|
+
- AI focus is controlled by object selection, not by replaying history
|
|
1065
|
+
|
|
1066
|
+
## License
|
|
1067
|
+
|
|
1068
|
+
This client is intended for use with the Rool platform and requires an account. Access is currently by invitation.
|
|
1069
|
+
|
|
1070
|
+
Proprietary — © Lightpost One. All rights reserved.
|