@rool-dev/sdk 0.10.2 → 0.11.0-dev.59f195f
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 +458 -1227
- package/dist/channel.d.ts +65 -129
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +259 -393
- package/dist/channel.js.map +1 -1
- package/dist/client.d.ts +3 -55
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +7 -93
- package/dist/client.js.map +1 -1
- package/dist/graphql.d.ts +4 -46
- package/dist/graphql.d.ts.map +1 -1
- package/dist/graphql.js +27 -250
- package/dist/graphql.js.map +1 -1
- package/dist/index.d.ts +3 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -4
- package/dist/index.js.map +1 -1
- package/dist/path.d.ts +6 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +47 -0
- package/dist/path.js.map +1 -0
- package/dist/reroute.d.ts +22 -0
- package/dist/reroute.d.ts.map +1 -0
- package/dist/reroute.js +61 -0
- package/dist/reroute.js.map +1 -0
- package/dist/rest.d.ts +9 -0
- package/dist/rest.d.ts.map +1 -1
- package/dist/rest.js +33 -1
- package/dist/rest.js.map +1 -1
- package/dist/space.d.ts +4 -14
- package/dist/space.d.ts.map +1 -1
- package/dist/space.js +30 -50
- package/dist/space.js.map +1 -1
- package/dist/subscription.d.ts.map +1 -1
- package/dist/subscription.js +23 -33
- package/dist/subscription.js.map +1 -1
- package/dist/types.d.ts +36 -212
- package/dist/types.d.ts.map +1 -1
- package/dist/webdav.d.ts +31 -21
- package/dist/webdav.d.ts.map +1 -1
- package/dist/webdav.js +58 -57
- package/dist/webdav.js.map +1 -1
- package/package.json +2 -1
- package/dist/apps.d.ts +0 -30
- package/dist/apps.d.ts.map +0 -1
- package/dist/apps.js +0 -81
- package/dist/apps.js.map +0 -1
- package/dist/locations.d.ts +0 -34
- package/dist/locations.d.ts.map +0 -1
- package/dist/locations.js +0 -90
- package/dist/locations.js.map +0 -1
- package/dist/machine.d.ts +0 -16
- package/dist/machine.d.ts.map +0 -1
- package/dist/machine.js +0 -51
- package/dist/machine.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
# Rool SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript SDK for Rool, a persistent collaborative workspace for objects, AI-assisted editing, and per-space files.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Core primitives:
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **Objects** — Records addressed by a **location** path (`/space/<collection>/<basename>.json`). The body holds user-defined fields. References between objects are body fields whose values are location strings.
|
|
13
|
-
- **AI operations** — Create, update, or query objects using natural language and `{{placeholders}}`
|
|
14
|
-
- **File storage** — Every space has WebDAV file storage
|
|
7
|
+
- **Spaces** — containers for objects, schema, metadata, channels, collaborators, and files.
|
|
8
|
+
- **Channels** — named contexts within a space for object operations and AI conversations.
|
|
9
|
+
- **Conversations** — independent interaction histories within a channel.
|
|
10
|
+
- **Objects** — JSON records addressed by object paths such as `/space/article/welcome.json`.
|
|
11
|
+
- **Files** — user-visible files stored under `/rool-drive/...` through WebDAV.
|
|
15
12
|
|
|
16
13
|
## Installation
|
|
17
14
|
|
|
@@ -24,1293 +21,611 @@ npm install @rool-dev/sdk
|
|
|
24
21
|
```typescript
|
|
25
22
|
import { RoolClient } from '@rool-dev/sdk';
|
|
26
23
|
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
if (!authenticated) {
|
|
31
|
-
client.login('My App'); // Redirects to auth page, shows "Sign in to My App"
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Create a new space, then open a channel on it
|
|
35
|
-
const space = await client.createSpace('Solar System');
|
|
36
|
-
const channel = await space.openChannel('main');
|
|
37
|
-
|
|
38
|
-
// Define the schema — what types of objects exist and their fields
|
|
39
|
-
await channel.createCollection('body', [
|
|
40
|
-
{ name: 'name', type: { kind: 'string' } },
|
|
41
|
-
{ name: 'mass', type: { kind: 'string' } },
|
|
42
|
-
{ name: 'radius', type: { kind: 'string' } },
|
|
43
|
-
{ name: 'orbits', type: { kind: 'maybe', inner: { kind: 'ref' } } },
|
|
44
|
-
]);
|
|
45
|
-
|
|
46
|
-
// Create objects with AI-generated content using {{placeholders}}.
|
|
47
|
-
// First arg is the collection, second is the body.
|
|
48
|
-
const { object: sun } = await channel.createObject('body', {
|
|
49
|
-
name: 'Sun',
|
|
50
|
-
mass: '{{mass in solar masses}}',
|
|
51
|
-
radius: '{{radius in km}}',
|
|
52
|
-
}, { basename: 'sun' });
|
|
53
|
-
|
|
54
|
-
const { object: earth } = await channel.createObject('body', {
|
|
55
|
-
name: 'Earth',
|
|
56
|
-
mass: '{{mass in Earth masses}}',
|
|
57
|
-
radius: '{{radius in km}}',
|
|
58
|
-
orbits: sun.location, // Reference to the sun via its location
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// Use the AI agent to work with your data
|
|
62
|
-
const { message, objects } = await channel.prompt(
|
|
63
|
-
'Add the other planets in our solar system, each referencing the Sun'
|
|
64
|
-
);
|
|
65
|
-
console.log(message); // AI explains what it did
|
|
66
|
-
console.log(`Modified ${objects.length} objects`);
|
|
67
|
-
|
|
68
|
-
// Query with natural language
|
|
69
|
-
const { objects: innerPlanets } = await channel.findObjects({
|
|
70
|
-
prompt: 'planets closer to the sun than Earth'
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// Clean up
|
|
74
|
-
channel.close();
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## Core Concepts
|
|
78
|
-
|
|
79
|
-
### Spaces and Channels
|
|
80
|
-
|
|
81
|
-
A **space** is a container that holds objects, schema, metadata, channels, and files. A **channel** is a named context within a space — it's the handle you use for all object and AI operations. Each channel contains one or more **conversations**, each with independent interaction history.
|
|
24
|
+
async function main() {
|
|
25
|
+
const client = new RoolClient();
|
|
82
26
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// Open a space — live handle with SSE subscription
|
|
89
|
-
const space = await client.openSpace('space-id');
|
|
90
|
-
await space.addUser(userId, 'editor');
|
|
91
|
-
await space.setLinkAccess('viewer');
|
|
92
|
-
|
|
93
|
-
// React to channel changes in real-time
|
|
94
|
-
space.on('channelCreated', (channel) => console.log('New channel:', channel.id));
|
|
95
|
-
space.on('channelUpdated', (channel) => console.log('Updated:', channel.id));
|
|
96
|
-
space.on('channelDeleted', (channelId) => console.log('Deleted:', channelId));
|
|
97
|
-
|
|
98
|
-
// Open a channel for object and AI operations
|
|
99
|
-
const channel = await space.openChannel('my-channel');
|
|
100
|
-
await channel.prompt('Create some planets');
|
|
27
|
+
if (!(await client.initialize())) {
|
|
28
|
+
await client.login('My App');
|
|
29
|
+
// Browser auth redirects away. Run startup again after the auth callback.
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
101
32
|
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
await channel2.prompt('Analyze the data'); // Independent channel
|
|
33
|
+
const space = await client.createSpace('Solar System');
|
|
34
|
+
const channel = await space.openChannel('main');
|
|
105
35
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
36
|
+
await channel.createCollection('body', [
|
|
37
|
+
{ name: 'name', type: { kind: 'string' } },
|
|
38
|
+
{ name: 'mass', type: { kind: 'string' } },
|
|
39
|
+
{ name: 'radius', type: { kind: 'string' } },
|
|
40
|
+
{ name: 'orbits', type: { kind: 'maybe', inner: { kind: 'ref' } } },
|
|
41
|
+
]);
|
|
109
42
|
|
|
110
|
-
|
|
43
|
+
const { object: sun } = await channel.putObject('/space/body/sun.json', {
|
|
44
|
+
name: 'Sun',
|
|
45
|
+
mass: '1 solar mass',
|
|
46
|
+
radius: '696,340 km',
|
|
47
|
+
});
|
|
111
48
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
49
|
+
const { object: earth } = await channel.putObject('/space/body/earth.json', {
|
|
50
|
+
name: 'Earth',
|
|
51
|
+
mass: '1 Earth mass',
|
|
52
|
+
radius: '6,371 km',
|
|
53
|
+
orbits: sun.path,
|
|
54
|
+
});
|
|
115
55
|
|
|
116
|
-
|
|
56
|
+
const { message, objects } = await channel.prompt(
|
|
57
|
+
'Add the other planets in our solar system, each referencing the Sun.'
|
|
58
|
+
);
|
|
117
59
|
|
|
118
|
-
|
|
60
|
+
console.log(message);
|
|
61
|
+
console.log(`Modified ${objects.length} objects`);
|
|
119
62
|
|
|
120
|
-
|
|
63
|
+
const loadedEarth = await channel.getObject(earth.path);
|
|
64
|
+
console.log(loadedEarth?.body.name);
|
|
121
65
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const space = await client.openSpace('space-id');
|
|
125
|
-
const channel = await space.openChannel('main');
|
|
126
|
-
await channel.prompt('Hello'); // Uses 'default' conversation
|
|
66
|
+
space.close();
|
|
67
|
+
}
|
|
127
68
|
|
|
128
|
-
|
|
129
|
-
const thread = channel.conversation('thread-42');
|
|
130
|
-
await thread.prompt('Hello'); // Uses 'thread-42' conversation
|
|
131
|
-
thread.getInteractions(); // Interactions for thread-42 only
|
|
69
|
+
void main();
|
|
132
70
|
```
|
|
133
71
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
```typescript
|
|
137
|
-
// System instructions are per-conversation
|
|
138
|
-
const thread = channel.conversation('research');
|
|
139
|
-
await thread.setSystemInstruction('Respond in haiku');
|
|
140
|
-
|
|
141
|
-
// List all conversations in this channel
|
|
142
|
-
const conversations = channel.getConversations();
|
|
143
|
-
|
|
144
|
-
// Delete a conversation (cannot delete 'default')
|
|
145
|
-
await channel.deleteConversation('old-thread');
|
|
72
|
+
## Paths and Resource URIs
|
|
146
73
|
|
|
147
|
-
|
|
148
|
-
await thread.rename('Research Thread');
|
|
149
|
-
```
|
|
74
|
+
Most SDK methods take plain path strings:
|
|
150
75
|
|
|
151
|
-
|
|
76
|
+
- Object paths: `/space/<collection>/<name>.json` (exactly three segments; no dotfile collection or object names)
|
|
77
|
+
- File paths: `/rool-drive/<path/to/file>`
|
|
152
78
|
|
|
153
|
-
|
|
79
|
+
`rool-machine:/...` URIs are the user-facing/canonical form for resource references in prompt attachments and interaction history. The exported helpers normalize between these forms when you need them.
|
|
154
80
|
|
|
155
81
|
```typescript
|
|
156
|
-
|
|
82
|
+
import { machinePath, machineUri, isObjectPath } from '@rool-dev/sdk';
|
|
157
83
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
await thread.prompt('What is my favorite color?'); // Sees "blue"
|
|
84
|
+
machinePath('rool-machine:/rool-drive/docs/read%20me.md');
|
|
85
|
+
// '/rool-drive/docs/read me.md'
|
|
161
86
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const tree = thread.getTree();
|
|
165
|
-
const firstInteractionId = tree[firstLeaf!].parentId!; // The root
|
|
87
|
+
machineUri('/space/article/welcome.json');
|
|
88
|
+
// 'rool-machine:/space/article/welcome.json'
|
|
166
89
|
|
|
167
|
-
|
|
168
|
-
parentInteractionId: firstInteractionId, // Sibling of "blue"
|
|
169
|
-
});
|
|
170
|
-
await thread.prompt('What is my favorite color?'); // Sees "red", not "blue"
|
|
171
|
-
|
|
172
|
-
// Switch back to the blue branch
|
|
173
|
-
thread.setActiveLeaf(firstLeaf!);
|
|
174
|
-
thread.getInteractions(); // Returns the blue branch (root → leaf)
|
|
90
|
+
isObjectPath('/space/article/welcome.json'); // true
|
|
175
91
|
```
|
|
176
92
|
|
|
177
|
-
|
|
178
|
-
- `getInteractions()` returns the active branch as a flat `Interaction[]` (root → leaf)
|
|
179
|
-
- `getTree()` returns the full `Record<string, Interaction>` for branch navigation UI
|
|
180
|
-
- `activeLeafId` is the tip of the branch the user is currently viewing
|
|
181
|
-
- `setActiveLeaf(id)` switches branches (emits `conversationUpdated` so reactive UIs refresh)
|
|
182
|
-
- `prompt()` with no `parentInteractionId` auto-continues from `activeLeafId`
|
|
183
|
-
- `prompt()` with `parentInteractionId: null` starts a new root-level branch
|
|
184
|
-
|
|
185
|
-
### Objects, Locations, and References
|
|
186
|
-
|
|
187
|
-
Every object lives at a **location** — a path of the form `/space/<collection>/<basename>.json`. The collection is the parent directory, the basename is the filename without `.json`, and together they fully identify the object.
|
|
93
|
+
Object APIs require full object paths. References between objects are ordinary body fields containing object paths:
|
|
188
94
|
|
|
189
95
|
```typescript
|
|
190
96
|
{
|
|
191
|
-
|
|
192
|
-
collection: 'article',
|
|
193
|
-
basename: 'welcome',
|
|
194
|
-
body: { title: 'Hello World', status: 'draft' },
|
|
195
|
-
}
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
The **body** holds the user-defined data.
|
|
199
|
-
|
|
200
|
-
**References** between objects are body fields whose values are location strings:
|
|
201
|
-
|
|
202
|
-
```typescript
|
|
203
|
-
// A planet references a star
|
|
204
|
-
{
|
|
205
|
-
location: '/space/body/earth.json',
|
|
206
|
-
collection: 'body',
|
|
207
|
-
basename: 'earth',
|
|
97
|
+
path: '/space/body/earth.json',
|
|
208
98
|
body: { name: 'Earth', orbits: '/space/body/sun.json' },
|
|
209
99
|
}
|
|
210
|
-
|
|
211
|
-
// An array of references
|
|
212
|
-
{
|
|
213
|
-
location: '/space/team/alpha.json',
|
|
214
|
-
collection: 'team',
|
|
215
|
-
basename: 'alpha',
|
|
216
|
-
body: {
|
|
217
|
-
name: 'Alpha',
|
|
218
|
-
members: [
|
|
219
|
-
'/space/user/alice.json',
|
|
220
|
-
'/space/user/bob.json',
|
|
221
|
-
'/space/user/carol.json',
|
|
222
|
-
],
|
|
223
|
-
},
|
|
224
|
-
}
|
|
225
100
|
```
|
|
226
101
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
#### Location helpers
|
|
230
|
-
|
|
231
|
-
```typescript
|
|
232
|
-
import { loc, parseLocation, normalizeLocation, generateBasename } from '@rool-dev/sdk';
|
|
233
|
-
|
|
234
|
-
loc('article', 'welcome'); // '/space/article/welcome.json'
|
|
235
|
-
parseLocation('/space/article/welcome.json'); // { collection: 'article', basename: 'welcome' }
|
|
236
|
-
|
|
237
|
-
// normalizeLocation accepts canonical or short form and returns canonical
|
|
238
|
-
normalizeLocation('article/welcome'); // '/space/article/welcome.json'
|
|
239
|
-
normalizeLocation('/space/article/welcome.json'); // unchanged
|
|
240
|
-
|
|
241
|
-
// 6-char random basename — same generator the SDK uses by default
|
|
242
|
-
generateBasename(); // e.g., 'X7kQ9p'
|
|
243
|
-
```
|
|
102
|
+
## Authentication
|
|
244
103
|
|
|
245
|
-
|
|
104
|
+
### Browser
|
|
246
105
|
|
|
247
|
-
|
|
106
|
+
The default auth provider stores tokens in browser storage and redirects to the Rool auth page.
|
|
248
107
|
|
|
249
108
|
```typescript
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const objectResource = resolveMachineResource('/space/article/welcome.json');
|
|
253
|
-
// { kind: 'object', path: '/space/article/welcome.json' }
|
|
254
|
-
|
|
255
|
-
const fileResource = resolveMachineResource('/rool-drive/docs/readme.md');
|
|
256
|
-
// { kind: 'file', path: '/rool-drive/docs/readme.md' }
|
|
257
|
-
```
|
|
109
|
+
async function start() {
|
|
110
|
+
const client = new RoolClient();
|
|
258
111
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
```typescript
|
|
266
|
-
// Create with AI-generated content
|
|
267
|
-
await channel.createObject('article', {
|
|
268
|
-
headline: '{{catchy headline about coffee}}',
|
|
269
|
-
body: '{{informative paragraph}}',
|
|
270
|
-
});
|
|
112
|
+
if (!(await client.initialize())) {
|
|
113
|
+
await client.login('My App');
|
|
114
|
+
// Browser auth redirects; stop startup until the callback reloads the app.
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
271
117
|
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
prompt: 'Make the body shorter and more casual'
|
|
275
|
-
});
|
|
118
|
+
// Use the authenticated client here.
|
|
119
|
+
}
|
|
276
120
|
|
|
277
|
-
|
|
278
|
-
await channel.updateObject('/space/article/welcome.json', {
|
|
279
|
-
data: { summary: '{{one-sentence summary}}' }
|
|
280
|
-
});
|
|
121
|
+
void start();
|
|
281
122
|
```
|
|
282
123
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
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.
|
|
286
|
-
|
|
287
|
-
### Checkpoints & Undo/Redo
|
|
124
|
+
### Node.js
|
|
288
125
|
|
|
289
|
-
|
|
126
|
+
Use the Node auth provider for CLIs and scripts. It stores endpoint-scoped credentials under `~/.config/rool/` by default (for example, `credentials-<hash>.json`) and opens a browser for login.
|
|
290
127
|
|
|
291
128
|
```typescript
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
await channel.deleteObjects([location]);
|
|
129
|
+
import { RoolClient } from '@rool-dev/sdk';
|
|
130
|
+
import { NodeAuthProvider } from '@rool-dev/sdk/node';
|
|
295
131
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
await channel.undo(); // Restores the deleted object
|
|
299
|
-
}
|
|
132
|
+
const client = new RoolClient({ authProvider: new NodeAuthProvider() });
|
|
133
|
+
let authenticated = await client.initialize();
|
|
300
134
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
135
|
+
if (!authenticated) {
|
|
136
|
+
await client.login('My CLI Tool');
|
|
137
|
+
// Re-run initialize after the non-redirect login to hydrate currentUser,
|
|
138
|
+
// user storage, and client-level event subscriptions.
|
|
139
|
+
authenticated = await client.initialize();
|
|
304
140
|
}
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
Checkpoints are **space-wide**: one shared stack across all channels and users. `undo()` restores the entire space — including any work others did since the checkpoint. Stacks are capped at 25 entries; identical-content checkpoints are deduped; a new checkpoint clears the redo stack.
|
|
308
141
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
Body fields starting with `_` (e.g., `_ui`, `_cache`) are hidden from AI and ignored by the schema — you can add them to any object regardless of its collection definition. Otherwise they 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:
|
|
312
|
-
|
|
313
|
-
```typescript
|
|
314
|
-
await channel.createObject('article', {
|
|
315
|
-
title: 'My Article',
|
|
316
|
-
author: 'John Doe',
|
|
317
|
-
_ui: { x: 100, y: 200, collapsed: false }
|
|
318
|
-
});
|
|
142
|
+
if (!authenticated) throw new Error('Login required');
|
|
319
143
|
```
|
|
320
144
|
|
|
321
|
-
###
|
|
322
|
-
|
|
323
|
-
Events fire for both local and remote changes. The `source` field indicates origin:
|
|
324
|
-
|
|
325
|
-
- `local_user` — This client made the change
|
|
326
|
-
- `remote_user` — Another user/client made the change
|
|
327
|
-
- `remote_agent` — AI agent made the change
|
|
328
|
-
- `system` — Resync after error
|
|
145
|
+
### Auth API
|
|
329
146
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
channel.updateObject(location, { prompt: 'expand this' });
|
|
341
|
-
```
|
|
147
|
+
| Method | Description |
|
|
148
|
+
| --- | --- |
|
|
149
|
+
| `initialize(): Promise<boolean>` | Call on startup. Initializes auth, refreshes user/storage state, and starts client events when authenticated. |
|
|
150
|
+
| `login(appName, params?): Promise<void>` | Start login flow. |
|
|
151
|
+
| `signup(appName, params?): Promise<void>` | Start signup flow. |
|
|
152
|
+
| `verify(token): Promise<boolean>` | Complete email verification token flow; returns `false` when the active auth provider does not implement verification. |
|
|
153
|
+
| `logout(): void` | Clear auth state and close open spaces. |
|
|
154
|
+
| `isAuthenticated(): Promise<boolean>` | Validate current auth state. |
|
|
155
|
+
| `getAuthUser(): AuthUser` | Return auth identity decoded from the token. |
|
|
156
|
+
| `setPassword(password): Promise<void>` | Set/change password for the current user. |
|
|
342
157
|
|
|
343
|
-
|
|
158
|
+
## Spaces and Channels
|
|
344
159
|
|
|
345
|
-
|
|
160
|
+
Open a space to receive live events and manage collaborators, file storage, and channels. Open a channel to work with objects, schema, metadata, and AI.
|
|
346
161
|
|
|
347
162
|
```typescript
|
|
348
|
-
await
|
|
349
|
-
{ title: 'The Meaning of Life' },
|
|
350
|
-
{ basename: 'meaning-of-life' },
|
|
351
|
-
);
|
|
352
|
-
// → location: /space/article/meaning-of-life.json
|
|
353
|
-
```
|
|
163
|
+
const space = await client.openSpace('space-id');
|
|
354
164
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
- **Meaningful identifiers** — Use domain-specific names like `welcome` or `2026-budget` for easier debugging and external references.
|
|
165
|
+
space.on('channelCreated', (channel) => console.log(channel.id));
|
|
166
|
+
space.on('filesChanged', () => console.log('files changed'));
|
|
358
167
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const basename = RoolClient.generateBasename();
|
|
362
|
-
const location = loc('note', basename);
|
|
363
|
-
|
|
364
|
-
channel.createObject('note', { text: '{{expand this idea}}' }, { basename });
|
|
365
|
-
channel.updateObject(parentLocation, {
|
|
366
|
-
data: { notes: [...existingNotes, location] },
|
|
367
|
-
}); // Add reference immediately
|
|
168
|
+
const channel = await space.openChannel('main');
|
|
169
|
+
await channel.prompt('Summarize this space');
|
|
368
170
|
```
|
|
369
171
|
|
|
370
|
-
|
|
371
|
-
- Must start with an alphanumeric character.
|
|
372
|
-
- Other characters may be alphanumeric, hyphens (`-`), or underscores (`_`).
|
|
373
|
-
- Must be unique within its collection (throws if the location already exists).
|
|
374
|
-
|
|
375
|
-
Use `moveObject` to rename an object or move it to a different collection — see [Moving and Renaming](#moving-and-renaming).
|
|
376
|
-
|
|
377
|
-
## Authentication
|
|
172
|
+
Channel IDs must be 1–32 characters and contain only letters, numbers, `_`, and `-`.
|
|
378
173
|
|
|
379
|
-
|
|
174
|
+
## Object Operations
|
|
380
175
|
|
|
381
|
-
|
|
176
|
+
Objects are JSON files under `/space`. Create the collection before writing objects in it.
|
|
382
177
|
|
|
383
178
|
```typescript
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
client.login('My App'); // Redirect to the auth page
|
|
389
|
-
}
|
|
390
|
-
```
|
|
179
|
+
await channel.createCollection('article', [
|
|
180
|
+
{ name: 'title', type: { kind: 'string' } },
|
|
181
|
+
{ name: 'status', type: { kind: 'string' } },
|
|
182
|
+
]);
|
|
391
183
|
|
|
392
|
-
|
|
184
|
+
// Create or replace an exact object path
|
|
185
|
+
const { object } = await channel.putObject('/space/article/welcome.json', {
|
|
186
|
+
title: 'Welcome',
|
|
187
|
+
status: 'draft',
|
|
188
|
+
});
|
|
393
189
|
|
|
394
|
-
|
|
190
|
+
// Patch fields; null or undefined deletes a field
|
|
191
|
+
await channel.patchObject(object.path, {
|
|
192
|
+
data: { status: 'published', obsoleteField: null },
|
|
193
|
+
});
|
|
395
194
|
|
|
396
|
-
|
|
397
|
-
|
|
195
|
+
// Read one or many objects
|
|
196
|
+
await channel.getObject('/space/article/welcome.json');
|
|
197
|
+
await channel.getObjects([
|
|
198
|
+
'/space/article/welcome.json',
|
|
199
|
+
'/space/article/intro.json',
|
|
200
|
+
]);
|
|
398
201
|
|
|
399
|
-
|
|
400
|
-
|
|
202
|
+
// Rename or move an object
|
|
203
|
+
await channel.moveObject(
|
|
204
|
+
'/space/article/welcome.json',
|
|
205
|
+
'/space/article/hello-world.json'
|
|
206
|
+
);
|
|
401
207
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
}
|
|
208
|
+
// Delete objects
|
|
209
|
+
await channel.deleteObjects(['/space/article/hello-world.json']);
|
|
405
210
|
```
|
|
406
211
|
|
|
407
|
-
### Auth Methods
|
|
408
|
-
|
|
409
212
|
| Method | Description |
|
|
410
|
-
|
|
411
|
-
| `
|
|
412
|
-
| `
|
|
413
|
-
| `
|
|
414
|
-
| `
|
|
415
|
-
| `
|
|
416
|
-
| `
|
|
417
|
-
| `
|
|
418
|
-
| `setPassword(password): Promise<void>` | Set or change the current user's password. Requires an authenticated session. Password must be at least 8 characters and contain both letters and either digits or symbols. Throws with a human-readable message on validation or server failure. |
|
|
213
|
+
| --- | --- |
|
|
214
|
+
| `getObject(path): Promise<RoolObject | undefined>` | Fetch one object by object path. |
|
|
215
|
+
| `getObjects(paths): Promise<GetObjectsResult>` | Fetch objects in bulk; returns `objects` and `missing`. |
|
|
216
|
+
| `stat(path): RoolObjectStat | undefined` | Cached audit info for an object. |
|
|
217
|
+
| `putObject(path, body): Promise<{ object, message }>` | Create or replace an object at an exact path. |
|
|
218
|
+
| `patchObject(path, { data }): Promise<{ object, message }>` | Patch an object's body; `null`/`undefined` deletes fields. |
|
|
219
|
+
| `moveObject(from, to, options?): Promise<{ object, message }>` | Rename or relocate an object; `options.body` can replace the body after moving. |
|
|
220
|
+
| `deleteObjects(paths): Promise<void>` | Delete object files. |
|
|
419
221
|
|
|
420
222
|
## AI Agent
|
|
421
223
|
|
|
422
|
-
|
|
224
|
+
`prompt()` invokes the AI agent. The agent can inspect space context and, unless `readOnly` or a read-only effort is used, create/modify/move/delete objects.
|
|
423
225
|
|
|
424
226
|
```typescript
|
|
425
227
|
const { message, objects } = await channel.prompt(
|
|
426
|
-
|
|
228
|
+
'Create a topic node for the solar system, then child nodes for each planet.'
|
|
427
229
|
);
|
|
428
|
-
console.log(`AI: ${message}`);
|
|
429
|
-
console.log(`Modified ${objects.length} objects:`, objects);
|
|
430
|
-
```
|
|
431
230
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
### Method Signature
|
|
435
|
-
|
|
436
|
-
```typescript
|
|
437
|
-
prompt(text: string, options?: PromptOptions): Promise<{ message: string; objects: RoolObject[] }>
|
|
231
|
+
console.log(message);
|
|
232
|
+
console.log(objects.map((object) => object.path));
|
|
438
233
|
```
|
|
439
234
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
### Options
|
|
235
|
+
### Prompt Options
|
|
443
236
|
|
|
444
237
|
| Option | Description |
|
|
445
|
-
|
|
446
|
-
| `responseSchema` | Request structured JSON
|
|
447
|
-
| `effort` |
|
|
448
|
-
| `ephemeral` |
|
|
449
|
-
| `readOnly` |
|
|
450
|
-
| `parentInteractionId` |
|
|
451
|
-
| `attachments` |
|
|
452
|
-
| `signal` |
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
| `STANDARD` | Default behavior with balanced capabilities. |
|
|
460
|
-
| `REASONING` | Extended reasoning for complex tasks. |
|
|
461
|
-
| `RESEARCH` | Most thorough mode with deep analysis. Slowest and most credit-intensive. |
|
|
462
|
-
|
|
463
|
-
### Examples
|
|
464
|
-
|
|
465
|
-
```typescript
|
|
466
|
-
// Reorganize existing objects
|
|
467
|
-
const { objects } = await channel.prompt(
|
|
468
|
-
"Group these notes by topic and create a parent node for each group."
|
|
469
|
-
);
|
|
470
|
-
|
|
471
|
-
// Work with specific objects
|
|
472
|
-
const intro = resolveMachineResource('/space/article/intro.json');
|
|
473
|
-
const conclusion = resolveMachineResource('/space/article/conclusion.json');
|
|
474
|
-
if (!intro || !conclusion) throw new Error('invalid resource');
|
|
475
|
-
const result = await channel.prompt(
|
|
476
|
-
"Summarize these articles",
|
|
477
|
-
{ attachments: [intro, conclusion] }
|
|
478
|
-
);
|
|
479
|
-
|
|
480
|
-
// Quick question without mutations (fast model + read-only)
|
|
481
|
-
const { message } = await channel.prompt(
|
|
482
|
-
"What topics are covered?",
|
|
483
|
-
{ effort: 'QUICK', readOnly: true }
|
|
484
|
-
);
|
|
485
|
-
|
|
486
|
-
// Complex analysis with extended reasoning
|
|
487
|
-
await channel.prompt(
|
|
488
|
-
"Analyze relationships and reorganize",
|
|
489
|
-
{ effort: 'REASONING' }
|
|
490
|
-
);
|
|
491
|
-
|
|
492
|
-
// Attach existing WebDAV files/folders or local uploads
|
|
493
|
-
const report = resolveMachineResource('/rool-drive/docs/report.pdf');
|
|
494
|
-
if (!report) throw new Error('invalid resource');
|
|
495
|
-
const file = fileInput.files[0]; // from <input type="file">
|
|
496
|
-
await channel.prompt(
|
|
497
|
-
"Compare this report with the uploaded photo",
|
|
498
|
-
{ attachments: [report, file] }
|
|
499
|
-
);
|
|
500
|
-
|
|
501
|
-
// Cancel a long-running prompt
|
|
502
|
-
const ac = new AbortController();
|
|
503
|
-
cancelButton.onclick = () => ac.abort();
|
|
504
|
-
await channel.prompt("Do a deep analysis...", {
|
|
505
|
-
effort: 'RESEARCH',
|
|
506
|
-
signal: ac.signal,
|
|
238
|
+
| --- | --- |
|
|
239
|
+
| `responseSchema` | Request structured JSON text matching a JSON-schema-like shape. |
|
|
240
|
+
| `effort` | `'QUICK'` (fast/read-only), `'STANDARD'` (default), `'REASONING'`, or `'RESEARCH'`. |
|
|
241
|
+
| `ephemeral` | Do not record the prompt in interaction history. |
|
|
242
|
+
| `readOnly` | Disable mutation tools. |
|
|
243
|
+
| `parentInteractionId` | Conversation-tree parent. Omit to continue from the active leaf; pass `null` for a new root branch. |
|
|
244
|
+
| `attachments` | Existing object/file paths or `rool-machine:/...` URIs, plus local files (`File`, `Blob`, or `{ data, contentType, filename? }`). |
|
|
245
|
+
| `signal` | AbortSignal used to request that the server stop an in-flight prompt. |
|
|
246
|
+
| `eventName` | Optional telemetry event name. Defaults to `'prompt_user'`. |
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// Read-only quick question
|
|
250
|
+
await channel.prompt('What topics are covered?', {
|
|
251
|
+
effort: 'QUICK', // fast/read-only
|
|
507
252
|
});
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
### Structured Responses
|
|
511
253
|
|
|
512
|
-
|
|
254
|
+
// Focus on existing objects and files
|
|
255
|
+
await channel.prompt('Compare these resources', {
|
|
256
|
+
attachments: [
|
|
257
|
+
'/space/article/intro.json',
|
|
258
|
+
'rool-machine:/rool-drive/docs/report.pdf',
|
|
259
|
+
],
|
|
260
|
+
});
|
|
513
261
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
'/space/item/gadget.json',
|
|
518
|
-
'/space/item/gizmo.json',
|
|
519
|
-
].map((path) => {
|
|
520
|
-
const resource = resolveMachineResource(path);
|
|
521
|
-
if (!resource) throw new Error(`invalid resource: ${path}`);
|
|
522
|
-
return resource;
|
|
262
|
+
// Upload a local file as an attachment
|
|
263
|
+
await channel.prompt('Describe this image', {
|
|
264
|
+
attachments: [fileInput.files![0]],
|
|
523
265
|
});
|
|
524
266
|
|
|
525
|
-
|
|
526
|
-
|
|
267
|
+
// Structured response
|
|
268
|
+
const { message } = await channel.prompt('Categorize these items', {
|
|
527
269
|
responseSchema: {
|
|
528
270
|
type: 'object',
|
|
529
271
|
properties: {
|
|
530
|
-
categories: {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
summary: { type: 'string' }
|
|
535
|
-
}
|
|
536
|
-
}
|
|
272
|
+
categories: { type: 'array', items: { type: 'string' } },
|
|
273
|
+
summary: { type: 'string' },
|
|
274
|
+
},
|
|
275
|
+
},
|
|
537
276
|
});
|
|
538
|
-
|
|
539
277
|
const result = JSON.parse(message);
|
|
540
|
-
console.log(result.categories, result.summary);
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
### Context Flow
|
|
544
278
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
This context flows automatically — no configuration needed. The AI sees enough history to maintain coherent interactions while respecting the `_`-prefixed field hiding rules.
|
|
551
|
-
|
|
552
|
-
## Collaboration
|
|
553
|
-
|
|
554
|
-
### Adding Users to a Space
|
|
555
|
-
|
|
556
|
-
To add a user to a space, you need their user ID. Use `searchUser()` to find them by email:
|
|
557
|
-
|
|
558
|
-
```typescript
|
|
559
|
-
// Find the user by email
|
|
560
|
-
const user = await client.searchUser('colleague@example.com');
|
|
561
|
-
if (!user) {
|
|
562
|
-
throw new Error('User not found');
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
// Add them to the space
|
|
566
|
-
const space = await client.openSpace('space-id');
|
|
567
|
-
await space.addUser(user.id, 'editor');
|
|
568
|
-
```
|
|
569
|
-
|
|
570
|
-
### Roles
|
|
571
|
-
|
|
572
|
-
| Role | Capabilities |
|
|
573
|
-
|------|--------------|
|
|
574
|
-
| `owner` | Full control, can delete space and manage all users |
|
|
575
|
-
| `admin` | All editor capabilities, plus can manage users (except other admins/owners) |
|
|
576
|
-
| `editor` | Can create, modify, move, and delete objects |
|
|
577
|
-
| `viewer` | Read-only access (can query with `prompt` and `findObjects`) |
|
|
578
|
-
|
|
579
|
-
### Space Collaboration Methods
|
|
580
|
-
|
|
581
|
-
These methods are available on `RoolSpace`:
|
|
582
|
-
|
|
583
|
-
| Method | Description |
|
|
584
|
-
|--------|-------------|
|
|
585
|
-
| `listUsers(): Promise<SpaceMember[]>` | List users with access |
|
|
586
|
-
| `addUser(userId, role): Promise<void>` | Add user to space (requires owner or admin role) |
|
|
587
|
-
| `removeUser(userId): Promise<void>` | Remove user from space (requires owner or admin role) |
|
|
588
|
-
| `setLinkAccess(linkAccess): Promise<void>` | Set URL sharing level (requires owner or admin role) |
|
|
589
|
-
|
|
590
|
-
### URL Sharing
|
|
591
|
-
|
|
592
|
-
Enable public URL access to allow anyone with the space URL to access it:
|
|
593
|
-
|
|
594
|
-
```typescript
|
|
595
|
-
const space = await client.openSpace('space-id');
|
|
596
|
-
|
|
597
|
-
// Allow anyone with the URL to view
|
|
598
|
-
await space.setLinkAccess('viewer');
|
|
599
|
-
|
|
600
|
-
// Allow anyone with the URL to edit
|
|
601
|
-
await space.setLinkAccess('editor');
|
|
602
|
-
|
|
603
|
-
// Disable URL access (default)
|
|
604
|
-
await space.setLinkAccess('none');
|
|
605
|
-
|
|
606
|
-
// Check current setting
|
|
607
|
-
console.log(space.linkAccess); // 'none' | 'viewer' | 'editor'
|
|
608
|
-
```
|
|
609
|
-
|
|
610
|
-
When a user accesses a space via URL, they're granted the corresponding role (`viewer` or `editor`) based on the space's `linkAccess` setting.
|
|
611
|
-
|
|
612
|
-
### Client User Methods
|
|
613
|
-
|
|
614
|
-
| Method | Description |
|
|
615
|
-
|--------|-------------|
|
|
616
|
-
| `currentUser: CurrentUser \| null` | Cached user profile from `initialize()`. Use for sync access to user info (id, email, name, etc.). Returns `null` before `initialize()` is called. |
|
|
617
|
-
| `getCurrentUser(): Promise<CurrentUser>` | Fetch fresh user profile from server (id, email, name, photoUrl, slug, plan, creditsBalance, totalCreditsUsed, createdAt, lastActivity, processedAt, storage) |
|
|
618
|
-
| `updateCurrentUser(input): Promise<CurrentUser>` | Update the current user's profile (`name`, `slug`). Returns the updated user. Slug must be 3–32 chars, start with a letter, and contain only lowercase alphanumeric characters, hyphens, and underscores. |
|
|
619
|
-
| `deleteCurrentUser(): Promise<void>` | Mark the current user's account for deletion (10-minute grace period before irreversible). Logs out the client. |
|
|
620
|
-
| `searchUser(email): Promise<UserResult \| null>` | Find user by exact email address (no partial matching) |
|
|
621
|
-
|
|
622
|
-
### Real-time Collaboration
|
|
623
|
-
|
|
624
|
-
When multiple users have a space open, changes sync in real-time. The `source` field in events tells you who made the change:
|
|
625
|
-
|
|
626
|
-
```typescript
|
|
627
|
-
channel.on('objectUpdated', ({ location, object, source }) => {
|
|
628
|
-
if (source === 'remote_user') {
|
|
629
|
-
// Another user made this change
|
|
630
|
-
showCollaboratorActivity(object);
|
|
631
|
-
}
|
|
279
|
+
// Stop a long prompt with a signal (when the caller holds the controller)
|
|
280
|
+
const ac = new AbortController();
|
|
281
|
+
const promptPromise = channel.prompt('Do a deep analysis', {
|
|
282
|
+
effort: 'RESEARCH',
|
|
283
|
+
signal: ac.signal,
|
|
632
284
|
});
|
|
285
|
+
ac.abort(); // asks the server to stop the in-flight interaction
|
|
286
|
+
await promptPromise;
|
|
633
287
|
```
|
|
634
288
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
## RoolClient API
|
|
289
|
+
### Stopping interactions
|
|
638
290
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
291
|
+
Use `signal` when the same call site cancels the prompt. When the Stop button
|
|
292
|
+
lives elsewhere — a different component, after a reload, or a prompt another
|
|
293
|
+
client started — stop by ID or stop the conversation's active interaction
|
|
294
|
+
instead. Both are best-effort: the server halts the agent loop and closes the
|
|
295
|
+
stream, but an LLM turn already in flight keeps generating server-side and is
|
|
296
|
+
billed.
|
|
642
297
|
|
|
643
298
|
```typescript
|
|
644
|
-
//
|
|
645
|
-
|
|
299
|
+
// Stop whatever is in flight on this channel's (default) conversation.
|
|
300
|
+
// No-op returning false when nothing is running.
|
|
301
|
+
await channel.stop();
|
|
646
302
|
|
|
647
|
-
//
|
|
648
|
-
|
|
303
|
+
// Stop a specific interaction by ID (e.g. from channel.activeLeafId or
|
|
304
|
+
// the interactions list). Returns whether the server stopped it.
|
|
305
|
+
await channel.stopInteraction(channel.activeLeafId!);
|
|
649
306
|
|
|
650
|
-
//
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
});
|
|
307
|
+
// Conversation handles stop their own in-flight interaction.
|
|
308
|
+
const thread = channel.conversation('thread-42');
|
|
309
|
+
await thread.stop();
|
|
654
310
|
```
|
|
655
311
|
|
|
656
|
-
### Space & Channel Lifecycle
|
|
657
|
-
|
|
658
|
-
| Method | Description |
|
|
659
|
-
|--------|-------------|
|
|
660
|
-
| `listSpaces(): Promise<RoolSpaceInfo[]>` | List available spaces |
|
|
661
|
-
| `openSpace(spaceId): Promise<RoolSpace>` | Open a space with live SSE subscription. Caches and reuses open spaces. Call `space.openChannel(channelId)` to get a channel. |
|
|
662
|
-
| `createSpace(name): Promise<RoolSpace>` | Create a new space, returns live handle with SSE subscription |
|
|
663
|
-
| `duplicateSpace(sourceSpaceId, name): Promise<RoolSpace>` | Duplicate an existing space. Returns a handle to the new space. |
|
|
664
|
-
| `deleteSpace(id): Promise<void>` | Permanently delete a space (cannot be undone) |
|
|
665
|
-
| `importArchive(name, archive): Promise<RoolSpace>` | Import from a zip archive, creating a new space |
|
|
666
|
-
| `webdav(spaceId): RoolWebDAV` | Open a WebDAV client for a space's file storage |
|
|
667
|
-
| `getSpaceStorageUsage(spaceId): Promise<SpaceFileStorageUsage>` | Get WebDAV quota usage for a space |
|
|
668
|
-
|
|
669
|
-
### Channel Management
|
|
670
|
-
|
|
671
|
-
Manage channels on the `RoolSpace` handle:
|
|
672
|
-
|
|
673
312
|
| Method | Description |
|
|
674
|
-
|
|
675
|
-
| `
|
|
676
|
-
| `
|
|
677
|
-
| `
|
|
678
|
-
| `space.deleteChannel(channelId): Promise<void>` | Delete a channel and its interaction history |
|
|
679
|
-
| `channel.rename(name): Promise<void>` | Rename the current open channel |
|
|
313
|
+
| --- | --- |
|
|
314
|
+
| `stop(): Promise<boolean>` | Stop the in-flight interaction on the default conversation; `false` if none. |
|
|
315
|
+
| `stopInteraction(id): Promise<boolean>` | Ask the server to stop a specific interaction by ID. |
|
|
316
|
+
| `conversation.stop(): Promise<boolean>` | Stop a specific conversation's in-flight interaction. |
|
|
680
317
|
|
|
681
|
-
|
|
318
|
+
## Conversations
|
|
682
319
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
**Features:**
|
|
686
|
-
- Fresh data fetched from server on `initialize()` — cache is authoritative after init
|
|
687
|
-
- Sync reads from local cache (fast, no network round-trip)
|
|
688
|
-
- Automatic sync to server and across tabs/devices via SSE
|
|
689
|
-
- `userStorageChanged` event fires on all changes (local or remote)
|
|
690
|
-
- Total storage limited to 10MB per user
|
|
691
|
-
|
|
692
|
-
| Method | Description |
|
|
693
|
-
|--------|-------------|
|
|
694
|
-
| `getUserStorage<T>(key): T \| undefined` | Get a value (sync, from cache) |
|
|
695
|
-
| `setUserStorage(key, value): void` | Set a value (updates cache, syncs to server) |
|
|
696
|
-
| `getAllUserStorage(): Record<string, unknown>` | Get all stored data (sync, from cache) |
|
|
320
|
+
Every channel has a default conversation. Use `channel.conversation(id)` for independent histories (for example, multiple chat threads). Conversations are represented as trees: interactions point at a `parentId`, and the SDK tracks an active leaf for each conversation.
|
|
697
321
|
|
|
698
322
|
```typescript
|
|
699
|
-
|
|
700
|
-
const authenticated = await client.initialize();
|
|
701
|
-
|
|
702
|
-
// Sync reads are now trustworthy
|
|
703
|
-
const theme = client.getUserStorage<string>('theme');
|
|
704
|
-
applyTheme(theme ?? 'light');
|
|
323
|
+
await channel.prompt('Hello'); // default conversation
|
|
705
324
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
325
|
+
const thread = channel.conversation('thread-42');
|
|
326
|
+
await thread.prompt('Hello from another thread');
|
|
327
|
+
await thread.setSystemInstruction('Answer in haiku');
|
|
709
328
|
|
|
710
|
-
//
|
|
711
|
-
|
|
329
|
+
const branch = thread.getInteractions(); // active branch, root → leaf
|
|
330
|
+
const tree = thread.getTree(); // full interaction tree
|
|
712
331
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
if (key === 'theme') applyTheme(value as string);
|
|
717
|
-
});
|
|
332
|
+
if (thread.activeLeafId) {
|
|
333
|
+
thread.setActiveLeaf(thread.activeLeafId);
|
|
334
|
+
}
|
|
718
335
|
```
|
|
719
336
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
|
731
|
-
|
|
732
|
-
| `uploadExtension(extensionId, options): Promise<ExtensionInfo>` | Upload or update an extension (`options.bundle`: zip with `index.html` and `manifest.json`) |
|
|
733
|
-
| `listExtensions(): Promise<ExtensionInfo[]>` | List your extensions |
|
|
734
|
-
| `getExtensionInfo(extensionId): Promise<ExtensionInfo \| null>` | Get info for a specific extension |
|
|
735
|
-
| `deleteExtension(extensionId): Promise<void>` | Delete an extension permanently (removes files and DB row) |
|
|
736
|
-
|
|
737
|
-
#### Marketplace (`PublishedExtensionInfo`)
|
|
738
|
-
|
|
739
|
-
Discover and install extensions published by other users.
|
|
337
|
+
| Method/property | Description |
|
|
338
|
+
| --- | --- |
|
|
339
|
+
| `channel.conversation(id): ConversationHandle` | Get a conversation-scoped handle. |
|
|
340
|
+
| `getInteractions(): Interaction[]` | Active branch as a flat list. |
|
|
341
|
+
| `getTree(): Record<string, Interaction>` | Full interaction tree. |
|
|
342
|
+
| `activeLeafId` | Current branch tip. |
|
|
343
|
+
| `setActiveLeaf(id): void` | Switch branches. |
|
|
344
|
+
| `getSystemInstruction()` / `setSystemInstruction(value)` | Manage conversation system instruction. Pass `null` to clear. |
|
|
345
|
+
| `getConversations(): ConversationInfo[]` | List channel conversations (on `RoolChannel`). |
|
|
346
|
+
| `deleteConversation(id): Promise<void>` | Delete a non-active conversation. |
|
|
347
|
+
| `renameConversation(name): Promise<void>` | Rename the current/default conversation (on `RoolChannel`). |
|
|
348
|
+
| `conversation.rename(name): Promise<void>` | Rename a specific conversation handle. |
|
|
740
349
|
|
|
741
|
-
|
|
742
|
-
|--------|-------------|
|
|
743
|
-
| `findExtensions(options?): Promise<PublishedExtensionInfo[]>` | Search the marketplace. Options: `query` (semantic search string), `limit` (default 20, max 100). Omit `query` to browse all. |
|
|
744
|
-
| `publishToPublic(extensionId): Promise<void>` | Publish one of your extensions to the marketplace |
|
|
745
|
-
| `unpublishFromPublic(extensionId): Promise<void>` | Remove from the marketplace (keeps the extension in your library) |
|
|
350
|
+
`ConversationHandle` also supports conversation-scoped `putObject`, `patchObject`, `moveObject`, `deleteObjects`, `prompt`, `stop`, collection-schema methods, and `setMetadata`.
|
|
746
351
|
|
|
747
|
-
|
|
352
|
+
## Schema and Metadata
|
|
748
353
|
|
|
749
|
-
|
|
750
|
-
|--------|-------------|
|
|
751
|
-
| `RoolClient.generateBasename(): string` | Generate a 6-char alphanumeric basename for new object identities. |
|
|
752
|
-
| `RoolClient.generateId(): string` | Same as `generateBasename()`; retained for callers minting non-object IDs (interactions, conversations, channels). |
|
|
753
|
-
| `destroy(): void` | Clean up resources |
|
|
754
|
-
|
|
755
|
-
### Client Events
|
|
354
|
+
Collections define the schema visible to the AI agent. Hidden body fields whose names start with `_` are useful for app/UI state that should not be considered by AI.
|
|
756
355
|
|
|
757
356
|
```typescript
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
client.on('connectionStateChanged', (state: 'connected' | 'disconnected' | 'reconnecting') => void)
|
|
767
|
-
client.on('error', (error: Error, context?: string) => void)
|
|
768
|
-
```
|
|
769
|
-
|
|
770
|
-
Channel events on the client (`channelCreated`, `channelUpdated`, `channelDeleted`) are pass-throughs from space events for backwards compatibility. Prefer listening on the space handle directly for new code.
|
|
771
|
-
|
|
772
|
-
**Space list management pattern:**
|
|
773
|
-
```typescript
|
|
774
|
-
const spaces = new Map<string, RoolSpaceInfo>();
|
|
775
|
-
|
|
776
|
-
client.on('spaceAdded', (space) => spaces.set(space.id, space));
|
|
777
|
-
client.on('spaceRemoved', (id) => spaces.delete(id));
|
|
778
|
-
client.on('spaceRenamed', (id, name) => {
|
|
779
|
-
const space = spaces.get(id);
|
|
780
|
-
if (space) spaces.set(id, { ...space, name });
|
|
357
|
+
await channel.createCollection('article', {
|
|
358
|
+
schemaOrgType: 'Article',
|
|
359
|
+
fields: [
|
|
360
|
+
{ name: 'title', type: { kind: 'string' } },
|
|
361
|
+
{ name: 'status', type: { kind: 'enum', values: ['draft', 'published'] } },
|
|
362
|
+
{ name: 'tags', type: { kind: 'array', inner: { kind: 'string' } } },
|
|
363
|
+
{ name: 'author', type: { kind: 'ref' } },
|
|
364
|
+
],
|
|
781
365
|
});
|
|
782
|
-
```
|
|
783
|
-
|
|
784
|
-
## RoolSpace API
|
|
785
366
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
`openSpace()` caches and reuses open spaces — calling it twice with the same ID returns the same instance. Call `close()` when done to stop the subscription and close all open channels.
|
|
789
|
-
|
|
790
|
-
### Properties
|
|
791
|
-
|
|
792
|
-
| Property | Description |
|
|
793
|
-
|----------|-------------|
|
|
794
|
-
| `id: string` | Space ID |
|
|
795
|
-
| `name: string` | Space name |
|
|
796
|
-
| `role: RoolUserRole` | User's role |
|
|
797
|
-
| `linkAccess: LinkAccess` | URL sharing level |
|
|
798
|
-
| `memberCount: number` | Number of users with access to the space |
|
|
799
|
-
| `channels: ChannelInfo[]` | Live channel list (auto-updates via SSE) |
|
|
800
|
-
| `webdav: RoolWebDAV` | WebDAV client for this space's file storage |
|
|
801
|
-
|
|
802
|
-
### Methods
|
|
367
|
+
const schema = channel.getSchema();
|
|
803
368
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
| `rename(newName): Promise<void>` | Rename this space |
|
|
809
|
-
| `delete(): Promise<void>` | Permanently delete this space |
|
|
810
|
-
| `listUsers(): Promise<SpaceMember[]>` | List users with access |
|
|
811
|
-
| `addUser(userId, role): Promise<void>` | Add user to space |
|
|
812
|
-
| `removeUser(userId): Promise<void>` | Remove user from space |
|
|
813
|
-
| `setLinkAccess(linkAccess): Promise<void>` | Set URL sharing level |
|
|
814
|
-
| `getChannels(): ChannelInfo[]` | List channels (deprecated — use `channels` property instead) |
|
|
815
|
-
| `renameChannel(channelId, name): Promise<void>` | Rename a channel |
|
|
816
|
-
| `deleteChannel(channelId): Promise<void>` | Delete a channel |
|
|
817
|
-
| `installExtension(extensionId, channelId): Promise<string>` | Install an extension into a channel of this space. If you own it, wires it directly. If it's a marketplace extension, copies and builds a new extension in your library. Returns the channel ID. |
|
|
818
|
-
| `exportArchive(): Promise<Blob>` | Export space as zip archive |
|
|
819
|
-
| `getStorageUsage(): Promise<SpaceFileStorageUsage>` | Get WebDAV quota usage for this space |
|
|
820
|
-
| `fetchMachineResource(resource): Promise<Response>` | Fetch a resolved file `MachineResource` through this space |
|
|
821
|
-
| `refresh(): Promise<void>` | Refresh space data from server |
|
|
822
|
-
|
|
823
|
-
### Space Events
|
|
369
|
+
await channel.alterCollection('article', [
|
|
370
|
+
{ name: 'title', type: { kind: 'string' } },
|
|
371
|
+
{ name: 'status', type: { kind: 'string' } },
|
|
372
|
+
]);
|
|
824
373
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
space.on('channelUpdated', (channel: ChannelInfo) => void) // Channel metadata changed (name, extension, manifest)
|
|
828
|
-
space.on('channelDeleted', (channelId: string) => void) // Channel removed
|
|
829
|
-
space.on('filesChanged', ({ source, timestamp }) => void) // WebDAV file storage changed; call webdav.syncCollection()
|
|
830
|
-
space.on('connectionStateChanged', (state: 'connected' | 'disconnected' | 'reconnecting') => void)
|
|
374
|
+
channel.setMetadata('viewport', { x: 0, y: 0, zoom: 1 });
|
|
375
|
+
const viewport = channel.getMetadata('viewport');
|
|
831
376
|
```
|
|
832
377
|
|
|
833
|
-
## RoolChannel API
|
|
834
|
-
|
|
835
|
-
A channel is a named context within a space. All object operations, AI prompts, and real-time sync go through a channel. The `channelId` is fixed at open time — to use a different channel, open a new one.
|
|
836
|
-
|
|
837
|
-
### Properties
|
|
838
|
-
|
|
839
|
-
| Property | Description |
|
|
840
|
-
|----------|-------------|
|
|
841
|
-
| `id: string` | Space ID |
|
|
842
|
-
| `name: string` | Space name |
|
|
843
|
-
| `role: RoolUserRole` | User's role (`'owner' \| 'admin' \| 'editor' \| 'viewer'`) |
|
|
844
|
-
| `linkAccess: LinkAccess` | URL sharing level (`'none' \| 'viewer' \| 'editor'`) |
|
|
845
|
-
| `userId: string` | Current user's ID |
|
|
846
|
-
| `channelId: string` | Channel ID (read-only, fixed at open time) |
|
|
847
|
-
| `isReadOnly: boolean` | True if viewer role |
|
|
848
|
-
| `extensionUrl: string \| null` | URL of the installed extension, or null if this is a plain channel |
|
|
849
|
-
| `extensionId: string \| null` | ID of the installed extension, or null if this is a plain channel |
|
|
850
|
-
| `manifest: ExtensionManifest \| null` | Extension manifest snapshot (name, icon, collections, etc.), or null |
|
|
851
|
-
|
|
852
|
-
### Lifecycle
|
|
853
|
-
|
|
854
378
|
| Method | Description |
|
|
855
|
-
|
|
856
|
-
| `
|
|
857
|
-
| `
|
|
858
|
-
| `
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
All methods that accept a location accept either the canonical form or the short form (`collection/basename`).
|
|
865
|
-
|
|
866
|
-
| Method | Description |
|
|
867
|
-
|--------|-------------|
|
|
868
|
-
| `getObject(location): Promise<RoolObject \| undefined>` | Get an object, or undefined if not found. |
|
|
869
|
-
| `stat(location): RoolObjectStat \| undefined` | Get audit info for an object: when it was last modified, by whom, and where (channel/conversation/interaction). Sync read from local cache. |
|
|
870
|
-
| `findObjects(options): Promise<{ objects, message }>` | Find objects using structured filters and/or natural language. Results sorted by modifiedAt (desc by default). |
|
|
871
|
-
| `getObjectLocations(options?): string[]` | Get all object locations. Sorted by modifiedAt (desc by default). Options: `{ limit?, order? }`. |
|
|
872
|
-
| `createObject(collection, body, options?): Promise<{ object, message }>` | Create a new object in `collection`. The SDK mints a random basename unless you pass `options.basename`. |
|
|
873
|
-
| `updateObject(location, options): Promise<{ object, message }>` | Update an existing object's body. |
|
|
874
|
-
| `moveObject(from, to, options?): Promise<{ object, message }>` | Rename or relocate an object. See [Moving and Renaming](#moving-and-renaming). |
|
|
875
|
-
| `deleteObjects(locations): Promise<void>` | Delete objects by location. Other objects' refs become stale. |
|
|
876
|
-
|
|
877
|
-
#### createObject
|
|
878
|
-
|
|
879
|
-
```typescript
|
|
880
|
-
// Auto-generated basename
|
|
881
|
-
const { object } = await channel.createObject('article', {
|
|
882
|
-
title: 'Hello',
|
|
883
|
-
body: 'World',
|
|
884
|
-
});
|
|
885
|
-
// → object.location: '/space/article/X7kQ9p.json'
|
|
886
|
-
|
|
887
|
-
// Pinned basename
|
|
888
|
-
await channel.createObject('article',
|
|
889
|
-
{ title: 'Welcome' },
|
|
890
|
-
{ basename: 'welcome' },
|
|
891
|
-
);
|
|
892
|
-
// → location: '/space/article/welcome.json'
|
|
379
|
+
| --- | --- |
|
|
380
|
+
| `getSchema(): SpaceSchema` | Get collection definitions. |
|
|
381
|
+
| `createCollection(name, fieldsOrDef, options?): Promise<CollectionDef>` | Create a collection. |
|
|
382
|
+
| `alterCollection(name, fieldsOrDef, options?): Promise<CollectionDef>` | Replace a collection definition. |
|
|
383
|
+
| `dropCollection(name): Promise<void>` | Remove a collection and its object directory. |
|
|
384
|
+
| `setMetadata(key, value): void` | Set space metadata (fire-and-forget sync). |
|
|
385
|
+
| `getMetadata(key): unknown` | Read metadata from local cache. |
|
|
386
|
+
| `getAllMetadata(): Record<string, unknown>` | Read all metadata from local cache. |
|
|
893
387
|
|
|
894
|
-
|
|
895
|
-
await channel.createObject('article', {
|
|
896
|
-
headline: '{{catchy headline}}',
|
|
897
|
-
body: '{{long-form intro}}',
|
|
898
|
-
});
|
|
899
|
-
```
|
|
900
|
-
|
|
901
|
-
| Option | Description |
|
|
902
|
-
|--------|-------------|
|
|
903
|
-
| `basename` | Specific basename to use. If omitted, the SDK generates a random 6-char one. |
|
|
904
|
-
| `ephemeral` | If true, the operation won't be recorded in interaction history. |
|
|
905
|
-
| `parentInteractionId` | Conversation tree parent. Omit to auto-continue; pass `null` for a new root. |
|
|
906
|
-
|
|
907
|
-
#### updateObject
|
|
908
|
-
|
|
909
|
-
```typescript
|
|
910
|
-
// Add/update fields
|
|
911
|
-
await channel.updateObject('/space/article/welcome.json', {
|
|
912
|
-
data: { status: 'published' },
|
|
913
|
-
});
|
|
914
|
-
|
|
915
|
-
// Delete a field (pass null)
|
|
916
|
-
await channel.updateObject('/space/article/welcome.json', {
|
|
917
|
-
data: { draft: null },
|
|
918
|
-
});
|
|
919
|
-
|
|
920
|
-
// AI-driven rewrite
|
|
921
|
-
await channel.updateObject('/space/article/welcome.json', {
|
|
922
|
-
prompt: 'Tighten the intro by 30%.',
|
|
923
|
-
});
|
|
924
|
-
```
|
|
925
|
-
|
|
926
|
-
| Option | Description |
|
|
927
|
-
|--------|-------------|
|
|
928
|
-
| `data` | Body fields to add, update, or delete. `null` removes the field. Use `{{placeholder}}` for AI-generated content. Fields prefixed with `_` are hidden from AI. |
|
|
929
|
-
| `prompt` | Natural language instruction for AI to modify content. |
|
|
930
|
-
| `ephemeral` | If true, the operation won't be recorded in interaction history. |
|
|
931
|
-
| `parentInteractionId` | Conversation tree parent. Omit to auto-continue; pass `null` for a new root. |
|
|
932
|
-
|
|
933
|
-
Use `moveObject` to change an object's location (collection or basename).
|
|
934
|
-
|
|
935
|
-
#### Moving and Renaming
|
|
936
|
-
|
|
937
|
-
`moveObject` is how you rename an object (new basename in the same collection) or move it across collections. Pass `options.body` to atomically rewrite the body as part of the move.
|
|
938
|
-
|
|
939
|
-
```typescript
|
|
940
|
-
// Rename within the same collection
|
|
941
|
-
await channel.moveObject(
|
|
942
|
-
'/space/article/welcome.json',
|
|
943
|
-
'/space/article/hello-world.json',
|
|
944
|
-
);
|
|
945
|
-
|
|
946
|
-
// Move into a different collection
|
|
947
|
-
await channel.moveObject(
|
|
948
|
-
'/space/draft/post-42.json',
|
|
949
|
-
'/space/article/post-42.json',
|
|
950
|
-
);
|
|
388
|
+
Field kinds: `string`, `number`, `boolean`, `ref`, `enum`, `literal`, `array`, and `maybe`.
|
|
951
389
|
|
|
952
|
-
|
|
953
|
-
await channel.moveObject(from, to, {
|
|
954
|
-
body: { title: 'Hello, world', status: 'published' },
|
|
955
|
-
});
|
|
956
|
-
```
|
|
957
|
-
|
|
958
|
-
| Option | Description |
|
|
959
|
-
|--------|-------------|
|
|
960
|
-
| `body` | Replace the body atomically as part of the move. If omitted, the body is preserved. |
|
|
961
|
-
| `ephemeral` | If true, the operation won't be recorded in interaction history. |
|
|
962
|
-
| `parentInteractionId` | Conversation tree parent. Omit to auto-continue; pass `null` for a new root. |
|
|
390
|
+
## Undo/Redo
|
|
963
391
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
Find objects using structured filters and/or natural language.
|
|
967
|
-
|
|
968
|
-
- **`where` only** — exact-match filtering, no AI, no credits.
|
|
969
|
-
- **`collection` only** — filter by collection name, no AI, no credits.
|
|
970
|
-
- **`prompt` only** — AI-powered semantic query over all objects.
|
|
971
|
-
- **`where` + `prompt`** — `where` (and `locations`) narrow the data set first, then the AI queries within the constrained set.
|
|
972
|
-
|
|
973
|
-
| Option | Description |
|
|
974
|
-
|--------|-------------|
|
|
975
|
-
| `where` | Exact-match body-field filter (e.g. `{ status: 'published' }`). Values must match literally — no operators or `{{placeholders}}`. When combined with `prompt`, constrains which objects the AI can see. |
|
|
976
|
-
| `collection` | Filter by collection name. |
|
|
977
|
-
| `prompt` | Natural language query. Triggers AI evaluation (uses credits). |
|
|
978
|
-
| `limit` | Maximum number of results. |
|
|
979
|
-
| `locations` | Scope to specific object locations. Constrains the candidate set in both structured and AI queries. |
|
|
980
|
-
| `order` | Sort order by modifiedAt: `'asc'` or `'desc'` (default: `'desc'`). |
|
|
981
|
-
| `ephemeral` | If true, the query won't be recorded in interaction history. Useful for responsive search. |
|
|
982
|
-
|
|
983
|
-
**Examples:**
|
|
392
|
+
Undo/redo uses checkpoints for the current channel ID. A checkpoint captures space state; call `checkpoint()` before a user action you want to make undoable.
|
|
984
393
|
|
|
985
394
|
```typescript
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
collection: 'article'
|
|
989
|
-
});
|
|
990
|
-
|
|
991
|
-
// Exact field matching (no AI, no credits)
|
|
992
|
-
const { objects } = await channel.findObjects({
|
|
993
|
-
where: { status: 'published' }
|
|
994
|
-
});
|
|
995
|
-
|
|
996
|
-
// Combine collection and field filters
|
|
997
|
-
const { objects } = await channel.findObjects({
|
|
998
|
-
collection: 'article',
|
|
999
|
-
where: { status: 'published' }
|
|
1000
|
-
});
|
|
1001
|
-
|
|
1002
|
-
// Pure natural language query (AI interprets)
|
|
1003
|
-
const { objects, message } = await channel.findObjects({
|
|
1004
|
-
prompt: 'articles about space exploration published this year'
|
|
1005
|
-
});
|
|
395
|
+
await channel.checkpoint('Delete article');
|
|
396
|
+
await channel.deleteObjects(['/space/article/welcome.json']);
|
|
1006
397
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
prompt: 'that discuss climate solutions positively',
|
|
1011
|
-
limit: 10
|
|
1012
|
-
});
|
|
398
|
+
if (await channel.canUndo()) {
|
|
399
|
+
await channel.undo();
|
|
400
|
+
}
|
|
1013
401
|
```
|
|
1014
402
|
|
|
1015
|
-
When `where` or `locations` are provided with a `prompt`, the AI only sees the filtered subset — not the full space. The returned `message` explains the query result.
|
|
1016
|
-
|
|
1017
|
-
### Undo/Redo
|
|
1018
|
-
|
|
1019
|
-
| Method | Description |
|
|
1020
|
-
|--------|-------------|
|
|
1021
|
-
| `checkpoint(label?): Promise<string>` | Call before mutations. Saves current state for undo. |
|
|
1022
|
-
| `canUndo(): Promise<boolean>` | Check if undo available |
|
|
1023
|
-
| `canRedo(): Promise<boolean>` | Check if redo available |
|
|
1024
|
-
| `undo(): Promise<boolean>` | Undo to previous checkpoint |
|
|
1025
|
-
| `redo(): Promise<boolean>` | Redo undone action |
|
|
1026
|
-
| `clearHistory(): Promise<void>` | Clear undo/redo stack |
|
|
1027
|
-
|
|
1028
|
-
See [Checkpoints & Undo/Redo](#checkpoints--undoredo) for semantics.
|
|
1029
|
-
|
|
1030
|
-
### Space Metadata
|
|
1031
|
-
|
|
1032
|
-
Store arbitrary data alongside the space without it being part of an object's body (e.g., viewport state, user preferences).
|
|
1033
|
-
|
|
1034
403
|
| Method | Description |
|
|
1035
|
-
|
|
1036
|
-
| `
|
|
1037
|
-
| `
|
|
1038
|
-
| `
|
|
404
|
+
| --- | --- |
|
|
405
|
+
| `checkpoint(label?): Promise<string>` | Save current space state. |
|
|
406
|
+
| `canUndo(): Promise<boolean>` | Check whether undo is available. |
|
|
407
|
+
| `canRedo(): Promise<boolean>` | Check whether redo is available. |
|
|
408
|
+
| `undo(): Promise<boolean>` | Restore the latest checkpoint. |
|
|
409
|
+
| `redo(): Promise<boolean>` | Reapply undone work. |
|
|
410
|
+
| `clearHistory(): Promise<void>` | Clear checkpoint history. |
|
|
1039
411
|
|
|
1040
|
-
|
|
412
|
+
Undo/redo availability and history are scoped to the channel handle (`channel.channelId`).
|
|
1041
413
|
|
|
1042
|
-
|
|
414
|
+
## File Storage and WebDAV
|
|
1043
415
|
|
|
1044
|
-
|
|
416
|
+
Every space has authenticated WebDAV storage. WebDAV methods take SDK machine paths such as `/space/...`, `/rool-drive/...`, or `/` for the root collection.
|
|
1045
417
|
|
|
1046
418
|
```typescript
|
|
1047
|
-
|
|
419
|
+
const webdav = space.webdav;
|
|
1048
420
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
await webdav.mkcol('docs');
|
|
1052
|
-
await webdav.put('docs/readme.md', '# Hello', {
|
|
421
|
+
await webdav.mkcol('/rool-drive/docs');
|
|
422
|
+
await webdav.put('/rool-drive/docs/readme.md', '# Hello', {
|
|
1053
423
|
contentType: 'text/markdown',
|
|
1054
424
|
ifNoneMatch: '*',
|
|
1055
425
|
});
|
|
1056
426
|
|
|
1057
|
-
const listing = await webdav.propfind('docs
|
|
427
|
+
const listing = await webdav.propfind('/rool-drive/docs', {
|
|
1058
428
|
depth: '1',
|
|
1059
429
|
props: ['displayname', 'getcontentlength', 'getcontenttype', 'getetag'],
|
|
1060
430
|
});
|
|
1061
431
|
|
|
1062
|
-
const
|
|
1063
|
-
console.log(await
|
|
432
|
+
const response = await webdav.get('/rool-drive/docs/readme.md');
|
|
433
|
+
console.log(await response.text());
|
|
1064
434
|
|
|
1065
|
-
const
|
|
1066
|
-
|
|
1067
|
-
const sameFile = await space.fetchMachineResource(resource);
|
|
435
|
+
const file = await space.fetchPath('/rool-drive/docs/readme.md');
|
|
436
|
+
console.log(file.headers.get('Content-Type'));
|
|
1068
437
|
|
|
1069
438
|
const usage = await space.getStorageUsage();
|
|
1070
|
-
console.log(usage.usedBytes);
|
|
1071
|
-
console.log(usage.availableBytes); // null means unlimited
|
|
1072
|
-
console.log(usage.limitBytes); // null means unlimited
|
|
1073
|
-
|
|
1074
|
-
const rootProps = await webdav.propfind('', {
|
|
1075
|
-
depth: '0',
|
|
1076
|
-
props: ['sync-token', 'supported-report-set'],
|
|
1077
|
-
});
|
|
1078
|
-
let syncToken = rootProps.responses[0]?.props.syncToken ?? null;
|
|
1079
|
-
|
|
1080
|
-
space.on('filesChanged', async () => {
|
|
1081
|
-
const delta = await space.webdav.syncCollection('', {
|
|
1082
|
-
token: syncToken,
|
|
1083
|
-
level: 'infinite',
|
|
1084
|
-
});
|
|
1085
|
-
syncToken = delta.token;
|
|
1086
|
-
console.log('Changed file responses:', delta.responses);
|
|
1087
|
-
});
|
|
439
|
+
console.log(usage.usedBytes, usage.availableBytes, usage.limitBytes);
|
|
1088
440
|
```
|
|
1089
441
|
|
|
1090
|
-
|
|
442
|
+
### Real-time file sync
|
|
1091
443
|
|
|
1092
|
-
|
|
1093
|
-
|--------|-------------|
|
|
1094
|
-
| `client.webdav(spaceId)` | Create a WebDAV client for a space |
|
|
1095
|
-
| `client.getSpaceStorageUsage(spaceId)` | Get WebDAV quota usage for a space |
|
|
1096
|
-
| `space.webdav` | WebDAV client for an open space |
|
|
1097
|
-
| `space.getStorageUsage()` | Get WebDAV quota usage for an open space |
|
|
1098
|
-
| `webdav.getStorageUsage()` | Get WebDAV quota usage through the WebDAV client |
|
|
1099
|
-
| `webdav.path(path)` | Normalize a WebDAV path |
|
|
1100
|
-
| `webdav.propfind(path, options)` | Read properties/list collections; explicit `depth` required. Supports `sync-token` and `supported-report-set` props. |
|
|
1101
|
-
| `webdav.syncCollection(path, options)` | Reconcile WebDAV changes with `REPORT sync-collection`. Pass the previous `token` (or `null`), `level: '1' \| 'infinite'`, optional `props`/`limit`; returns changed responses plus the next `token`. |
|
|
1102
|
-
| `webdav.get(path, options?)` / `webdav.head(path)` | Read a file, including optional byte ranges for `get` |
|
|
1103
|
-
| `webdav.put(path, body, options?)` | Write an exact file path; parents must already exist |
|
|
1104
|
-
| `webdav.mkcol(path)` | Create one collection |
|
|
1105
|
-
| `webdav.copy(source, destination, options?)` | Copy a file or collection within the same space |
|
|
1106
|
-
| `webdav.move(source, destination, options?)` | Move a file or collection within the same space |
|
|
1107
|
-
| `webdav.delete(path, options?)` | Delete a file or collection |
|
|
1108
|
-
| `webdav.lock(path, options)` / `webdav.refreshLock(path, token)` / `webdav.unlock(token)` | WebDAV Class 2 write locks |
|
|
1109
|
-
| `webdav.request(method, path, init?)` | Raw authenticated WebDAV request escape hatch |
|
|
1110
|
-
|
|
1111
|
-
> **Note**: `resolveMachineResource()` returns either a file resource or an object resource. File resources point at user-visible files in the space's WebDAV storage and can be fetched with `space.fetchMachineResource(resource)`. Object resources identify records inside the space. They're not interchangeable.
|
|
1112
|
-
|
|
1113
|
-
#### File references from AI responses
|
|
1114
|
-
|
|
1115
|
-
When an agent refers to a user-visible file, the SDK contract is `rool-machine:/rool-drive/path/to/file.ext`. That prefix makes a file reference unambiguous without exposing the authenticated WebDAV URL. In free text, ambiguous characters such as spaces are percent-encoded (`rool-machine:/rool-drive/docs/read%20me.md`).
|
|
444
|
+
Object and file changes are announced at the space level. Use WebDAV `syncCollection()` to reconcile changes.
|
|
1116
445
|
|
|
1117
446
|
```typescript
|
|
1118
|
-
|
|
1119
|
-
if (!resource || resource.kind !== 'file') throw new Error('not a file');
|
|
1120
|
-
const response = await space.fetchMachineResource(resource);
|
|
1121
|
-
const blob = await response.blob();
|
|
1122
|
-
img.src = URL.createObjectURL(blob);
|
|
1123
|
-
```
|
|
1124
|
-
|
|
1125
|
-
Plain relative strings like `docs/readme.md` are valid WebDAV paths when you already know you are working with file storage. In user text or agent output, use `rool-machine:/rool-drive/docs/readme.md` so clients do not have to guess whether a string is a file.
|
|
447
|
+
let token: string | null = null;
|
|
1126
448
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
449
|
+
async function syncFiles() {
|
|
450
|
+
const result = await space.webdav.syncCollection('/', {
|
|
451
|
+
token,
|
|
452
|
+
level: 'infinite',
|
|
453
|
+
props: ['displayname', 'getetag', 'getlastmodified', 'resourcetype'],
|
|
454
|
+
});
|
|
455
|
+
token = result.token;
|
|
456
|
+
updateFileTree(result.responses);
|
|
457
|
+
}
|
|
1134
458
|
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
// POST with headers and body
|
|
1141
|
-
const response = await channel.fetch('https://api.example.com/submit', {
|
|
1142
|
-
method: 'POST',
|
|
1143
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1144
|
-
body: { key: 'value' },
|
|
459
|
+
space.on('filesChanged', syncFiles);
|
|
460
|
+
space.on('filesReset', () => {
|
|
461
|
+
token = null;
|
|
462
|
+
void syncFiles();
|
|
1145
463
|
});
|
|
1146
|
-
```
|
|
1147
|
-
|
|
1148
|
-
### Collection Schema
|
|
1149
464
|
|
|
1150
|
-
|
|
465
|
+
await syncFiles();
|
|
466
|
+
```
|
|
1151
467
|
|
|
1152
|
-
|
|
468
|
+
| Method | Description |
|
|
469
|
+
| --- | --- |
|
|
470
|
+
| `webdav.href(path)` / `webdav.url(path)` | Return WebDAV href/URL for an absolute SDK path. |
|
|
471
|
+
| `webdav.options(path)` | Send `OPTIONS`. |
|
|
472
|
+
| `webdav.propfind(path, options)` | Read properties/list collections. `depth` is required. |
|
|
473
|
+
| `webdav.syncCollection(path, options)` | WebDAV `REPORT sync-collection`; returns changed responses and next token. |
|
|
474
|
+
| `webdav.get(path, options?)` / `webdav.head(path)` | Read a file; `get` supports byte ranges. |
|
|
475
|
+
| `webdav.put(path, body, options?)` | Write a file/object at an exact path. Parent collection must exist. |
|
|
476
|
+
| `webdav.mkcol(path)` | Create one collection. |
|
|
477
|
+
| `webdav.copy(source, destination, options?)` | Copy a file or collection. |
|
|
478
|
+
| `webdav.move(source, destination, options?)` | Move a file or collection. |
|
|
479
|
+
| `webdav.delete(path, options?)` | Delete a file or collection. |
|
|
480
|
+
| `webdav.lock(path, options)` / `refreshLock(path, token)` / `unlock(token)` | WebDAV write locks. |
|
|
481
|
+
| `webdav.request(method, path, init?)` | Raw authenticated WebDAV request. |
|
|
482
|
+
| `space.fetchPath(path, options?)` | Fetch a `/rool-drive/...` file path or `rool-machine:` file URI. |
|
|
483
|
+
| `space.getStorageUsage()` / `webdav.getStorageUsage()` | Storage quota usage. |
|
|
484
|
+
|
|
485
|
+
High-level WebDAV methods that validate response status throw `WebDAVError` with `status`, `statusText`, and `body`; raw `request()` and `options()` return `Response`.
|
|
1153
486
|
|
|
487
|
+
## Collaboration
|
|
1154
488
|
|
|
1155
489
|
```typescript
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
{ name: 'tags', type: { kind: 'array', inner: { kind: 'string' } } },
|
|
1161
|
-
{ name: 'author', type: { kind: 'ref' } },
|
|
1162
|
-
]);
|
|
1163
|
-
|
|
1164
|
-
// Read the current schema
|
|
1165
|
-
const schema = channel.getSchema();
|
|
1166
|
-
console.log(schema.article.fields); // FieldDef[]
|
|
1167
|
-
|
|
1168
|
-
// Modify an existing collection's fields
|
|
1169
|
-
await channel.alterCollection('article', [
|
|
1170
|
-
{ name: 'title', type: { kind: 'string' } },
|
|
1171
|
-
{ name: 'status', type: { kind: 'enum', values: ['draft', 'review', 'published', 'archived'] } },
|
|
1172
|
-
{ name: 'tags', type: { kind: 'array', inner: { kind: 'string' } } },
|
|
1173
|
-
{ name: 'author', type: { kind: 'ref' } },
|
|
1174
|
-
{ name: 'wordCount', type: { kind: 'number' } },
|
|
1175
|
-
]);
|
|
490
|
+
const user = await client.searchUser('colleague@example.com');
|
|
491
|
+
if (user) {
|
|
492
|
+
await space.addUser(user.id, 'editor');
|
|
493
|
+
}
|
|
1176
494
|
|
|
1177
|
-
//
|
|
1178
|
-
await channel.dropCollection('article');
|
|
495
|
+
await space.setLinkAccess('viewer'); // 'none' | 'viewer' | 'editor'
|
|
1179
496
|
```
|
|
1180
497
|
|
|
1181
|
-
|
|
1182
|
-
|--------|-------------|
|
|
1183
|
-
| `getSchema(): SpaceSchema` | Get all collection definitions |
|
|
1184
|
-
| `createCollection(name, fields): Promise<CollectionDef>` | Add a new collection to the schema |
|
|
1185
|
-
| `alterCollection(name, fields): Promise<CollectionDef>` | Replace a collection's field definitions |
|
|
1186
|
-
| `dropCollection(name): Promise<void>` | Remove a collection from the schema |
|
|
1187
|
-
|
|
1188
|
-
#### Field Types
|
|
1189
|
-
|
|
1190
|
-
| Kind | Description | Example |
|
|
1191
|
-
|------|-------------|---------|
|
|
1192
|
-
| `string` | Text value | `{ kind: 'string' }` |
|
|
1193
|
-
| `number` | Numeric value | `{ kind: 'number' }` |
|
|
1194
|
-
| `boolean` | True/false | `{ kind: 'boolean' }` |
|
|
1195
|
-
| `ref` | Reference to another object (location string) | `{ kind: 'ref' }` |
|
|
1196
|
-
| `enum` | One of a set of values | `{ kind: 'enum', values: ['a', 'b'] }` |
|
|
1197
|
-
| `literal` | Exact value | `{ kind: 'literal', value: 'fixed' }` |
|
|
1198
|
-
| `array` | List of values | `{ kind: 'array', inner: { kind: 'string' } }` |
|
|
1199
|
-
| `maybe` | Optional (nullable) | `{ kind: 'maybe', inner: { kind: 'number' } }` |
|
|
498
|
+
Roles:
|
|
1200
499
|
|
|
1201
|
-
|
|
500
|
+
| Role | Capabilities |
|
|
501
|
+
| --- | --- |
|
|
502
|
+
| `owner` | Full control. |
|
|
503
|
+
| `admin` | Editor capabilities plus user/link management. |
|
|
504
|
+
| `editor` | Create, modify, move, and delete objects/files. |
|
|
505
|
+
| `viewer` | Read-only access. |
|
|
1202
506
|
|
|
1203
|
-
|
|
507
|
+
## RoolClient API
|
|
1204
508
|
|
|
1205
|
-
|
|
1206
|
-
|--------|-------------|
|
|
1207
|
-
| `space.exportArchive(): Promise<Blob>` | Export objects, metadata, channels, and files as a zip archive |
|
|
1208
|
-
| `client.importArchive(name, archive): Promise<RoolSpace>` | Import from a zip archive, creating a new space |
|
|
509
|
+
### Constructor config
|
|
1209
510
|
|
|
1210
|
-
**Export:**
|
|
1211
511
|
```typescript
|
|
1212
|
-
const
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
512
|
+
const client = new RoolClient({
|
|
513
|
+
apiUrl: 'https://api.rool.dev',
|
|
514
|
+
authUrl: 'https://rool.dev/auth',
|
|
515
|
+
graphqlUrl: 'https://api.rool.dev/graphql',
|
|
516
|
+
logger: console,
|
|
517
|
+
});
|
|
1216
518
|
```
|
|
1217
519
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
520
|
+
`apiUrl` defaults to `https://api.rool.dev`; `authUrl` is derived by stripping the `api.` hostname prefix unless provided. `baseUrl` is still accepted as a deprecated alias for `apiUrl`. Pass `authProvider` for Node.js, Electron, or custom auth flows.
|
|
521
|
+
|
|
522
|
+
| Method/property | Description |
|
|
523
|
+
| --- | --- |
|
|
524
|
+
| `currentUser: CurrentUser | null` | Cached user profile from initialization/fetch. |
|
|
525
|
+
| `getCurrentUser(): Promise<CurrentUser>` | Fetch current user. |
|
|
526
|
+
| `updateCurrentUser(input): Promise<CurrentUser>` | Update `name`, `slug`, or `marketingOptIn`. |
|
|
527
|
+
| `deleteCurrentUser(): Promise<void>` | Mark account for deletion and log out. |
|
|
528
|
+
| `searchUser(email): Promise<UserResult | null>` | Exact email lookup. |
|
|
529
|
+
| `listSpaces(): Promise<RoolSpaceInfo[]>` | List accessible spaces. |
|
|
530
|
+
| `openSpace(id): Promise<RoolSpace>` | Open/cached live space handle. |
|
|
531
|
+
| `createSpace(name): Promise<RoolSpace>` | Create and open a space. |
|
|
532
|
+
| `duplicateSpace(sourceId, name): Promise<RoolSpace>` | Duplicate a space. |
|
|
533
|
+
| `deleteSpace(id): Promise<void>` | Permanently delete a space. |
|
|
534
|
+
| `importArchive(name, archive): Promise<RoolSpace>` | Import a zip archive as a new space. |
|
|
535
|
+
| `getUserStorage<T>(key): T | undefined` | Sync read from user-storage cache. |
|
|
536
|
+
| `setUserStorage(key, value): void` | Update user storage; `null`/`undefined` deletes. |
|
|
537
|
+
| `getAllUserStorage(): Record<string, unknown>` | Copy all cached user storage. |
|
|
538
|
+
| `reportEvent(event, url?): void` | Fire-and-forget telemetry event. |
|
|
539
|
+
| `destroy(): void` | Close subscriptions, spaces, auth resources, and listeners. |
|
|
540
|
+
| `generateId(): string` | Generate a 6-character alphanumeric ID. |
|
|
541
|
+
|
|
542
|
+
### Client events
|
|
543
|
+
|
|
544
|
+
```typescript
|
|
545
|
+
client.on('authStateChanged', (authenticated) => void 0);
|
|
546
|
+
client.on('spaceAdded', (space) => void 0);
|
|
547
|
+
client.on('spaceRemoved', (spaceId) => void 0);
|
|
548
|
+
client.on('spaceRenamed', (spaceId, newName) => void 0);
|
|
549
|
+
client.on('channelCreated', (spaceId, channel) => void 0);
|
|
550
|
+
client.on('channelUpdated', (spaceId, channel) => void 0);
|
|
551
|
+
client.on('channelDeleted', (spaceId, channelId) => void 0);
|
|
552
|
+
client.on('userStorageChanged', ({ key, value, source }) => void 0);
|
|
553
|
+
client.on('connectionStateChanged', (state) => void 0);
|
|
554
|
+
client.on('error', (error, context) => void 0);
|
|
1222
555
|
```
|
|
1223
556
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
### Channel Events
|
|
1227
|
-
|
|
1228
|
-
Semantic events describe what changed. Events fire for both local changes and remote changes.
|
|
1229
|
-
|
|
1230
|
-
```typescript
|
|
1231
|
-
// source indicates origin:
|
|
1232
|
-
// - 'local_user': This client made the change
|
|
1233
|
-
// - 'remote_user': Another user/client made the change
|
|
1234
|
-
// - 'remote_agent': AI agent made the change
|
|
1235
|
-
// - 'system': Resync after error
|
|
557
|
+
## RoolSpace API
|
|
1236
558
|
|
|
1237
|
-
|
|
1238
|
-
channel.on('objectCreated', ({ location, object, source }) => void)
|
|
1239
|
-
channel.on('objectUpdated', ({ location, object, source }) => void)
|
|
1240
|
-
channel.on('objectDeleted', ({ location, source }) => void)
|
|
1241
|
-
channel.on('objectMoved', ({ from, to, object, source }) => void)
|
|
559
|
+
Properties: `id`, `name`, `role`, `linkAccess`, `memberCount`, `channels`, `route`, `webdav`.
|
|
1242
560
|
|
|
1243
|
-
|
|
1244
|
-
|
|
561
|
+
| Method | Description |
|
|
562
|
+
| --- | --- |
|
|
563
|
+
| `openChannel(channelId): Promise<RoolChannel>` | Open/create a channel. |
|
|
564
|
+
| `close(): void` | Stop subscription and close open channels. |
|
|
565
|
+
| `rename(newName): Promise<void>` | Rename the space. |
|
|
566
|
+
| `delete(): Promise<void>` | Permanently delete the space. |
|
|
567
|
+
| `listUsers(): Promise<SpaceMember[]>` | List collaborators. |
|
|
568
|
+
| `addUser(userId, role): Promise<void>` | Add collaborator. |
|
|
569
|
+
| `removeUser(userId): Promise<void>` | Remove collaborator. |
|
|
570
|
+
| `setLinkAccess(linkAccess): Promise<void>` | Set URL sharing level. |
|
|
571
|
+
| `renameChannel(channelId, name): Promise<void>` | Rename a channel. |
|
|
572
|
+
| `deleteChannel(channelId): Promise<void>` | Delete a channel and history. |
|
|
573
|
+
| `exportArchive(): Promise<Blob>` | Export a space archive. |
|
|
574
|
+
| `refresh(): Promise<void>` | Refresh cached space data. |
|
|
575
|
+
| `fetchPath(path, options?): Promise<Response>` | Fetch a `/rool-drive/...` file. |
|
|
576
|
+
| `getStorageUsage(): Promise<SpaceFileStorageUsage>` | File-storage quota usage. |
|
|
577
|
+
|
|
578
|
+
Events:
|
|
579
|
+
|
|
580
|
+
```typescript
|
|
581
|
+
space.on('channelCreated', (channel) => void 0);
|
|
582
|
+
space.on('channelUpdated', (channel) => void 0);
|
|
583
|
+
space.on('channelDeleted', (channelId) => void 0);
|
|
584
|
+
space.on('filesChanged', ({ spaceId, source, timestamp }) => void 0);
|
|
585
|
+
space.on('filesReset', ({ spaceId, source, timestamp }) => void 0);
|
|
586
|
+
space.on('connectionStateChanged', (state) => void 0);
|
|
587
|
+
```
|
|
1245
588
|
|
|
1246
|
-
|
|
1247
|
-
channel.on('schemaUpdated', ({ schema, source }) => void)
|
|
589
|
+
## RoolChannel API
|
|
1248
590
|
|
|
1249
|
-
|
|
1250
|
-
channel.on('channelUpdated', ({ channelId, source }) => void)
|
|
591
|
+
Properties: `id` (space ID), `name` (space name), `role`, `linkAccess`, `userId`, `channelId`, `channelName`, `conversationId`, `isReadOnly`, `activeLeafId`.
|
|
1251
592
|
|
|
1252
|
-
|
|
1253
|
-
|
|
593
|
+
| Area | Methods |
|
|
594
|
+
| --- | --- |
|
|
595
|
+
| Lifecycle | `close()`, `rename(name)`, `conversation(id)` |
|
|
596
|
+
| Objects | `getObject`, `getObjects`, `stat`, `putObject`, `patchObject`, `moveObject`, `deleteObjects` |
|
|
597
|
+
| Schema | `getSchema`, `createCollection`, `alterCollection`, `dropCollection` |
|
|
598
|
+
| Metadata | `setMetadata`, `getMetadata`, `getAllMetadata` |
|
|
599
|
+
| Conversations | `getInteractions`, `getTree`, `setActiveLeaf`, `getConversations`, `deleteConversation`, `getSystemInstruction`, `setSystemInstruction`, `renameConversation` |
|
|
600
|
+
| AI | `prompt`, `stop`, `stopInteraction` |
|
|
601
|
+
| Undo/redo | `checkpoint`, `canUndo`, `canRedo`, `undo`, `redo`, `clearHistory` |
|
|
602
|
+
| Utilities | `fetch(url, init?)` server-side proxied fetch |
|
|
1254
603
|
|
|
1255
|
-
|
|
1256
|
-
channel.on('reset', ({ source }) => void)
|
|
604
|
+
Channel events:
|
|
1257
605
|
|
|
1258
|
-
|
|
1259
|
-
channel.on('
|
|
606
|
+
```typescript
|
|
607
|
+
channel.on('metadataUpdated', ({ metadata, source }) => void 0);
|
|
608
|
+
channel.on('schemaUpdated', ({ schema, source }) => void 0);
|
|
609
|
+
channel.on('channelUpdated', ({ channelId, source }) => void 0);
|
|
610
|
+
channel.on('conversationUpdated', ({ conversationId, channelId, source }) => void 0);
|
|
611
|
+
channel.on('reset', ({ source }) => void 0);
|
|
612
|
+
channel.on('syncError', (error) => void 0);
|
|
1260
613
|
```
|
|
1261
614
|
|
|
1262
|
-
|
|
615
|
+
`channel.fetch(url, init?)` proxies external HTTP requests through the server to bypass browser CORS.
|
|
1263
616
|
|
|
1264
|
-
|
|
617
|
+
## Import/Export
|
|
1265
618
|
|
|
1266
619
|
```typescript
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
} catch (error) {
|
|
1270
|
-
if (error.message.includes('temporarily unavailable')) {
|
|
1271
|
-
showToast('Service busy, please try again in a moment');
|
|
1272
|
-
} else {
|
|
1273
|
-
showToast(error.message);
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
620
|
+
const archive = await space.exportArchive();
|
|
621
|
+
const imported = await client.importArchive('Imported Data', archive);
|
|
1276
622
|
```
|
|
1277
623
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
Each channel contains one or more conversations, each with its own interaction history. History is stored as a tree (interactions linked by `parentId`) in the space data and syncs in real-time. Capped at 200 interactions per conversation.
|
|
1281
|
-
|
|
1282
|
-
### Conversation History Methods
|
|
1283
|
-
|
|
1284
|
-
| Method | Description |
|
|
1285
|
-
|--------|-------------|
|
|
1286
|
-
| `getInteractions(): Interaction[]` | Get the active branch as a flat array (root → leaf) |
|
|
1287
|
-
| `getTree(): Record<string, Interaction>` | Get the full interaction tree for branch navigation |
|
|
1288
|
-
| `activeLeafId: string \| undefined` | The tip of the currently active branch |
|
|
1289
|
-
| `setActiveLeaf(id: string): void` | Switch to a different branch (emits `conversationUpdated`) |
|
|
1290
|
-
| `getSystemInstruction(): string \| undefined` | Get system instruction for the default conversation |
|
|
1291
|
-
| `setSystemInstruction(instruction): Promise<void>` | Set system instruction for the default conversation. Pass `null` to clear. |
|
|
1292
|
-
| `getConversations(): ConversationInfo[]` | List all conversations in this channel |
|
|
1293
|
-
| `deleteConversation(conversationId): Promise<void>` | Delete a conversation (cannot delete `'default'`) |
|
|
1294
|
-
| `renameConversation(name): Promise<void>` | Rename the default conversation |
|
|
1295
|
-
|
|
1296
|
-
Channel management (listing, renaming, deleting channels) is done via the client — see [Channel Management](#channel-management).
|
|
1297
|
-
|
|
1298
|
-
### The ai Field
|
|
1299
|
-
|
|
1300
|
-
The `ai` field in interactions distinguishes AI-generated responses from synthetic confirmations:
|
|
1301
|
-
- `ai: true` — AI processed this operation (prompt, or createObject/updateObject with placeholders)
|
|
1302
|
-
- `ai: false` — System confirmation only (e.g., "Created object /space/note/welcome.json")
|
|
1303
|
-
|
|
1304
|
-
### Tool Calls
|
|
1305
|
-
|
|
1306
|
-
The `toolCalls` array captures what the AI agent did during execution. The `conversationUpdated` event fires when each tool starts and completes. A tool call with `status: 'running'` has no result; once `status: 'done'`, `result` contains the truncated result string.
|
|
624
|
+
Archives include objects, metadata, channels/conversations, and file storage.
|
|
1307
625
|
|
|
1308
626
|
## Data Types
|
|
1309
627
|
|
|
1310
|
-
### Schema Types
|
|
1311
|
-
|
|
1312
628
|
```typescript
|
|
1313
|
-
// Allowed field types
|
|
1314
629
|
type FieldType =
|
|
1315
630
|
| { kind: 'string' }
|
|
1316
631
|
| { kind: 'number' }
|
|
@@ -1328,150 +643,66 @@ interface FieldDef {
|
|
|
1328
643
|
|
|
1329
644
|
interface CollectionDef {
|
|
1330
645
|
fields: FieldDef[];
|
|
646
|
+
schemaOrgType?: string;
|
|
1331
647
|
}
|
|
1332
648
|
|
|
1333
|
-
// Full schema — collection names to definitions
|
|
1334
649
|
type SpaceSchema = Record<string, CollectionDef>;
|
|
1335
|
-
```
|
|
1336
|
-
|
|
1337
|
-
### Object Data
|
|
1338
650
|
|
|
1339
|
-
```typescript
|
|
1340
|
-
// An object addressed by location. References between objects are body
|
|
1341
|
-
// fields whose values are location strings.
|
|
1342
651
|
interface RoolObject {
|
|
1343
|
-
|
|
1344
|
-
collection: string;
|
|
1345
|
-
basename: string;
|
|
652
|
+
path: string;
|
|
1346
653
|
body: Record<string, unknown>;
|
|
1347
654
|
}
|
|
1348
655
|
|
|
1349
|
-
|
|
656
|
+
interface GetObjectsResult {
|
|
657
|
+
objects: RoolObject[];
|
|
658
|
+
missing: string[];
|
|
659
|
+
}
|
|
660
|
+
|
|
1350
661
|
interface RoolObjectStat {
|
|
1351
|
-
|
|
662
|
+
path: string;
|
|
1352
663
|
modifiedAt: number;
|
|
1353
664
|
modifiedBy: string;
|
|
1354
665
|
modifiedByName: string | null;
|
|
1355
|
-
modifiedInChannel: string;
|
|
1356
|
-
modifiedInConversation: string | null;
|
|
1357
|
-
modifiedInInteraction: string | null;
|
|
1358
|
-
}
|
|
1359
|
-
```
|
|
1360
|
-
|
|
1361
|
-
### Channels and Conversations
|
|
1362
|
-
|
|
1363
|
-
```typescript
|
|
1364
|
-
// Conversation — holds interaction tree and optional system instruction
|
|
1365
|
-
interface Conversation {
|
|
1366
|
-
name?: string; // Conversation name (optional)
|
|
1367
|
-
systemInstruction?: string; // Custom system instruction for AI
|
|
1368
|
-
createdAt: number; // Timestamp when conversation was created
|
|
1369
|
-
createdBy: string; // User ID who created the conversation
|
|
1370
|
-
interactions: Record<string, Interaction>; // Interaction tree (keyed by ID, linked by parentId)
|
|
666
|
+
modifiedInChannel: string;
|
|
667
|
+
modifiedInConversation: string | null;
|
|
668
|
+
modifiedInInteraction: string | null;
|
|
1371
669
|
}
|
|
1372
670
|
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
createdAt: number;
|
|
1379
|
-
createdBy: string;
|
|
1380
|
-
interactionCount: number;
|
|
1381
|
-
}
|
|
671
|
+
type PromptAttachment =
|
|
672
|
+
| File
|
|
673
|
+
| Blob
|
|
674
|
+
| { data: string; contentType: string; filename?: string }
|
|
675
|
+
| string;
|
|
1382
676
|
|
|
1383
|
-
|
|
1384
|
-
interface Channel {
|
|
1385
|
-
name?: string; // Channel name (optional)
|
|
1386
|
-
createdAt: number; // Timestamp when channel was created
|
|
1387
|
-
createdBy: string; // User ID who created the channel
|
|
1388
|
-
createdByName?: string; // Display name at time of creation
|
|
1389
|
-
extensionUrl?: string; // URL of installed extension (set by installExtension)
|
|
1390
|
-
extensionId?: string; // ID of installed extension (user_extensions.extension_id)
|
|
1391
|
-
manifest?: ExtensionManifest; // Extension manifest snapshot (set when extension is wired)
|
|
1392
|
-
conversations: Record<string, Conversation>; // Keyed by conversation ID
|
|
1393
|
-
}
|
|
677
|
+
type PromptEffort = 'QUICK' | 'STANDARD' | 'REASONING' | 'RESEARCH';
|
|
1394
678
|
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
extensionId: string | null; // ID of installed extension, or null
|
|
1405
|
-
manifest: ExtensionManifest | null; // Extension manifest snapshot, or null
|
|
679
|
+
interface PromptOptions {
|
|
680
|
+
responseSchema?: Record<string, unknown>;
|
|
681
|
+
effort?: PromptEffort;
|
|
682
|
+
parentInteractionId?: string | null;
|
|
683
|
+
ephemeral?: boolean;
|
|
684
|
+
readOnly?: boolean;
|
|
685
|
+
attachments?: PromptAttachment[];
|
|
686
|
+
signal?: AbortSignal;
|
|
687
|
+
eventName?: string;
|
|
1406
688
|
}
|
|
1407
|
-
```
|
|
1408
|
-
|
|
1409
|
-
Note: `Channel` and `ChannelInfo` are data types describing the stored channel metadata. The `Channel` interface is the wire format; `RoolChannel` is the live SDK class you interact with.
|
|
1410
|
-
|
|
1411
|
-
### Interaction Types
|
|
1412
|
-
|
|
1413
|
-
```typescript
|
|
1414
|
-
type ToolCall =
|
|
1415
|
-
| {
|
|
1416
|
-
id: string;
|
|
1417
|
-
name: string; // Tool name (e.g., "create_object", "update_object", "search_web")
|
|
1418
|
-
input: unknown; // Arguments passed to the tool
|
|
1419
|
-
status: 'running';
|
|
1420
|
-
}
|
|
1421
|
-
| {
|
|
1422
|
-
id: string;
|
|
1423
|
-
name: string;
|
|
1424
|
-
input: unknown;
|
|
1425
|
-
status: 'done';
|
|
1426
|
-
result: string; // Truncated result
|
|
1427
|
-
};
|
|
1428
689
|
|
|
1429
690
|
type InteractionStatus = 'pending' | 'streaming' | 'done' | 'error';
|
|
1430
691
|
|
|
1431
692
|
interface Interaction {
|
|
1432
|
-
id: string;
|
|
1433
|
-
parentId: string | null;
|
|
693
|
+
id: string;
|
|
694
|
+
parentId: string | null;
|
|
1434
695
|
timestamp: number;
|
|
1435
|
-
userId: string;
|
|
1436
|
-
userName?: string | null;
|
|
1437
|
-
operation: 'prompt' | '
|
|
1438
|
-
input: string;
|
|
1439
|
-
output: string | null;
|
|
1440
|
-
status: InteractionStatus;
|
|
1441
|
-
ai: boolean;
|
|
1442
|
-
|
|
1443
|
-
toolCalls: ToolCall[];
|
|
1444
|
-
attachments?: string[];
|
|
1445
|
-
}
|
|
1446
|
-
```
|
|
1447
|
-
|
|
1448
|
-
### Info Types
|
|
1449
|
-
|
|
1450
|
-
```typescript
|
|
1451
|
-
type RoolUserRole = 'owner' | 'admin' | 'editor' | 'viewer';
|
|
1452
|
-
type LinkAccess = 'none' | 'viewer' | 'editor';
|
|
1453
|
-
|
|
1454
|
-
interface RoolSpaceInfo { id: string; name: string; inboundEmailAddress: string; role: RoolUserRole; ownerId: string; size: number; createdAt: string; updatedAt: string; linkAccess: LinkAccess; memberCount: number; }
|
|
1455
|
-
interface SpaceMember { id: string; email: string; role: RoolUserRole; photoUrl: string | null; }
|
|
1456
|
-
interface UserResult { id: string; email: string; name: string | null; photoUrl: string | null; }
|
|
1457
|
-
interface CurrentUser { id: string; email: string; name: string | null; photoUrl: string | null; slug: string; plan: string; creditsBalance: number; totalCreditsUsed: number; createdAt: string; lastActivity: string; processedAt: string; storage: Record<string, unknown>; }
|
|
1458
|
-
type ChangeSource = 'local_user' | 'remote_user' | 'remote_agent' | 'system';
|
|
1459
|
-
```
|
|
1460
|
-
|
|
1461
|
-
### Prompt Options
|
|
1462
|
-
|
|
1463
|
-
```typescript
|
|
1464
|
-
type PromptEffort = 'QUICK' | 'STANDARD' | 'REASONING' | 'RESEARCH';
|
|
1465
|
-
type PromptAttachment = File | Blob | { data: string; contentType: string; filename?: string } | MachineResource;
|
|
1466
|
-
|
|
1467
|
-
interface PromptOptions {
|
|
1468
|
-
responseSchema?: Record<string, unknown>;
|
|
1469
|
-
effort?: PromptEffort; // Effort level (default: 'STANDARD')
|
|
1470
|
-
ephemeral?: boolean; // Don't record in interaction history
|
|
1471
|
-
readOnly?: boolean; // Disable mutation tools (default: false)
|
|
1472
|
-
parentInteractionId?: string | null; // Branch from a specific interaction (omit to auto-continue)
|
|
1473
|
-
attachments?: PromptAttachment[]; // Machine resources or local files to upload
|
|
1474
|
-
signal?: AbortSignal; // Cancel an in-flight prompt
|
|
696
|
+
userId: string;
|
|
697
|
+
userName?: string | null;
|
|
698
|
+
operation: 'prompt' | 'putObject' | 'patchObject' | 'moveObject' | 'deleteObjects' | 'deletePaths' | string;
|
|
699
|
+
input: string;
|
|
700
|
+
output: string | null;
|
|
701
|
+
status: InteractionStatus;
|
|
702
|
+
ai: boolean;
|
|
703
|
+
modifiedObjectPaths: string[];
|
|
704
|
+
toolCalls: ToolCall[];
|
|
705
|
+
attachments?: string[];
|
|
1475
706
|
}
|
|
1476
707
|
```
|
|
1477
708
|
|