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