@rool-dev/sdk 0.10.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +438 -1236
- package/dist/channel.d.ts +41 -129
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +216 -391
- 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,582 @@ 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
|
-
});
|
|
24
|
+
async function main() {
|
|
25
|
+
const client = new RoolClient();
|
|
60
26
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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.
|
|
82
|
-
|
|
83
|
-
There are two main handles:
|
|
84
|
-
- **`RoolSpace`** — Live handle with SSE subscription for user management, link access, channel management, file storage, export, and channel lifecycle events. Extends `EventEmitter`.
|
|
85
|
-
- **`RoolChannel`** — Full real-time handle for objects, AI prompts, schema, and undo/redo.
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
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
|
-
|
|
72
|
+
## Paths and Resource URIs
|
|
135
73
|
|
|
136
|
-
|
|
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');
|
|
74
|
+
Most SDK methods take plain path strings:
|
|
146
75
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### Branching Conversations
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
// Normal conversation — each prompt auto-continues from the last
|
|
159
|
-
await thread.prompt('My favorite color is blue. Say OK.');
|
|
160
|
-
await thread.prompt('What is my favorite color?'); // Sees "blue"
|
|
82
|
+
import { machinePath, machineUri, isObjectPath } from '@rool-dev/sdk';
|
|
161
83
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const tree = thread.getTree();
|
|
165
|
-
const firstInteractionId = tree[firstLeaf!].parentId!; // The root
|
|
84
|
+
machinePath('rool-machine:/rool-drive/docs/read%20me.md');
|
|
85
|
+
// '/rool-drive/docs/read me.md'
|
|
166
86
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
});
|
|
170
|
-
await thread.prompt('What is my favorite color?'); // Sees "red", not "blue"
|
|
87
|
+
machineUri('/space/article/welcome.json');
|
|
88
|
+
// 'rool-machine:/space/article/welcome.json'
|
|
171
89
|
|
|
172
|
-
//
|
|
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
|
-
```
|
|
258
|
-
|
|
259
|
-
`rool-machine:` is the canonical URI scheme for user-visible resources from the Rool machine filesystem. `resolveMachineResource()` accepts either canonical `rool-machine:/...` URIs or bare machine paths such as `/rool-drive/...`, and returns the resource kind plus machine path. Fetch file resources through `space.fetchMachineResource(resource)`.
|
|
260
|
-
|
|
261
|
-
### AI Placeholder Pattern
|
|
109
|
+
async function start() {
|
|
110
|
+
const client = new RoolClient();
|
|
262
111
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
309
|
-
### Hidden Fields
|
|
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
141
|
|
|
313
|
-
|
|
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
|
|
329
|
-
|
|
330
|
-
```typescript
|
|
331
|
-
// All UI updates happen in one place, regardless of change source
|
|
332
|
-
channel.on('objectUpdated', ({ location, object, source }) => {
|
|
333
|
-
renderObject(location, object);
|
|
334
|
-
if (source === 'remote_agent') {
|
|
335
|
-
doLayout(); // AI might have added content
|
|
336
|
-
}
|
|
337
|
-
});
|
|
145
|
+
### Auth API
|
|
338
146
|
|
|
339
|
-
|
|
340
|
-
|
|
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).
|
|
172
|
+
Channel IDs must be 1–32 characters and contain only letters, numbers, `_`, and `-`.
|
|
376
173
|
|
|
377
|
-
##
|
|
378
|
-
|
|
379
|
-
### Browser (Default)
|
|
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
253
|
|
|
510
|
-
|
|
511
|
-
|
|
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
|
-
|
|
545
|
-
AI operations automatically receive context:
|
|
546
|
-
- **Interaction history** — Previous interactions and their results from this channel
|
|
547
|
-
- **Recently modified objects** — Objects created or changed recently
|
|
548
|
-
- **Attached resources** — Object resources passed via `attachments` are given primary focus; file resources are surfaced as `/rool-drive/...` paths
|
|
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
278
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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
|
-
}
|
|
632
|
-
});
|
|
633
|
-
```
|
|
634
|
-
|
|
635
|
-
See [Real-time Sync](#real-time-sync) for more on event sources.
|
|
636
|
-
|
|
637
|
-
## RoolClient API
|
|
638
|
-
|
|
639
|
-
### Logging
|
|
640
|
-
|
|
641
|
-
By default the SDK logs errors to the console. Pass a `logger` to see more or customize output:
|
|
642
|
-
|
|
643
|
-
```typescript
|
|
644
|
-
// Default — errors only
|
|
645
|
-
const client = new RoolClient();
|
|
646
|
-
|
|
647
|
-
// Log everything to console
|
|
648
|
-
const client = new RoolClient({ logger: console });
|
|
649
|
-
|
|
650
|
-
// Bring your own logger (pino, winston, etc.)
|
|
651
|
-
const client = new RoolClient({
|
|
652
|
-
logger: myLogger // any object with { debug, info, warn, error }
|
|
279
|
+
// Stop a long prompt
|
|
280
|
+
const ac = new AbortController();
|
|
281
|
+
const promptPromise = channel.prompt('Do a deep analysis', {
|
|
282
|
+
effort: 'RESEARCH',
|
|
283
|
+
signal: ac.signal,
|
|
653
284
|
});
|
|
285
|
+
ac.abort(); // asks the server to stop the in-flight interaction
|
|
286
|
+
await promptPromise;
|
|
654
287
|
```
|
|
655
288
|
|
|
656
|
-
|
|
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
|
|
289
|
+
## Conversations
|
|
670
290
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
| Method | Description |
|
|
674
|
-
|--------|-------------|
|
|
675
|
-
| `space.channels: ChannelInfo[]` | Live channel list (auto-updates via SSE) |
|
|
676
|
-
| `space.getChannels(): ChannelInfo[]` | List channels (deprecated — use `space.channels` instead) |
|
|
677
|
-
| `space.renameChannel(channelId, name): Promise<void>` | Rename a channel |
|
|
678
|
-
| `space.deleteChannel(channelId): Promise<void>` | Delete a channel and its interaction history |
|
|
679
|
-
| `channel.rename(name): Promise<void>` | Rename the current open channel |
|
|
680
|
-
|
|
681
|
-
### User Storage
|
|
682
|
-
|
|
683
|
-
Server-side key-value storage for user preferences, UI state, and other persistent data. Replaces browser localStorage with cross-device, server-synced storage.
|
|
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) |
|
|
291
|
+
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
292
|
|
|
698
293
|
```typescript
|
|
699
|
-
|
|
700
|
-
const authenticated = await client.initialize();
|
|
294
|
+
await channel.prompt('Hello'); // default conversation
|
|
701
295
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
// Write - updates immediately, syncs to server in background
|
|
707
|
-
client.setUserStorage('theme', 'dark');
|
|
708
|
-
client.setUserStorage('sidebar', { collapsed: true, width: 280 });
|
|
296
|
+
const thread = channel.conversation('thread-42');
|
|
297
|
+
await thread.prompt('Hello from another thread');
|
|
298
|
+
await thread.setSystemInstruction('Answer in haiku');
|
|
709
299
|
|
|
710
|
-
//
|
|
711
|
-
|
|
300
|
+
const branch = thread.getInteractions(); // active branch, root → leaf
|
|
301
|
+
const tree = thread.getTree(); // full interaction tree
|
|
712
302
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
if (key === 'theme') applyTheme(value as string);
|
|
717
|
-
});
|
|
303
|
+
if (thread.activeLeafId) {
|
|
304
|
+
thread.setActiveLeaf(thread.activeLeafId);
|
|
305
|
+
}
|
|
718
306
|
```
|
|
719
307
|
|
|
720
|
-
|
|
308
|
+
| Method/property | Description |
|
|
309
|
+
| --- | --- |
|
|
310
|
+
| `channel.conversation(id): ConversationHandle` | Get a conversation-scoped handle. |
|
|
311
|
+
| `getInteractions(): Interaction[]` | Active branch as a flat list. |
|
|
312
|
+
| `getTree(): Record<string, Interaction>` | Full interaction tree. |
|
|
313
|
+
| `activeLeafId` | Current branch tip. |
|
|
314
|
+
| `setActiveLeaf(id): void` | Switch branches. |
|
|
315
|
+
| `getSystemInstruction()` / `setSystemInstruction(value)` | Manage conversation system instruction. Pass `null` to clear. |
|
|
316
|
+
| `getConversations(): ConversationInfo[]` | List channel conversations (on `RoolChannel`). |
|
|
317
|
+
| `deleteConversation(id): Promise<void>` | Delete a non-active conversation. |
|
|
318
|
+
| `renameConversation(name): Promise<void>` | Rename the current/default conversation (on `RoolChannel`). |
|
|
319
|
+
| `conversation.rename(name): Promise<void>` | Rename a specific conversation handle. |
|
|
721
320
|
|
|
722
|
-
|
|
321
|
+
`ConversationHandle` also supports conversation-scoped `putObject`, `patchObject`, `moveObject`, `deleteObjects`, `prompt`, collection-schema methods, and `setMetadata`.
|
|
723
322
|
|
|
724
|
-
|
|
323
|
+
## Schema and Metadata
|
|
725
324
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
Manage extensions you own. Each `ExtensionInfo` includes `published` (whether it's listed in the marketplace) and `marketplaceExtensionId` (non-null if you installed it from someone else's listing, null if you authored it).
|
|
729
|
-
|
|
730
|
-
| Method | Description |
|
|
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) |
|
|
325
|
+
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.
|
|
736
326
|
|
|
737
|
-
#### Marketplace (`PublishedExtensionInfo`)
|
|
738
|
-
|
|
739
|
-
Discover and install extensions published by other users.
|
|
740
|
-
|
|
741
|
-
| Method | Description |
|
|
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) |
|
|
746
|
-
|
|
747
|
-
### Utilities
|
|
748
|
-
|
|
749
|
-
| Method | Description |
|
|
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
|
|
756
|
-
|
|
757
|
-
```typescript
|
|
758
|
-
client.on('authStateChanged', (authenticated: boolean) => void)
|
|
759
|
-
client.on('spaceAdded', (space: RoolSpaceInfo) => void) // Space created or access granted
|
|
760
|
-
client.on('spaceRemoved', (spaceId: string) => void) // Space deleted or access revoked
|
|
761
|
-
client.on('spaceRenamed', (spaceId: string, newName: string) => void)
|
|
762
|
-
client.on('channelCreated', (spaceId: string, channel: ChannelInfo) => void)
|
|
763
|
-
client.on('channelUpdated', (spaceId: string, channel: ChannelInfo) => void)
|
|
764
|
-
client.on('channelDeleted', (spaceId: string, channelId: string) => void)
|
|
765
|
-
client.on('userStorageChanged', ({ key, value, source }: UserStorageChangedEvent) => void)
|
|
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
327
|
```typescript
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
328
|
+
await channel.createCollection('article', {
|
|
329
|
+
schemaOrgType: 'Article',
|
|
330
|
+
fields: [
|
|
331
|
+
{ name: 'title', type: { kind: 'string' } },
|
|
332
|
+
{ name: 'status', type: { kind: 'enum', values: ['draft', 'published'] } },
|
|
333
|
+
{ name: 'tags', type: { kind: 'array', inner: { kind: 'string' } } },
|
|
334
|
+
{ name: 'author', type: { kind: 'ref' } },
|
|
335
|
+
],
|
|
781
336
|
});
|
|
782
|
-
```
|
|
783
|
-
|
|
784
|
-
## RoolSpace API
|
|
785
|
-
|
|
786
|
-
A space handle with a live SSE subscription. Extends `EventEmitter`. Manages user access, link sharing, channels, file storage, and export. The `channels` property auto-updates via SSE, and channel lifecycle events fire in real-time.
|
|
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
337
|
|
|
802
|
-
|
|
338
|
+
const schema = channel.getSchema();
|
|
803
339
|
|
|
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
|
|
340
|
+
await channel.alterCollection('article', [
|
|
341
|
+
{ name: 'title', type: { kind: 'string' } },
|
|
342
|
+
{ name: 'status', type: { kind: 'string' } },
|
|
343
|
+
]);
|
|
824
344
|
|
|
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)
|
|
345
|
+
channel.setMetadata('viewport', { x: 0, y: 0, zoom: 1 });
|
|
346
|
+
const viewport = channel.getMetadata('viewport');
|
|
831
347
|
```
|
|
832
348
|
|
|
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
349
|
| 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'
|
|
350
|
+
| --- | --- |
|
|
351
|
+
| `getSchema(): SpaceSchema` | Get collection definitions. |
|
|
352
|
+
| `createCollection(name, fieldsOrDef, options?): Promise<CollectionDef>` | Create a collection. |
|
|
353
|
+
| `alterCollection(name, fieldsOrDef, options?): Promise<CollectionDef>` | Replace a collection definition. |
|
|
354
|
+
| `dropCollection(name): Promise<void>` | Remove a collection and its object directory. |
|
|
355
|
+
| `setMetadata(key, value): void` | Set space metadata (fire-and-forget sync). |
|
|
356
|
+
| `getMetadata(key): unknown` | Read metadata from local cache. |
|
|
357
|
+
| `getAllMetadata(): Record<string, unknown>` | Read all metadata from local cache. |
|
|
886
358
|
|
|
887
|
-
|
|
888
|
-
await channel.createObject('article',
|
|
889
|
-
{ title: 'Welcome' },
|
|
890
|
-
{ basename: 'welcome' },
|
|
891
|
-
);
|
|
892
|
-
// → location: '/space/article/welcome.json'
|
|
359
|
+
Field kinds: `string`, `number`, `boolean`, `ref`, `enum`, `literal`, `array`, and `maybe`.
|
|
893
360
|
|
|
894
|
-
|
|
895
|
-
await channel.createObject('article', {
|
|
896
|
-
headline: '{{catchy headline}}',
|
|
897
|
-
body: '{{long-form intro}}',
|
|
898
|
-
});
|
|
899
|
-
```
|
|
361
|
+
## Undo/Redo
|
|
900
362
|
|
|
901
|
-
|
|
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
|
|
363
|
+
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.
|
|
908
364
|
|
|
909
365
|
```typescript
|
|
910
|
-
|
|
911
|
-
await channel.
|
|
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. |
|
|
366
|
+
await channel.checkpoint('Delete article');
|
|
367
|
+
await channel.deleteObjects(['/space/article/welcome.json']);
|
|
932
368
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
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
|
-
);
|
|
951
|
-
|
|
952
|
-
// Move and replace body in one go
|
|
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. |
|
|
963
|
-
|
|
964
|
-
#### findObjects
|
|
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:**
|
|
984
|
-
|
|
985
|
-
```typescript
|
|
986
|
-
// Filter by collection (no AI, no credits)
|
|
987
|
-
const { objects } = await channel.findObjects({
|
|
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
|
-
});
|
|
1006
|
-
|
|
1007
|
-
// Combined: collection + where narrow the data, prompt queries within it
|
|
1008
|
-
const { objects } = await channel.findObjects({
|
|
1009
|
-
collection: 'article',
|
|
1010
|
-
prompt: 'that discuss climate solutions positively',
|
|
1011
|
-
limit: 10
|
|
1012
|
-
});
|
|
369
|
+
if (await channel.canUndo()) {
|
|
370
|
+
await channel.undo();
|
|
371
|
+
}
|
|
1013
372
|
```
|
|
1014
373
|
|
|
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
374
|
| Method | Description |
|
|
1035
|
-
|
|
1036
|
-
| `
|
|
1037
|
-
| `
|
|
1038
|
-
| `
|
|
375
|
+
| --- | --- |
|
|
376
|
+
| `checkpoint(label?): Promise<string>` | Save current space state. |
|
|
377
|
+
| `canUndo(): Promise<boolean>` | Check whether undo is available. |
|
|
378
|
+
| `canRedo(): Promise<boolean>` | Check whether redo is available. |
|
|
379
|
+
| `undo(): Promise<boolean>` | Restore the latest checkpoint. |
|
|
380
|
+
| `redo(): Promise<boolean>` | Reapply undone work. |
|
|
381
|
+
| `clearHistory(): Promise<void>` | Clear checkpoint history. |
|
|
1039
382
|
|
|
1040
|
-
|
|
383
|
+
Undo/redo availability and history are scoped to the channel handle (`channel.channelId`).
|
|
1041
384
|
|
|
1042
|
-
|
|
385
|
+
## File Storage and WebDAV
|
|
1043
386
|
|
|
1044
|
-
|
|
387
|
+
Every space has authenticated WebDAV storage. WebDAV methods take SDK machine paths such as `/space/...`, `/rool-drive/...`, or `/` for the root collection.
|
|
1045
388
|
|
|
1046
389
|
```typescript
|
|
1047
|
-
|
|
390
|
+
const webdav = space.webdav;
|
|
1048
391
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
await webdav.mkcol('docs');
|
|
1052
|
-
await webdav.put('docs/readme.md', '# Hello', {
|
|
392
|
+
await webdav.mkcol('/rool-drive/docs');
|
|
393
|
+
await webdav.put('/rool-drive/docs/readme.md', '# Hello', {
|
|
1053
394
|
contentType: 'text/markdown',
|
|
1054
395
|
ifNoneMatch: '*',
|
|
1055
396
|
});
|
|
1056
397
|
|
|
1057
|
-
const listing = await webdav.propfind('docs
|
|
398
|
+
const listing = await webdav.propfind('/rool-drive/docs', {
|
|
1058
399
|
depth: '1',
|
|
1059
400
|
props: ['displayname', 'getcontentlength', 'getcontenttype', 'getetag'],
|
|
1060
401
|
});
|
|
1061
402
|
|
|
1062
|
-
const
|
|
1063
|
-
console.log(await
|
|
403
|
+
const response = await webdav.get('/rool-drive/docs/readme.md');
|
|
404
|
+
console.log(await response.text());
|
|
1064
405
|
|
|
1065
|
-
const
|
|
1066
|
-
|
|
1067
|
-
const sameFile = await space.fetchMachineResource(resource);
|
|
406
|
+
const file = await space.fetchPath('/rool-drive/docs/readme.md');
|
|
407
|
+
console.log(file.headers.get('Content-Type'));
|
|
1068
408
|
|
|
1069
409
|
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
|
-
});
|
|
410
|
+
console.log(usage.usedBytes, usage.availableBytes, usage.limitBytes);
|
|
1088
411
|
```
|
|
1089
412
|
|
|
1090
|
-
|
|
413
|
+
### Real-time file sync
|
|
1091
414
|
|
|
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`).
|
|
415
|
+
Object and file changes are announced at the space level. Use WebDAV `syncCollection()` to reconcile changes.
|
|
1116
416
|
|
|
1117
417
|
```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.
|
|
1126
|
-
|
|
1127
|
-
### Proxied Fetch
|
|
1128
|
-
|
|
1129
|
-
Fetch external URLs via the server, bypassing CORS restrictions. Requires editor role or above. Private/internal IP ranges are blocked (SSRF protection).
|
|
418
|
+
let token: string | null = null;
|
|
1130
419
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
420
|
+
async function syncFiles() {
|
|
421
|
+
const result = await space.webdav.syncCollection('/', {
|
|
422
|
+
token,
|
|
423
|
+
level: 'infinite',
|
|
424
|
+
props: ['displayname', 'getetag', 'getlastmodified', 'resourcetype'],
|
|
425
|
+
});
|
|
426
|
+
token = result.token;
|
|
427
|
+
updateFileTree(result.responses);
|
|
428
|
+
}
|
|
1134
429
|
|
|
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' },
|
|
430
|
+
space.on('filesChanged', syncFiles);
|
|
431
|
+
space.on('filesReset', () => {
|
|
432
|
+
token = null;
|
|
433
|
+
void syncFiles();
|
|
1145
434
|
});
|
|
1146
|
-
```
|
|
1147
|
-
|
|
1148
|
-
### Collection Schema
|
|
1149
435
|
|
|
1150
|
-
|
|
436
|
+
await syncFiles();
|
|
437
|
+
```
|
|
1151
438
|
|
|
1152
|
-
|
|
439
|
+
| Method | Description |
|
|
440
|
+
| --- | --- |
|
|
441
|
+
| `webdav.href(path)` / `webdav.url(path)` | Return WebDAV href/URL for an absolute SDK path. |
|
|
442
|
+
| `webdav.options(path)` | Send `OPTIONS`. |
|
|
443
|
+
| `webdav.propfind(path, options)` | Read properties/list collections. `depth` is required. |
|
|
444
|
+
| `webdav.syncCollection(path, options)` | WebDAV `REPORT sync-collection`; returns changed responses and next token. |
|
|
445
|
+
| `webdav.get(path, options?)` / `webdav.head(path)` | Read a file; `get` supports byte ranges. |
|
|
446
|
+
| `webdav.put(path, body, options?)` | Write a file/object at an exact path. Parent collection must exist. |
|
|
447
|
+
| `webdav.mkcol(path)` | Create one collection. |
|
|
448
|
+
| `webdav.copy(source, destination, options?)` | Copy a file or collection. |
|
|
449
|
+
| `webdav.move(source, destination, options?)` | Move a file or collection. |
|
|
450
|
+
| `webdav.delete(path, options?)` | Delete a file or collection. |
|
|
451
|
+
| `webdav.lock(path, options)` / `refreshLock(path, token)` / `unlock(token)` | WebDAV write locks. |
|
|
452
|
+
| `webdav.request(method, path, init?)` | Raw authenticated WebDAV request. |
|
|
453
|
+
| `space.fetchPath(path, options?)` | Fetch a `/rool-drive/...` file path or `rool-machine:` file URI. |
|
|
454
|
+
| `space.getStorageUsage()` / `webdav.getStorageUsage()` | Storage quota usage. |
|
|
455
|
+
|
|
456
|
+
High-level WebDAV methods that validate response status throw `WebDAVError` with `status`, `statusText`, and `body`; raw `request()` and `options()` return `Response`.
|
|
1153
457
|
|
|
458
|
+
## Collaboration
|
|
1154
459
|
|
|
1155
460
|
```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
|
-
]);
|
|
461
|
+
const user = await client.searchUser('colleague@example.com');
|
|
462
|
+
if (user) {
|
|
463
|
+
await space.addUser(user.id, 'editor');
|
|
464
|
+
}
|
|
1176
465
|
|
|
1177
|
-
//
|
|
1178
|
-
await channel.dropCollection('article');
|
|
466
|
+
await space.setLinkAccess('viewer'); // 'none' | 'viewer' | 'editor'
|
|
1179
467
|
```
|
|
1180
468
|
|
|
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
|
|
469
|
+
Roles:
|
|
1189
470
|
|
|
1190
|
-
|
|
|
1191
|
-
|
|
1192
|
-
| `
|
|
1193
|
-
| `
|
|
1194
|
-
| `
|
|
1195
|
-
| `
|
|
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' } }` |
|
|
1200
|
-
|
|
1201
|
-
### Import/Export
|
|
471
|
+
| Role | Capabilities |
|
|
472
|
+
| --- | --- |
|
|
473
|
+
| `owner` | Full control. |
|
|
474
|
+
| `admin` | Editor capabilities plus user/link management. |
|
|
475
|
+
| `editor` | Create, modify, move, and delete objects/files. |
|
|
476
|
+
| `viewer` | Read-only access. |
|
|
1202
477
|
|
|
1203
|
-
|
|
478
|
+
## RoolClient API
|
|
1204
479
|
|
|
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 |
|
|
480
|
+
### Constructor config
|
|
1209
481
|
|
|
1210
|
-
**Export:**
|
|
1211
482
|
```typescript
|
|
1212
|
-
const
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
483
|
+
const client = new RoolClient({
|
|
484
|
+
apiUrl: 'https://api.rool.dev',
|
|
485
|
+
authUrl: 'https://rool.dev/auth',
|
|
486
|
+
graphqlUrl: 'https://api.rool.dev/graphql',
|
|
487
|
+
logger: console,
|
|
488
|
+
});
|
|
1216
489
|
```
|
|
1217
490
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
491
|
+
`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.
|
|
492
|
+
|
|
493
|
+
| Method/property | Description |
|
|
494
|
+
| --- | --- |
|
|
495
|
+
| `currentUser: CurrentUser | null` | Cached user profile from initialization/fetch. |
|
|
496
|
+
| `getCurrentUser(): Promise<CurrentUser>` | Fetch current user. |
|
|
497
|
+
| `updateCurrentUser(input): Promise<CurrentUser>` | Update `name`, `slug`, or `marketingOptIn`. |
|
|
498
|
+
| `deleteCurrentUser(): Promise<void>` | Mark account for deletion and log out. |
|
|
499
|
+
| `searchUser(email): Promise<UserResult | null>` | Exact email lookup. |
|
|
500
|
+
| `listSpaces(): Promise<RoolSpaceInfo[]>` | List accessible spaces. |
|
|
501
|
+
| `openSpace(id): Promise<RoolSpace>` | Open/cached live space handle. |
|
|
502
|
+
| `createSpace(name): Promise<RoolSpace>` | Create and open a space. |
|
|
503
|
+
| `duplicateSpace(sourceId, name): Promise<RoolSpace>` | Duplicate a space. |
|
|
504
|
+
| `deleteSpace(id): Promise<void>` | Permanently delete a space. |
|
|
505
|
+
| `importArchive(name, archive): Promise<RoolSpace>` | Import a zip archive as a new space. |
|
|
506
|
+
| `getUserStorage<T>(key): T | undefined` | Sync read from user-storage cache. |
|
|
507
|
+
| `setUserStorage(key, value): void` | Update user storage; `null`/`undefined` deletes. |
|
|
508
|
+
| `getAllUserStorage(): Record<string, unknown>` | Copy all cached user storage. |
|
|
509
|
+
| `reportEvent(event, url?): void` | Fire-and-forget telemetry event. |
|
|
510
|
+
| `destroy(): void` | Close subscriptions, spaces, auth resources, and listeners. |
|
|
511
|
+
| `generateId(): string` | Generate a 6-character alphanumeric ID. |
|
|
512
|
+
|
|
513
|
+
### Client events
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
client.on('authStateChanged', (authenticated) => void 0);
|
|
517
|
+
client.on('spaceAdded', (space) => void 0);
|
|
518
|
+
client.on('spaceRemoved', (spaceId) => void 0);
|
|
519
|
+
client.on('spaceRenamed', (spaceId, newName) => void 0);
|
|
520
|
+
client.on('channelCreated', (spaceId, channel) => void 0);
|
|
521
|
+
client.on('channelUpdated', (spaceId, channel) => void 0);
|
|
522
|
+
client.on('channelDeleted', (spaceId, channelId) => void 0);
|
|
523
|
+
client.on('userStorageChanged', ({ key, value, source }) => void 0);
|
|
524
|
+
client.on('connectionStateChanged', (state) => void 0);
|
|
525
|
+
client.on('error', (error, context) => void 0);
|
|
1222
526
|
```
|
|
1223
527
|
|
|
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
|
|
528
|
+
## RoolSpace API
|
|
1236
529
|
|
|
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)
|
|
530
|
+
Properties: `id`, `name`, `role`, `linkAccess`, `memberCount`, `channels`, `route`, `webdav`.
|
|
1242
531
|
|
|
1243
|
-
|
|
1244
|
-
|
|
532
|
+
| Method | Description |
|
|
533
|
+
| --- | --- |
|
|
534
|
+
| `openChannel(channelId): Promise<RoolChannel>` | Open/create a channel. |
|
|
535
|
+
| `close(): void` | Stop subscription and close open channels. |
|
|
536
|
+
| `rename(newName): Promise<void>` | Rename the space. |
|
|
537
|
+
| `delete(): Promise<void>` | Permanently delete the space. |
|
|
538
|
+
| `listUsers(): Promise<SpaceMember[]>` | List collaborators. |
|
|
539
|
+
| `addUser(userId, role): Promise<void>` | Add collaborator. |
|
|
540
|
+
| `removeUser(userId): Promise<void>` | Remove collaborator. |
|
|
541
|
+
| `setLinkAccess(linkAccess): Promise<void>` | Set URL sharing level. |
|
|
542
|
+
| `renameChannel(channelId, name): Promise<void>` | Rename a channel. |
|
|
543
|
+
| `deleteChannel(channelId): Promise<void>` | Delete a channel and history. |
|
|
544
|
+
| `exportArchive(): Promise<Blob>` | Export a space archive. |
|
|
545
|
+
| `refresh(): Promise<void>` | Refresh cached space data. |
|
|
546
|
+
| `fetchPath(path, options?): Promise<Response>` | Fetch a `/rool-drive/...` file. |
|
|
547
|
+
| `getStorageUsage(): Promise<SpaceFileStorageUsage>` | File-storage quota usage. |
|
|
548
|
+
|
|
549
|
+
Events:
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
space.on('channelCreated', (channel) => void 0);
|
|
553
|
+
space.on('channelUpdated', (channel) => void 0);
|
|
554
|
+
space.on('channelDeleted', (channelId) => void 0);
|
|
555
|
+
space.on('filesChanged', ({ spaceId, source, timestamp }) => void 0);
|
|
556
|
+
space.on('filesReset', ({ spaceId, source, timestamp }) => void 0);
|
|
557
|
+
space.on('connectionStateChanged', (state) => void 0);
|
|
558
|
+
```
|
|
1245
559
|
|
|
1246
|
-
|
|
1247
|
-
channel.on('schemaUpdated', ({ schema, source }) => void)
|
|
560
|
+
## RoolChannel API
|
|
1248
561
|
|
|
1249
|
-
|
|
1250
|
-
channel.on('channelUpdated', ({ channelId, source }) => void)
|
|
562
|
+
Properties: `id` (space ID), `name` (space name), `role`, `linkAccess`, `userId`, `channelId`, `channelName`, `conversationId`, `isReadOnly`, `activeLeafId`.
|
|
1251
563
|
|
|
1252
|
-
|
|
1253
|
-
|
|
564
|
+
| Area | Methods |
|
|
565
|
+
| --- | --- |
|
|
566
|
+
| Lifecycle | `close()`, `rename(name)`, `conversation(id)` |
|
|
567
|
+
| Objects | `getObject`, `getObjects`, `stat`, `putObject`, `patchObject`, `moveObject`, `deleteObjects` |
|
|
568
|
+
| Schema | `getSchema`, `createCollection`, `alterCollection`, `dropCollection` |
|
|
569
|
+
| Metadata | `setMetadata`, `getMetadata`, `getAllMetadata` |
|
|
570
|
+
| Conversations | `getInteractions`, `getTree`, `setActiveLeaf`, `getConversations`, `deleteConversation`, `getSystemInstruction`, `setSystemInstruction`, `renameConversation` |
|
|
571
|
+
| AI | `prompt` |
|
|
572
|
+
| Undo/redo | `checkpoint`, `canUndo`, `canRedo`, `undo`, `redo`, `clearHistory` |
|
|
573
|
+
| Utilities | `fetch(url, init?)` server-side proxied fetch |
|
|
1254
574
|
|
|
1255
|
-
|
|
1256
|
-
channel.on('reset', ({ source }) => void)
|
|
575
|
+
Channel events:
|
|
1257
576
|
|
|
1258
|
-
|
|
1259
|
-
channel.on('
|
|
577
|
+
```typescript
|
|
578
|
+
channel.on('metadataUpdated', ({ metadata, source }) => void 0);
|
|
579
|
+
channel.on('schemaUpdated', ({ schema, source }) => void 0);
|
|
580
|
+
channel.on('channelUpdated', ({ channelId, source }) => void 0);
|
|
581
|
+
channel.on('conversationUpdated', ({ conversationId, channelId, source }) => void 0);
|
|
582
|
+
channel.on('reset', ({ source }) => void 0);
|
|
583
|
+
channel.on('syncError', (error) => void 0);
|
|
1260
584
|
```
|
|
1261
585
|
|
|
1262
|
-
|
|
586
|
+
`channel.fetch(url, init?)` proxies external HTTP requests through the server to bypass browser CORS.
|
|
1263
587
|
|
|
1264
|
-
|
|
588
|
+
## Import/Export
|
|
1265
589
|
|
|
1266
590
|
```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
|
-
}
|
|
591
|
+
const archive = await space.exportArchive();
|
|
592
|
+
const imported = await client.importArchive('Imported Data', archive);
|
|
1276
593
|
```
|
|
1277
594
|
|
|
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.
|
|
595
|
+
Archives include objects, metadata, channels/conversations, and file storage.
|
|
1307
596
|
|
|
1308
597
|
## Data Types
|
|
1309
598
|
|
|
1310
|
-
### Schema Types
|
|
1311
|
-
|
|
1312
599
|
```typescript
|
|
1313
|
-
// Allowed field types
|
|
1314
600
|
type FieldType =
|
|
1315
601
|
| { kind: 'string' }
|
|
1316
602
|
| { kind: 'number' }
|
|
@@ -1328,150 +614,66 @@ interface FieldDef {
|
|
|
1328
614
|
|
|
1329
615
|
interface CollectionDef {
|
|
1330
616
|
fields: FieldDef[];
|
|
617
|
+
schemaOrgType?: string;
|
|
1331
618
|
}
|
|
1332
619
|
|
|
1333
|
-
// Full schema — collection names to definitions
|
|
1334
620
|
type SpaceSchema = Record<string, CollectionDef>;
|
|
1335
|
-
```
|
|
1336
|
-
|
|
1337
|
-
### Object Data
|
|
1338
621
|
|
|
1339
|
-
```typescript
|
|
1340
|
-
// An object addressed by location. References between objects are body
|
|
1341
|
-
// fields whose values are location strings.
|
|
1342
622
|
interface RoolObject {
|
|
1343
|
-
|
|
1344
|
-
collection: string;
|
|
1345
|
-
basename: string;
|
|
623
|
+
path: string;
|
|
1346
624
|
body: Record<string, unknown>;
|
|
1347
625
|
}
|
|
1348
626
|
|
|
1349
|
-
|
|
627
|
+
interface GetObjectsResult {
|
|
628
|
+
objects: RoolObject[];
|
|
629
|
+
missing: string[];
|
|
630
|
+
}
|
|
631
|
+
|
|
1350
632
|
interface RoolObjectStat {
|
|
1351
|
-
|
|
633
|
+
path: string;
|
|
1352
634
|
modifiedAt: number;
|
|
1353
635
|
modifiedBy: string;
|
|
1354
636
|
modifiedByName: string | null;
|
|
1355
|
-
modifiedInChannel: string;
|
|
1356
|
-
modifiedInConversation: string | null;
|
|
1357
|
-
modifiedInInteraction: string | null;
|
|
637
|
+
modifiedInChannel: string;
|
|
638
|
+
modifiedInConversation: string | null;
|
|
639
|
+
modifiedInInteraction: string | null;
|
|
1358
640
|
}
|
|
1359
|
-
```
|
|
1360
|
-
|
|
1361
|
-
### Channels and Conversations
|
|
1362
641
|
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
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)
|
|
1371
|
-
}
|
|
642
|
+
type PromptAttachment =
|
|
643
|
+
| File
|
|
644
|
+
| Blob
|
|
645
|
+
| { data: string; contentType: string; filename?: string }
|
|
646
|
+
| string;
|
|
1372
647
|
|
|
1373
|
-
|
|
1374
|
-
interface ConversationInfo {
|
|
1375
|
-
id: string;
|
|
1376
|
-
name: string | null;
|
|
1377
|
-
systemInstruction: string | null;
|
|
1378
|
-
createdAt: number;
|
|
1379
|
-
createdBy: string;
|
|
1380
|
-
interactionCount: number;
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
// Channel container with metadata and conversations
|
|
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
|
-
}
|
|
648
|
+
type PromptEffort = 'QUICK' | 'STANDARD' | 'REASONING' | 'RESEARCH';
|
|
1394
649
|
|
|
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
|
|
650
|
+
interface PromptOptions {
|
|
651
|
+
responseSchema?: Record<string, unknown>;
|
|
652
|
+
effort?: PromptEffort;
|
|
653
|
+
parentInteractionId?: string | null;
|
|
654
|
+
ephemeral?: boolean;
|
|
655
|
+
readOnly?: boolean;
|
|
656
|
+
attachments?: PromptAttachment[];
|
|
657
|
+
signal?: AbortSignal;
|
|
658
|
+
eventName?: string;
|
|
1406
659
|
}
|
|
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
660
|
|
|
1429
661
|
type InteractionStatus = 'pending' | 'streaming' | 'done' | 'error';
|
|
1430
662
|
|
|
1431
663
|
interface Interaction {
|
|
1432
|
-
id: string;
|
|
1433
|
-
parentId: string | null;
|
|
664
|
+
id: string;
|
|
665
|
+
parentId: string | null;
|
|
1434
666
|
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
|
|
667
|
+
userId: string;
|
|
668
|
+
userName?: string | null;
|
|
669
|
+
operation: 'prompt' | 'putObject' | 'patchObject' | 'moveObject' | 'deleteObjects' | 'deletePaths' | string;
|
|
670
|
+
input: string;
|
|
671
|
+
output: string | null;
|
|
672
|
+
status: InteractionStatus;
|
|
673
|
+
ai: boolean;
|
|
674
|
+
modifiedObjectPaths: string[];
|
|
675
|
+
toolCalls: ToolCall[];
|
|
676
|
+
attachments?: string[];
|
|
1475
677
|
}
|
|
1476
678
|
```
|
|
1477
679
|
|