@rool-dev/client 0.6.5 → 0.6.6-dev.3e5f7f7

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 CHANGED
@@ -6,12 +6,48 @@ Rool Spaces enables you to build applications where AI operates on a structured
6
6
 
7
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
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
+
9
16
  ## Installation
10
17
 
11
18
  ```bash
12
19
  npm install @rool-dev/client
13
20
  ```
14
21
 
22
+ ## Configuration
23
+
24
+ ```typescript
25
+ import { RoolClient } from '@rool-dev/client';
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
+
15
51
  ## Quick Start
16
52
 
17
53
  ```typescript
@@ -23,7 +59,7 @@ const client = new RoolClient({
23
59
  });
24
60
 
25
61
  // Process auth callbacks if we are returning from the login page
26
- client.initialize();
62
+ client.initialize();
27
63
 
28
64
  if (!client.isAuthenticated()) {
29
65
  client.login(); // Redirects to auth page
@@ -44,7 +80,7 @@ space.on('linked', ({ sourceId, relation, targetId, source }) => {
44
80
  console.log('New link:', sourceId, '->', targetId, `[${relation}]`, 'from', source);
45
81
  });
46
82
 
47
- // Create objects with AI-generated content.
83
+ // Create objects with AI-generated content.
48
84
  // Field names are yours to define — only 'id' is reserved.
49
85
  const { object: sun } = await space.createObject({
50
86
  data: {
@@ -79,31 +115,158 @@ await space.redo(); // The link is back ...
79
115
  space.close();
80
116
  ```
81
117
 
82
- ## Configuration
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.
83
123
 
84
124
  ```typescript
85
- interface RoolClientConfig {
86
- baseUrl: string; // Base URL (e.g., 'https://api.dev.rool.dev')
87
- graphqlUrl?: string; // Override GraphQL endpoint (default: {baseUrl}/graphql)
88
- mediaUrl?: string; // Override media endpoint (default: {baseUrl}/media)
89
- authUrl?: string; // Override auth endpoint (default: {baseUrl}/auth)
90
- authProvider?: AuthProvider; // Optional, defaults to browser auth
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
91
193
  }
92
194
  ```
93
195
 
94
- ### Base URLs
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.
95
197
 
96
- | Environment | URL |
97
- |-------------|-----|
98
- | Development | `https://api.dev.rool.dev` |
99
- | Production | `https://api.rool.dev` |
198
+ In collaborative scenarios, conflicting changes (modified by others since your checkpoint) are silently skipped.
100
199
 
101
- ### Auth Providers
200
+ ### Hidden Fields
102
201
 
103
- **Browser (default)**No configuration needed. Uses localStorage for tokens, redirects to login page.
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:
104
203
 
105
204
  ```typescript
106
- const client = new RoolClient({ baseUrl: 'https://api.dev.rool.dev' });
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' });
107
270
  client.initialize(); // Process auth callbacks if this is a callback from the auth page
108
271
 
109
272
  if (!client.isAuthenticated()) {
@@ -111,13 +274,15 @@ if (!client.isAuthenticated()) {
111
274
  }
112
275
  ```
113
276
 
114
- **Node.js** — For CLI tools and scripts. Stores credentials in `~/.config/rool/`, opens browser for login.
277
+ ### Node.js
278
+
279
+ For CLI tools and scripts. Stores credentials in `~/.config/rool/`, opens browser for login.
115
280
 
116
281
  ```typescript
117
282
  import { NodeAuthProvider } from '@rool-dev/client/node';
118
283
 
119
284
  const client = new RoolClient({
120
- baseUrl: 'https://api.dev.rool.dev',
285
+ baseUrl: 'https://api.rool.dev',
121
286
  authProvider: new NodeAuthProvider()
122
287
  });
123
288
 
@@ -126,128 +291,172 @@ if (!client.isAuthenticated()) {
126
291
  }
127
292
  ```
128
293
 
129
- **Custom** Implement the `AuthProvider` interface for full control.
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(): void` | Redirect to login page |
300
+ | `logout(): void` | Clear tokens and state |
301
+ | `isAuthenticated(): boolean` | Check auth status |
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.
130
308
 
131
309
  ```typescript
132
- const client = new RoolClient({
133
- baseUrl: 'https://api.dev.rool.dev',
134
- authProvider: {
135
- initialize: () => false,
136
- getToken: async () => myStore.getAccessToken(),
137
- getUser: () => ({ email: 'user@example.com', name: 'User' }),
138
- isAuthenticated: () => myStore.hasValidToken(),
139
- login: async () => { /* your login flow */ },
140
- logout: () => myStore.clear(),
141
- }
142
- });
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);
143
315
  ```
144
316
 
145
- ---
317
+ Use `checkpoint()` before prompting to make operations undoable.
146
318
 
147
- ## Interaction History and AI Context
319
+ ### Method Signature
148
320
 
149
- 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.
321
+ ```typescript
322
+ prompt(text: string, options?: PromptOptions): Promise<{ message: string; objects: RoolObject[] }>
323
+ ```
150
324
 
151
- AI operations (`prompt`, `createObject`, `updateObject`, `findObjects`) automatically receive:
325
+ Returns a message (the AI's response) and the list of objects that were created or modified.
152
326
 
153
- - **Interaction history** — Previous interactions and their results from this conversation
154
- - **Recently modified objects** — Objects created or changed during this conversation
155
- - **Selected objects** — Objects passed via `objectIds` are given primary focus
327
+ ### Options
156
328
 
157
- This context flows automatically — no configuration needed. The AI sees enough history to maintain coherent interactions while respecting the `_`-prefixed field hiding rules.
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) |
158
335
 
159
- ### Accessing Interaction History
336
+ ### Effort Levels
160
337
 
161
- Interaction history is stored in the space and accessible via the Space API:
338
+ | Level | Description |
339
+ |-------|-------------|
340
+ | `QUICK` | Fast responses, read-only. Cannot create/update objects or links. Use for questions. |
341
+ | `STANDARD` | Default behavior with full capabilities. |
342
+ | `REASONING` | Extended reasoning for complex tasks. |
343
+ | `RESEARCH` | Pre-analysis and context gathering (reserved for future use). |
344
+
345
+ ### Examples
162
346
 
163
347
  ```typescript
164
- // Get interactions for the current conversationId
165
- const interactions = space.getInteractions();
166
- // Returns: Interaction[]
348
+ // Reorganize and link existing objects
349
+ const { objects } = await space.prompt(
350
+ "Group these notes by topic and create a parent node for each group."
351
+ );
167
352
 
168
- // Get interactions for a specific conversation ID
169
- const interactions = space.getInteractionsById('other-conversation-id');
170
- // Returns: Interaction[]
353
+ // Work with specific objects
354
+ const result = await space.prompt(
355
+ "Summarize these articles",
356
+ { objectIds: ['article-1', 'article-2'] }
357
+ );
171
358
 
172
- // List all conversation IDs that have interactions
173
- const conversationIds = space.getConversationIds();
174
- // Returns: string[]
175
- ```
359
+ // Quick question without mutations
360
+ const { message } = await space.prompt(
361
+ "What topics are covered?",
362
+ { effort: 'QUICK' }
363
+ );
176
364
 
177
- Each entry is a self-contained interaction record:
178
- ```typescript
179
- interface Interaction {
180
- timestamp: number;
181
- userId: string; // Who performed this interaction
182
- userName?: string | null; // Display name at time of interaction
183
- operation: 'prompt' | 'createObject' | 'updateObject' | 'link' | 'unlink' | 'deleteObjects';
184
- input: string; // What the user did: prompt text or action description
185
- output: string; // Result: AI response or confirmation message
186
- ai: boolean; // Whether AI was invoked (vs synthetic confirmation)
187
- modifiedObjectIds: string[]; // Objects affected by this interaction
188
- }
365
+ // Complex analysis with extended reasoning
366
+ await space.prompt(
367
+ "Analyze relationships and reorganize",
368
+ { effort: 'REASONING' }
369
+ );
189
370
  ```
190
371
 
191
- The `ai` field distinguishes AI-generated responses from synthetic confirmations:
192
- - `ai: true` — AI processed this operation (prompt, or createObject/updateObject with placeholders)
193
- - `ai: false` — System confirmation only (e.g., "Linked X to Y", "Created object abc123")
194
-
195
- ### Interaction History Events
372
+ ### Structured Responses
196
373
 
197
- Listen for history updates:
374
+ Use `responseSchema` to get structured JSON instead of a text message:
198
375
 
199
376
  ```typescript
200
- space.on('interactionsUpdated', ({ conversationId, source }) => {
201
- // Interaction history changed - refresh if needed
202
- const interactions = space.getInteractions();
203
- renderInteractions(interactions);
377
+ const { message } = await space.prompt("Categorize these items", {
378
+ objectIds: ['item-1', 'item-2', 'item-3'],
379
+ responseSchema: {
380
+ type: 'object',
381
+ properties: {
382
+ categories: {
383
+ type: 'array',
384
+ items: { type: 'string' }
385
+ },
386
+ summary: { type: 'string' }
387
+ }
388
+ }
204
389
  });
390
+
391
+ const result = JSON.parse(message);
392
+ console.log(result.categories, result.summary);
205
393
  ```
206
394
 
207
- ### Conversation Isolation
395
+ ### Context Flow
208
396
 
209
- By default, each call to `openSpace()` or `createSpace()` generates a new `conversationId`. This means:
210
- - Opening a space twice gives you two independent AI conversation histories
211
- - Closing and reopening a space starts fresh
397
+ AI operations automatically receive context:
398
+ - **Interaction history** Previous interactions and their results from this conversation
399
+ - **Recently modified objects** Objects created or changed recently
400
+ - **Selected objects** — Objects passed via `objectIds` are given primary focus
212
401
 
213
- ### Resuming Conversations
402
+ This context flows automatically — no configuration needed. The AI sees enough history to maintain coherent interactions while respecting the `_`-prefixed field hiding rules.
214
403
 
215
- Pass a `conversationId` to continue a previous conversation:
404
+ ## Collaboration
405
+
406
+ ### Adding Users to a Space
407
+
408
+ To add a user to a space, you need their user ID. Use `searchUser()` to find them by email:
216
409
 
217
410
  ```typescript
218
- // First conversation - save the conversationId
219
- const space = await client.openSpace('abc123');
220
- const savedConversationId = space.conversationId;
221
- space.close();
411
+ // Find the user by email
412
+ const user = await client.searchUser('colleague@example.com');
413
+ if (!user) {
414
+ throw new Error('User not found');
415
+ }
222
416
 
223
- // Later - resume the same conversation
224
- const resumedSpace = await client.openSpace('abc123', { conversationId: savedConversationId });
225
- // AI now has access to conversation history from before
417
+ // Add them to the space
418
+ await space.addUser(user.id, 'editor');
226
419
  ```
227
420
 
228
- **Use cases:**
229
- - **Page refresh** — Store `conversationId` in localStorage to maintain context across reloads
230
- - **Multiple conversations** — Maintain and switch between different conversation contexts with the same space
231
- - **Collaborative conversations** — Share a `conversationId` between users to enable shared AI conversation history
232
-
233
- **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.
421
+ ### Roles
234
422
 
235
- Note: Interaction history is truncated to the most recent 50 entries to manage space size.
423
+ | Role | Capabilities |
424
+ |------|--------------|
425
+ | `owner` | Full control, can delete space and manage users |
426
+ | `editor` | Can create, modify, delete objects and links |
427
+ | `viewer` | Read-only access |
236
428
 
237
- ---
429
+ ### Space Collaboration Methods
238
430
 
239
- ## RoolClient API
431
+ | Method | Description |
432
+ |--------|-------------|
433
+ | `listUsers(): Promise<SpaceMember[]>` | List users with access |
434
+ | `addUser(userId, role): Promise<void>` | Add user to space |
435
+ | `removeUser(userId): Promise<void>` | Remove user from space |
240
436
 
241
- ### Authentication
437
+ ### Client User Methods
242
438
 
243
439
  | Method | Description |
244
440
  |--------|-------------|
245
- | `initialize(): boolean` | **Call on app startup if running in browser.** Processes auth callback from URL, sets up token refresh. |
246
- | `login(): void` | Redirect to login page |
247
- | `logout(): void` | Clear tokens and state |
248
- | `isAuthenticated(): boolean` | Check auth status |
249
- | `getToken(): Promise<string \| undefined>` | Get current access token |
250
- | `getAuthUser(): AuthUser` | Get auth identity from JWT (`{ email, name }`) |
441
+ | `getCurrentUser(): Promise<CurrentUser>` | Get current Rool user (id, email, name, plan, credits, storage) |
442
+ | `searchUser(email): Promise<UserResult \| null>` | Find user by exact email address (no partial matching) |
443
+
444
+ ### Real-time Collaboration
445
+
446
+ When multiple users have a space open, changes sync in real-time. The `source` field in events tells you who made the change:
447
+
448
+ ```typescript
449
+ space.on('objectUpdated', ({ objectId, object, source }) => {
450
+ if (source === 'remote_user') {
451
+ // Another user made this change
452
+ showCollaboratorActivity(object);
453
+ }
454
+ });
455
+ ```
456
+
457
+ See [Real-time Sync](#real-time-sync) for more on event sources.
458
+
459
+ ## RoolClient API
251
460
 
252
461
  ### Space Lifecycle
253
462
 
@@ -256,14 +465,7 @@ Note: Interaction history is truncated to the most recent 50 entries to manage s
256
465
  | `listSpaces(): Promise<RoolSpaceInfo[]>` | List available spaces |
257
466
  | `openSpace(id, options?): Promise<RoolSpace>` | Open a space for editing. Options: `{ conversationId?: string }` |
258
467
  | `createSpace(name?, options?): Promise<RoolSpace>` | Create a new space. Options: `{ conversationId?: string }` |
259
- | `deleteSpace(id): Promise<void>` | Delete a space |
260
-
261
- ### Current User
262
-
263
- | Method | Description |
264
- |--------|-------------|
265
- | `getCurrentUser(): Promise<CurrentUser>` | Get current Rool user (id, email, name, plan, credits, storage) |
266
- | `searchUser(email): Promise<UserResult \| null>` | Search user by email (returns id, email, name) |
468
+ | `deleteSpace(id): Promise<void>` | Permanently delete a space (cannot be undone) |
267
469
 
268
470
  ### User Storage
269
471
 
@@ -293,10 +495,11 @@ client.setUserStorage('sidebar', { collapsed: true, width: 280 });
293
495
  // Delete a key
294
496
  client.setUserStorage('theme', null);
295
497
 
296
- // React to changes (from this tab, other tabs, or other devices)
498
+ // The cache may be stale from a previous session listen for updates
499
+ // to apply fresh values once sync completes (or when other tabs/devices change values)
297
500
  client.on('userStorageChanged', ({ key, value, source }) => {
501
+ // source: 'local' (this client) or 'remote' (server/other client)
298
502
  if (key === 'theme') applyTheme(value as string);
299
- console.log(`Storage updated from ${source}`); // 'local' or 'remote'
300
503
  });
301
504
  ```
302
505
 
@@ -320,8 +523,6 @@ client.on('connectionStateChanged', (state: 'connected' | 'disconnected' | 'reco
320
523
  client.on('error', (error: Error, context?: string) => void)
321
524
  ```
322
525
 
323
- ---
324
-
325
526
  ## RoolSpace API
326
527
 
327
528
  Spaces are first-class objects with built-in undo/redo, event emission, and real-time sync.
@@ -334,57 +535,15 @@ Spaces are first-class objects with built-in undo/redo, event emission, and real
334
535
  | `name: string` | Space name |
335
536
  | `role: RoolUserRole` | User's role (`'owner' \| 'editor' \| 'viewer'`) |
336
537
  | `userId: string` | Current user's ID |
337
- | `conversationId: string` | ID for interaction history (tracks AI context) |
338
- | `isReadOnly(): boolean` | True if viewer role |
339
-
340
- ### Conversation Access
341
-
342
- | Method | Description |
343
- |--------|-------------|
344
- | `getInteractions(): Interaction[]` | Get interactions for the current conversationId |
345
- | `getInteractionsById(id): Interaction[]` | Get interactions for a specific conversation ID |
346
- | `getConversationIds(): string[]` | List all conversation IDs that have interactions |
347
- | `clearInteractions(conversationId?): Promise<void>` | Clear interaction history. Defaults to current conversation. |
348
-
349
- ### Lifecycle
350
-
351
- | Method | Description |
352
- |--------|-------------|
353
- | `close(): void` | Clean up resources and stop receiving updates |
354
- | `rename(newName): Promise<void>` | Rename the space |
355
-
356
- ### Undo/Redo
357
-
358
- | Method | Description |
359
- |--------|-------------|
360
- | `checkpoint(label?): Promise<string>` | Call before mutations. Saves current state for undo. |
361
- | `canUndo(): Promise<boolean>` | Check if undo available |
362
- | `canRedo(): Promise<boolean>` | Check if redo available |
363
- | `undo(): Promise<boolean>` | Undo to previous checkpoint |
364
- | `redo(): Promise<boolean>` | Redo undone action |
365
- | `clearHistory(): Promise<void>` | Clear undo/redo stack |
366
-
367
- Call `checkpoint()` before making changes to create a restore point. Without a checkpoint, `undo()` has nothing to restore to.
368
-
369
- Undo always restores the space to the last checkpoint, regardless of how many changes were made since.
370
-
371
- In collaborative scenarios, conflicting changes (modified by others since checkpoint) are silently skipped.
372
-
373
- ```typescript
374
- // Create a checkpoint before user action
375
- await space.checkpoint('Delete object');
376
- await space.deleteObjects([objectId]);
377
-
378
- // User can now undo back to the checkpoint
379
- if (await space.canUndo()) {
380
- await space.undo(); // Restores the deleted object
381
- }
538
+ | `conversationId: string` | ID for interaction history (tracks AI context) |
539
+ | `isReadOnly(): boolean` | True if viewer role |
382
540
 
383
- // Redo reapplies the undone action
384
- if (await space.canRedo()) {
385
- await space.redo(); // Deletes the object again
386
- }
387
- ```
541
+ ### Lifecycle
542
+
543
+ | Method | Description |
544
+ |--------|-------------|
545
+ | `close(): void` | Clean up resources and stop receiving updates |
546
+ | `rename(newName): Promise<void>` | Rename the space |
388
547
 
389
548
  ### Object Operations
390
549
 
@@ -392,8 +551,7 @@ Objects are plain key/value records. `id` is the only reserved field; everything
392
551
 
393
552
  | Method | Description |
394
553
  |--------|-------------|
395
- | `getObject(objectId): RoolObject` | Get object data. Throws if not found. |
396
- | `getObjectOrUndefined(objectId): RoolObject \| undefined` | Get object data, or undefined if not found. |
554
+ | `getObject(objectId): RoolObject \| undefined` | Get object data, or undefined if not found. |
397
555
  | `findObjects(options): Promise<{ objects, message }>` | Find objects using structured filters and natural language. Server-side execution with AI. |
398
556
  | `getObjectIds(): string[]` | Get all object IDs in the space. |
399
557
  | `createObject(options): Promise<{ object, message }>` | Create a new object. Returns the object (with AI-filled content) and message. |
@@ -424,28 +582,28 @@ Find objects using structured filters, semantic matching, and natural language.
424
582
 
425
583
  ```typescript
426
584
  // Exact field matching (no AI needed)
427
- const { objects } = await space.findObjects({
428
- where: { type: 'article', status: 'published' }
585
+ const { objects } = await space.findObjects({
586
+ where: { type: 'article', status: 'published' }
429
587
  });
430
588
 
431
589
  // Pure natural language query (AI interprets)
432
- const { objects, message } = await space.findObjects({
433
- prompt: 'articles about space exploration published this year'
590
+ const { objects, message } = await space.findObjects({
591
+ prompt: 'articles about space exploration published this year'
434
592
  });
435
593
 
436
594
  // Semantic field matching with {{...}} placeholders
437
- const { objects } = await space.findObjects({
438
- where: {
595
+ const { objects } = await space.findObjects({
596
+ where: {
439
597
  type: 'product',
440
598
  category: '{{something edible}}' // AI interprets this
441
599
  }
442
600
  });
443
601
 
444
602
  // Combined: structured + semantic + natural language
445
- const { objects } = await space.findObjects({
446
- where: {
603
+ const { objects } = await space.findObjects({
604
+ where: {
447
605
  type: 'article',
448
- topic: '{{related to climate}}'
606
+ topic: '{{related to climate}}'
449
607
  },
450
608
  prompt: 'that discuss solutions positively',
451
609
  limit: 10
@@ -454,71 +612,8 @@ const { objects } = await space.findObjects({
454
612
 
455
613
  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.
456
614
 
457
- #### Underscore-Prefixed Fields
458
-
459
- 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:
460
-
461
- ```typescript
462
- await space.createObject({
463
- data: {
464
- title: 'My Article',
465
- author: "John Doe",
466
- _ui: { x: 100, y: 200, collapsed: false }
467
- }
468
- });
469
- ```
470
-
471
- #### Custom Object IDs
472
-
473
- By default, `createObject` generates a 6-character alphanumeric ID. Provide your own via `data.id`:
474
-
475
- ```typescript
476
- await space.createObject({ data: { id: 'article-42', title: 'The Meaning of Life' } });
477
- ```
478
-
479
- **Constraints:**
480
- - Must contain only alphanumeric characters, hyphens (`-`), and underscores (`_`)
481
- - Must be unique within the space (throws if ID exists)
482
- - Cannot be changed after creation (immutable)
483
-
484
- If generating your own IDs, use `RoolClient.generateId()` for efficient, guaranteed-valid IDs.
485
-
486
- #### AI Placeholder Pattern
487
-
488
- Use `{{description}}` in field values to have AI generate content:
489
-
490
- ```typescript
491
- // Create with AI-generated content
492
- await space.createObject({
493
- data: {
494
- type: 'article',
495
- headline: '{{catchy headline about coffee}}',
496
- body: '{{informative paragraph}}'
497
- },
498
- prompt: 'Write about specialty coffee brewing'
499
- });
500
-
501
- // Update existing content with AI
502
- await space.updateObject('abc123', {
503
- prompt: 'Make the body shorter and more casual'
504
- });
505
-
506
- // Add new AI-generated field to existing object
507
- await space.updateObject('abc123', {
508
- data: { summary: '{{one-sentence summary}}' }
509
- });
510
- ```
511
-
512
- 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.
513
-
514
- AI-generated placeholders are resolved during the mutation and replaced with concrete values. The {{...}} syntax is not stored — it is only used to guide the agent while creating or updating the object.
515
-
516
615
  ### Relations
517
616
 
518
- Relations connect objects directionally through named relations on the source object. Each relation name represents a multi-valued reference set on the source.
519
-
520
- Internally, relations are indexed and idempotent. Creating the same relation twice has no effect and produces no duplicates.
521
-
522
617
  | Method | Description |
523
618
  |--------|-------------|
524
619
  | `link(sourceId, relation, targetId): Promise<void>` | Add target to the named relation on source. Reads as "source.relation includes target". |
@@ -551,89 +646,28 @@ const planets = space.getChildren(sun.id, 'hasPlanet');
551
646
  const stars = space.getParents(earth.id, 'orbits');
552
647
  ```
553
648
 
554
- **Notes:**
555
- - Relation names are application-defined strings
556
- - Relations are not stored in object data fields
557
- - Only relations created via `link()` participate in traversal (`getParents`, `getChildren`) and indexing
558
- - Storing object IDs inside regular object fields is allowed, but those references are opaque to the system
559
-
560
- ### Space Metadata
561
-
562
- Store arbitrary data alongside the Space without it being part of the graph content (e.g., viewport state, user preferences).
563
-
564
- | Method | Description |
565
- |--------|-------------|
566
- | `setMetadata(key, value): void` | Set space-level metadata |
567
- | `getMetadata(key): unknown` | Get metadata value |
568
- | `getAllMetadata(): Record<string, unknown>` | Get all metadata |
569
-
570
- ### Import/Export
571
-
572
- Export space data as JSON-LD for backup, portability, or migration:
573
-
574
- | Method | Description |
575
- |--------|-------------|
576
- | `export(): JsonLdDocument` | Export all objects and relations as JSON-LD |
577
- | `import(data): Promise<void>` | Import JSON-LD into empty space |
578
-
579
- **Export:**
580
- ```typescript
581
- const jsonld = space.export();
582
- // Save to file, send to API, etc.
583
- const json = JSON.stringify(jsonld, null, 2);
584
- ```
585
-
586
- **Import:**
587
- ```typescript
588
- // Space must be empty
589
- const newSpace = await client.createSpace('Imported Data');
590
- await newSpace.import(jsonld);
591
- ```
592
-
593
- The JSON-LD format follows W3C standards and can be processed by standard JSON-LD tools. Objects are exported with their data and relations; space metadata and interaction history are not included.
594
-
595
- ### AI Agent
649
+ ### Undo/Redo
596
650
 
597
651
  | Method | Description |
598
652
  |--------|-------------|
599
- | `prompt(prompt, options?): Promise<{ message: string; objects: RoolObject[] }>` | Invoke the AI agent to manipulate the space. Returns a message and the list of objects that were created or modified. |
600
-
601
- The agent has editor-level capabilities — it can create, modify, delete, link, and research — but cannot modify `_`-prefixed fields. Use `checkpoint()` before prompting to make operations undoable.
602
-
603
- | Option | Description |
604
- |--------|-------------|
605
- | `objectIds` | Limit context to specific objects |
606
- | `responseSchema` | Request structured JSON instead of text summary |
607
- | `effort` | Effort level: `'QUICK'`, `'STANDARD'` (default), `'REASONING'`, or `'RESEARCH'` |
608
- | `ephemeral` | If true, don't record in conversation history (useful for tab completion) |
609
-
610
- **Effort levels:**
611
- - `QUICK` — Fast responses, read-only (cannot create/update objects or links)
612
- - `STANDARD` — Default behavior with full capabilities
613
- - `REASONING` — Extended reasoning for complex tasks
614
- - `RESEARCH` — Pre-analysis and context gathering (reserved for future use)
615
-
616
- ```typescript
617
- const { message, objects } = await space.prompt("Create a topic node for the solar system, then child nodes for each planet.");
618
- console.log(`AI: ${message}`);
619
- console.log(`Modified ${objects.length} objects:`, objects);
620
-
621
- const result = await space.prompt("Summarize these articles", { objectIds: ['article-1', 'article-2'] });
653
+ | `checkpoint(label?): Promise<string>` | Call before mutations. Saves current state for undo. |
654
+ | `canUndo(): Promise<boolean>` | Check if undo available |
655
+ | `canRedo(): Promise<boolean>` | Check if redo available |
656
+ | `undo(): Promise<boolean>` | Undo to previous checkpoint |
657
+ | `redo(): Promise<boolean>` | Redo undone action |
658
+ | `clearHistory(): Promise<void>` | Clear undo/redo stack |
622
659
 
623
- // Quick question without mutations
624
- const { message } = await space.prompt("What topics are covered?", { effort: 'QUICK' });
660
+ See [Checkpoints & Undo/Redo](#checkpoints--undoredo) for semantics.
625
661
 
626
- // Complex analysis with extended reasoning
627
- await space.prompt("Analyze relationships and reorganize", { effort: 'REASONING' });
628
- ```
662
+ ### Space Metadata
629
663
 
630
- ### Collaboration
664
+ Store arbitrary data alongside the Space without it being part of the graph content (e.g., viewport state, user preferences).
631
665
 
632
666
  | Method | Description |
633
667
  |--------|-------------|
634
- | `listUsers(): Promise<SpaceMember[]>` | List users with access |
635
- | `addUser(userId, role): Promise<void>` | Add user to space |
636
- | `removeUser(userId): Promise<void>` | Remove user from space |
668
+ | `setMetadata(key, value): void` | Set space-level metadata |
669
+ | `getMetadata(key): unknown` | Get metadata value, or undefined if key not set |
670
+ | `getAllMetadata(): Record<string, unknown>` | Get all metadata |
637
671
 
638
672
  ### Media
639
673
 
@@ -664,6 +698,46 @@ if (response.contentType.startsWith('image/')) {
664
698
  }
665
699
  ```
666
700
 
701
+ ### Import/Export
702
+
703
+ Export space data as JSON-LD for backup, portability, or migration:
704
+
705
+ | Method | Description |
706
+ |--------|-------------|
707
+ | `export(): JsonLdDocument` | Export all objects and relations as JSON-LD |
708
+ | `import(data): Promise<void>` | Import JSON-LD into empty space |
709
+ | `exportArchive(): Promise<Blob>` | Export objects, relations, and media as a zip archive |
710
+ | `importArchive(archive): Promise<void>` | Import from a zip archive into empty space |
711
+
712
+ **Export (data only):**
713
+ ```typescript
714
+ const jsonld = space.export();
715
+ const json = JSON.stringify(jsonld, null, 2);
716
+ ```
717
+
718
+ **Export (with media):**
719
+ ```typescript
720
+ const archive = await space.exportArchive();
721
+ // Save as .zip file
722
+ const url = URL.createObjectURL(archive);
723
+ ```
724
+
725
+ **Import (data only):**
726
+ ```typescript
727
+ // Space must be empty
728
+ const newSpace = await client.createSpace('Imported Data');
729
+ await newSpace.import(jsonld);
730
+ ```
731
+
732
+ **Import (with media):**
733
+ ```typescript
734
+ // Space must be empty
735
+ const newSpace = await client.createSpace('Imported Data');
736
+ await newSpace.importArchive(archiveBlob);
737
+ ```
738
+
739
+ 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.
740
+
667
741
  ### Space Events
668
742
 
669
743
  Semantic events describe what changed. Events fire for both local changes and remote changes.
@@ -697,20 +771,6 @@ space.on('reset', ({ source }) => void)
697
771
  space.on('syncError', (error: Error) => void)
698
772
  ```
699
773
 
700
- **Usage pattern:**
701
- ```typescript
702
- // All UI updates happen in one place, regardless of change source
703
- space.on('objectUpdated', ({ objectId, object, source }) => {
704
- renderObject(objectId, object);
705
- if (source === 'remote_agent') {
706
- doLayout(); // AI might have added content
707
- }
708
- });
709
-
710
- // Caller just makes the change - event handler does the UI work
711
- space.updateObject(objectId, { prompt: 'expand this' });
712
- ```
713
-
714
774
  ### Error Handling
715
775
 
716
776
  AI operations may fail due to rate limiting or other transient errors. Check `error.message` for user-friendly error text:
@@ -733,8 +793,94 @@ try {
733
793
  |--------|-------------|
734
794
  | `getData(): RoolSpaceData` | Get full space data (internal format) |
735
795
 
796
+ ## Interaction History
797
+
798
+ 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.
799
+
800
+ ### What the AI Receives
801
+
802
+ AI operations (`prompt`, `createObject`, `updateObject`, `findObjects`) automatically receive:
803
+
804
+ - **Interaction history** — Previous interactions and their results from this conversation
805
+ - **Recently modified objects** — Objects in the space recently created or changed
806
+ - **Selected objects** — Objects passed via `objectIds` are given primary focus
807
+
808
+ This context flows automatically — no configuration needed. The AI sees enough history to maintain coherent interactions while respecting the `_`-prefixed field hiding rules.
809
+
810
+ ### Accessing History
811
+
812
+ ```typescript
813
+ // Get interactions for the current conversationId
814
+ const interactions = space.getInteractions();
815
+ // Returns: Interaction[]
816
+
817
+ // Get interactions for a specific conversation ID
818
+ const interactions = space.getInteractionsById('other-conversation-id');
819
+ // Returns: Interaction[]
820
+
821
+ // List all conversation IDs that have interactions
822
+ const conversationIds = space.getConversationIds();
823
+ // Returns: string[]
824
+ ```
825
+
826
+ ### Conversation Access Methods
827
+
828
+ | Method | Description |
829
+ |--------|-------------|
830
+ | `getInteractions(): Interaction[]` | Get interactions for the current conversationId |
831
+ | `getInteractionsById(id): Interaction[]` | Get interactions for a specific conversation ID |
832
+ | `getConversationIds(): string[]` | List all conversation IDs that have interactions |
833
+ | `clearInteractions(conversationId?): Promise<void>` | Clear interaction history. Defaults to current conversation. |
834
+
835
+ ### Listening for Updates
836
+
837
+ ```typescript
838
+ space.on('interactionsUpdated', ({ conversationId, source }) => {
839
+ // Interaction history changed - refresh if needed
840
+ const interactions = space.getInteractions();
841
+ renderInteractions(interactions);
842
+ });
843
+ ```
844
+
845
+ ### Conversation Isolation
846
+
847
+ By default, each call to `openSpace()` or `createSpace()` generates a new `conversationId`. This means:
848
+ - Opening a space twice gives you two independent AI conversation histories
849
+ - Closing and reopening a space starts fresh
850
+
851
+ ### Resuming Conversations
852
+
853
+ Pass a `conversationId` to continue a previous conversation:
854
+
855
+ ```typescript
856
+ // First conversation - save the conversationId
857
+ const space = await client.openSpace('abc123');
858
+ const savedConversationId = space.conversationId;
859
+ space.close();
860
+
861
+ // Later - resume the same conversation
862
+ const resumedSpace = await client.openSpace('abc123', { conversationId: savedConversationId });
863
+ // AI now has access to conversation history from before
864
+ ```
865
+
866
+ **Use cases:**
867
+ - **Page refresh** — Store `conversationId` in localStorage to maintain context across reloads
868
+ - **Multiple conversations** — Maintain and switch between different conversation contexts with the same space
869
+ - **Collaborative conversations** — Share a `conversationId` between users to enable shared AI conversation history
870
+
871
+ **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.
872
+
873
+ Note: Interaction history is truncated to the most recent 50 entries to manage space size.
874
+
875
+ ### The ai Field
876
+
877
+ The `ai` field in interactions distinguishes AI-generated responses from synthetic confirmations:
878
+ - `ai: true` — AI processed this operation (prompt, or createObject/updateObject with placeholders)
879
+ - `ai: false` — System confirmation only (e.g., "Linked X to Y", "Created object abc123")
880
+
881
+ ### Tool Calls
736
882
 
737
- ---
883
+ 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 `interactionsUpdated` event fires as each tool completes, letting you display status updates or hints in real-time.
738
884
 
739
885
  ## Data Types
740
886
 
@@ -762,6 +908,7 @@ interface RoolObjectEntry {
762
908
  data: RoolObject; // The actual object data
763
909
  modifiedAt?: number; // Timestamp of last modification
764
910
  modifiedBy?: string; // User ID who last modified
911
+ modifiedByName?: string | null; // Display name at time of modification
765
912
  }
766
913
  ```
767
914
 
@@ -788,11 +935,6 @@ interface Interaction {
788
935
  }
789
936
  ```
790
937
 
791
- The `toolCalls` array captures what the AI agent did during execution. This provides:
792
- - **Real-time feedback**: Clients receive updates as tools execute (via `interactionsUpdated` event)
793
- - **Agent memory**: The AI sees its previous tool usage when continuing a conversation
794
- - **Debugging**: Inspect what tools were called and their results
795
-
796
938
  ### Info Types
797
939
 
798
940
  ```typescript
@@ -820,9 +962,7 @@ interface PromptOptions {
820
962
  }
821
963
  ```
822
964
 
823
- ---
824
-
825
- ## What Can You Build With a Space?
965
+ ## Patterns & Examples
826
966
 
827
967
  A Rool Space is a persistent, shared world model. Applications project different interaction patterns onto the same core primitives:
828
968
 
@@ -877,4 +1017,4 @@ Below are a few representative patterns.
877
1017
 
878
1018
  This client is intended for use with the Rool platform and requires an account. Access is currently by invitation.
879
1019
 
880
- Proprietary - © Lightpost One. All rights reserved.
1020
+ Proprietary © Lightpost One. All rights reserved.