@rool-dev/sdk 0.10.2-dev.7a2da99 → 0.10.2-dev.a0fe230
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 +437 -1108
- package/dist/channel.d.ts +5 -1
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +19 -24
- 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 +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/path.d.ts +1 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +13 -0
- package/dist/path.js.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 +2 -18
- 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,582 @@ 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');
|
|
74
|
+
Most SDK methods take plain path strings:
|
|
137
75
|
|
|
138
|
-
|
|
139
|
-
|
|
76
|
+
- Object paths: `/space/<collection>/<name>.json` (exactly three segments; no dotfile collection or object names)
|
|
77
|
+
- File paths: `/rool-drive/<path/to/file>`
|
|
140
78
|
|
|
141
|
-
|
|
142
|
-
await channel.deleteConversation('old-thread');
|
|
143
|
-
|
|
144
|
-
// Rename a conversation
|
|
145
|
-
await thread.rename('Research Thread');
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### Branching Conversations
|
|
149
|
-
|
|
150
|
-
The conversation history is a **tree**, not a flat list. Each interaction has a `parentId` pointing to the interaction it continues from. When you call `prompt()`, the SDK automatically continues from the current active leaf. To branch (edit/reroll), pass a different `parentInteractionId`:
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
// Normal conversation — each prompt auto-continues from the last
|
|
156
|
-
await thread.prompt('My favorite color is blue. Say OK.');
|
|
157
|
-
await thread.prompt('What is my favorite color?'); // Sees "blue"
|
|
82
|
+
import { machinePath, machineUri, isObjectPath } from '@rool-dev/sdk';
|
|
158
83
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const tree = thread.getTree();
|
|
162
|
-
const firstInteractionId = tree[firstLeaf!].parentId!; // The root
|
|
84
|
+
machinePath('rool-machine:/rool-drive/docs/read%20me.md');
|
|
85
|
+
// '/rool-drive/docs/read me.md'
|
|
163
86
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
});
|
|
167
|
-
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'
|
|
168
89
|
|
|
169
|
-
//
|
|
170
|
-
thread.setActiveLeaf(firstLeaf!);
|
|
171
|
-
thread.getInteractions(); // Returns the blue branch (root → leaf)
|
|
90
|
+
isObjectPath('/space/article/welcome.json'); // true
|
|
172
91
|
```
|
|
173
92
|
|
|
174
|
-
|
|
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
|
-
}
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
The **body** holds the user-defined data.
|
|
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.
|
|
280
|
-
|
|
281
|
-
### Checkpoints & Undo/Redo
|
|
109
|
+
async function start() {
|
|
110
|
+
const client = new RoolClient();
|
|
282
111
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
230
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
### Method Signature
|
|
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
|
-
|
|
620
|
-
See [Real-time Sync](#real-time-sync) for a WebDAV sync-token example.
|
|
621
|
-
|
|
622
|
-
## RoolClient API
|
|
623
278
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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
|
|
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
|
-
|
|
642
|
-
|
|
643
|
-
| Method | Description |
|
|
644
|
-
|--------|-------------|
|
|
645
|
-
| `listSpaces(): Promise<RoolSpaceInfo[]>` | List available spaces |
|
|
646
|
-
| `openSpace(spaceId): Promise<RoolSpace>` | Open a space with live SSE subscription. Caches and reuses open spaces. Call `space.openChannel(channelId)` to get a channel. |
|
|
647
|
-
| `createSpace(name): Promise<RoolSpace>` | Create a new space, returns live handle with SSE subscription |
|
|
648
|
-
| `duplicateSpace(sourceSpaceId, name): Promise<RoolSpace>` | Duplicate an existing space. Returns a handle to the new space. |
|
|
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:
|
|
289
|
+
## Conversations
|
|
655
290
|
|
|
656
|
-
|
|
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
|
+
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.
|
|
679
292
|
|
|
680
293
|
```typescript
|
|
681
|
-
|
|
682
|
-
const authenticated = await client.initialize();
|
|
683
|
-
|
|
684
|
-
// Sync reads are now trustworthy
|
|
685
|
-
const theme = client.getUserStorage<string>('theme');
|
|
686
|
-
applyTheme(theme ?? 'light');
|
|
294
|
+
await channel.prompt('Hello'); // default conversation
|
|
687
295
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
296
|
+
const thread = channel.conversation('thread-42');
|
|
297
|
+
await thread.prompt('Hello from another thread');
|
|
298
|
+
await thread.setSystemInstruction('Answer in haiku');
|
|
691
299
|
|
|
692
|
-
//
|
|
693
|
-
|
|
300
|
+
const branch = thread.getInteractions(); // active branch, root → leaf
|
|
301
|
+
const tree = thread.getTree(); // full interaction tree
|
|
694
302
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
if (key === 'theme') applyTheme(value as string);
|
|
699
|
-
});
|
|
303
|
+
if (thread.activeLeafId) {
|
|
304
|
+
thread.setActiveLeaf(thread.activeLeafId);
|
|
305
|
+
}
|
|
700
306
|
```
|
|
701
307
|
|
|
702
|
-
|
|
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. |
|
|
703
320
|
|
|
704
|
-
|
|
705
|
-
|--------|-------------|
|
|
706
|
-
| `RoolClient.generateId(): string` | Generate a unique 6-character alphanumeric ID. |
|
|
707
|
-
| `destroy(): void` | Clean up resources |
|
|
321
|
+
`ConversationHandle` also supports conversation-scoped `putObject`, `patchObject`, `moveObject`, `deleteObjects`, `prompt`, collection-schema methods, and `setMetadata`.
|
|
708
322
|
|
|
709
|
-
|
|
323
|
+
## Schema and Metadata
|
|
710
324
|
|
|
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
|
-
```
|
|
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.
|
|
723
326
|
|
|
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
327
|
```typescript
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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
|
+
],
|
|
735
336
|
});
|
|
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
337
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
| Property | Description |
|
|
747
|
-
|----------|-------------|
|
|
748
|
-
| `id: string` | Space ID |
|
|
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
|
|
338
|
+
const schema = channel.getSchema();
|
|
757
339
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
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
|
|
340
|
+
await channel.alterCollection('article', [
|
|
341
|
+
{ name: 'title', type: { kind: 'string' } },
|
|
342
|
+
{ name: 'status', type: { kind: 'string' } },
|
|
343
|
+
]);
|
|
776
344
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
space.on('channelUpdated', (channel: ChannelInfo) => void) // Channel metadata changed
|
|
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)
|
|
345
|
+
channel.setMetadata('viewport', { x: 0, y: 0, zoom: 1 });
|
|
346
|
+
const viewport = channel.getMetadata('viewport');
|
|
783
347
|
```
|
|
784
348
|
|
|
785
|
-
## RoolChannel API
|
|
786
|
-
|
|
787
|
-
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.
|
|
788
|
-
|
|
789
|
-
### Properties
|
|
790
|
-
|
|
791
|
-
| Property | Description |
|
|
792
|
-
|----------|-------------|
|
|
793
|
-
| `id: string` | Space ID |
|
|
794
|
-
| `name: string` | Space name |
|
|
795
|
-
| `role: RoolUserRole` | User's role (`'owner' \| 'admin' \| 'editor' \| 'viewer'`) |
|
|
796
|
-
| `linkAccess: LinkAccess` | URL sharing level (`'none' \| 'viewer' \| 'editor'`) |
|
|
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
349
|
| Method | Description |
|
|
804
|
-
|
|
805
|
-
| `
|
|
806
|
-
| `
|
|
807
|
-
| `
|
|
808
|
-
|
|
809
|
-
|
|
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. |
|
|
810
358
|
|
|
811
|
-
|
|
359
|
+
Field kinds: `string`, `number`, `boolean`, `ref`, `enum`, `literal`, `array`, and `maybe`.
|
|
812
360
|
|
|
813
|
-
|
|
361
|
+
## Undo/Redo
|
|
814
362
|
|
|
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. |
|
|
823
|
-
|
|
824
|
-
#### createObject
|
|
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.
|
|
825
364
|
|
|
826
365
|
```typescript
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
title: 'Hello',
|
|
830
|
-
body: 'World',
|
|
831
|
-
});
|
|
832
|
-
// → object.location: '/space/article/X7kQ9p.json'
|
|
366
|
+
await channel.checkpoint('Delete article');
|
|
367
|
+
await channel.deleteObjects(['/space/article/welcome.json']);
|
|
833
368
|
|
|
834
|
-
|
|
835
|
-
await channel.
|
|
836
|
-
|
|
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
|
-
});
|
|
861
|
-
|
|
862
|
-
// Delete a field (pass null)
|
|
863
|
-
await channel.updateObject('/space/article/welcome.json', {
|
|
864
|
-
data: { draft: null },
|
|
865
|
-
});
|
|
866
|
-
|
|
867
|
-
// AI-driven rewrite
|
|
868
|
-
await channel.updateObject('/space/article/welcome.json', {
|
|
869
|
-
prompt: 'Tighten the intro by 30%.',
|
|
870
|
-
});
|
|
871
|
-
```
|
|
872
|
-
|
|
873
|
-
| Option | Description |
|
|
874
|
-
|--------|-------------|
|
|
875
|
-
| `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. |
|
|
876
|
-
| `prompt` | Natural language instruction for AI to modify content. |
|
|
877
|
-
| `ephemeral` | If true, the operation won't be recorded in interaction history. |
|
|
878
|
-
| `parentInteractionId` | Conversation tree parent. Omit to auto-continue; pass `null` for a new root. |
|
|
879
|
-
|
|
880
|
-
Use `moveObject` to change an object's location (collection or basename).
|
|
881
|
-
|
|
882
|
-
#### Moving and Renaming
|
|
883
|
-
|
|
884
|
-
`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.
|
|
885
|
-
|
|
886
|
-
```typescript
|
|
887
|
-
// Rename within the same collection
|
|
888
|
-
await channel.moveObject(
|
|
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
|
-
);
|
|
898
|
-
|
|
899
|
-
// Move and replace body in one go
|
|
900
|
-
await channel.moveObject(from, to, {
|
|
901
|
-
body: { title: 'Hello, world', status: 'published' },
|
|
902
|
-
});
|
|
369
|
+
if (await channel.canUndo()) {
|
|
370
|
+
await channel.undo();
|
|
371
|
+
}
|
|
903
372
|
```
|
|
904
373
|
|
|
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
374
|
| Method | Description |
|
|
914
|
-
|
|
915
|
-
| `checkpoint(label?): Promise<string>` |
|
|
916
|
-
| `canUndo(): Promise<boolean>` | Check
|
|
917
|
-
| `canRedo(): Promise<boolean>` | Check
|
|
918
|
-
| `undo(): Promise<boolean>` |
|
|
919
|
-
| `redo(): Promise<boolean>` |
|
|
920
|
-
| `clearHistory(): Promise<void>` | Clear
|
|
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. |
|
|
921
382
|
|
|
922
|
-
|
|
383
|
+
Undo/redo availability and history are scoped to the channel handle (`channel.channelId`).
|
|
923
384
|
|
|
924
|
-
|
|
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
|
-
| Method | Description |
|
|
929
|
-
|--------|-------------|
|
|
930
|
-
| `setMetadata(key, value): void` | Set space-level metadata |
|
|
931
|
-
| `getMetadata(key): unknown` | Get metadata value, or undefined if key not set |
|
|
932
|
-
| `getAllMetadata(): Record<string, unknown>` | Get all metadata |
|
|
385
|
+
## File Storage and WebDAV
|
|
933
386
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
Every space has authenticated file storage. WebDAV is the SDK surface for that storage: paths are relative to the space root and collection operations use WebDAV collection semantics. Human/AI file links use `rool-machine:/rool-drive/...`; resolve those links with `resolveMachineResource()` and fetch file resources with `space.fetchMachineResource(resource)`.
|
|
937
|
-
|
|
938
|
-
Open a space, then use `space.webdav`.
|
|
387
|
+
Every space has authenticated WebDAV storage. WebDAV methods take SDK machine paths such as `/space/...`, `/rool-drive/...`, or `/` for the root collection.
|
|
939
388
|
|
|
940
389
|
```typescript
|
|
941
|
-
import { resolveMachineResource } from '@rool-dev/sdk';
|
|
942
|
-
|
|
943
|
-
const space = await client.openSpace('space-id');
|
|
944
390
|
const webdav = space.webdav;
|
|
945
391
|
|
|
946
|
-
await webdav.mkcol('docs');
|
|
947
|
-
await webdav.put('docs/readme.md', '# Hello', {
|
|
392
|
+
await webdav.mkcol('/rool-drive/docs');
|
|
393
|
+
await webdav.put('/rool-drive/docs/readme.md', '# Hello', {
|
|
948
394
|
contentType: 'text/markdown',
|
|
949
395
|
ifNoneMatch: '*',
|
|
950
396
|
});
|
|
951
397
|
|
|
952
|
-
const listing = await webdav.propfind('docs
|
|
398
|
+
const listing = await webdav.propfind('/rool-drive/docs', {
|
|
953
399
|
depth: '1',
|
|
954
400
|
props: ['displayname', 'getcontentlength', 'getcontenttype', 'getetag'],
|
|
955
401
|
});
|
|
956
402
|
|
|
957
|
-
const
|
|
958
|
-
console.log(await
|
|
403
|
+
const response = await webdav.get('/rool-drive/docs/readme.md');
|
|
404
|
+
console.log(await response.text());
|
|
959
405
|
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
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'));
|
|
963
408
|
|
|
964
409
|
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
|
-
});
|
|
410
|
+
console.log(usage.usedBytes, usage.availableBytes, usage.limitBytes);
|
|
983
411
|
```
|
|
984
412
|
|
|
985
|
-
|
|
413
|
+
### Real-time file sync
|
|
986
414
|
|
|
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`).
|
|
415
|
+
Object and file changes are announced at the space level. Use WebDAV `syncCollection()` to reconcile changes.
|
|
1009
416
|
|
|
1010
417
|
```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).
|
|
418
|
+
let token: string | null = null;
|
|
1023
419
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
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
|
+
}
|
|
1027
429
|
|
|
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' },
|
|
430
|
+
space.on('filesChanged', syncFiles);
|
|
431
|
+
space.on('filesReset', () => {
|
|
432
|
+
token = null;
|
|
433
|
+
void syncFiles();
|
|
1038
434
|
});
|
|
1039
|
-
```
|
|
1040
435
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
Collections are the types you use to group objects in a space. Every object belongs to exactly one collection: the collection is the parent directory of its location, and the server validates the object's body against that collection's definition. Renaming a collection changes the location of every object bound to it; dropping a collection is blocked while any object still lives there.
|
|
436
|
+
await syncFiles();
|
|
437
|
+
```
|
|
1044
438
|
|
|
1045
|
-
|
|
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`.
|
|
1046
457
|
|
|
458
|
+
## Collaboration
|
|
1047
459
|
|
|
1048
460
|
```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
|
-
]);
|
|
461
|
+
const user = await client.searchUser('colleague@example.com');
|
|
462
|
+
if (user) {
|
|
463
|
+
await space.addUser(user.id, 'editor');
|
|
464
|
+
}
|
|
1069
465
|
|
|
1070
|
-
//
|
|
1071
|
-
await channel.dropCollection('article');
|
|
466
|
+
await space.setLinkAccess('viewer'); // 'none' | 'viewer' | 'editor'
|
|
1072
467
|
```
|
|
1073
468
|
|
|
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 |
|
|
1080
|
-
|
|
1081
|
-
#### Field Types
|
|
1082
|
-
|
|
1083
|
-
| Kind | Description | Example |
|
|
1084
|
-
|------|-------------|---------|
|
|
1085
|
-
| `string` | Text value | `{ kind: 'string' }` |
|
|
1086
|
-
| `number` | Numeric value | `{ kind: 'number' }` |
|
|
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' } }` |
|
|
469
|
+
Roles:
|
|
1093
470
|
|
|
1094
|
-
|
|
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. |
|
|
1095
477
|
|
|
1096
|
-
|
|
478
|
+
## RoolClient API
|
|
1097
479
|
|
|
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 |
|
|
480
|
+
### Constructor config
|
|
1102
481
|
|
|
1103
|
-
**Export:**
|
|
1104
482
|
```typescript
|
|
1105
|
-
const
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
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
|
+
});
|
|
1109
489
|
```
|
|
1110
490
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
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);
|
|
1115
526
|
```
|
|
1116
527
|
|
|
1117
|
-
|
|
528
|
+
## RoolSpace API
|
|
1118
529
|
|
|
1119
|
-
|
|
530
|
+
Properties: `id`, `name`, `role`, `linkAccess`, `memberCount`, `channels`, `route`, `webdav`.
|
|
1120
531
|
|
|
1121
|
-
|
|
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
|
+
```
|
|
1122
559
|
|
|
1123
|
-
|
|
1124
|
-
// Channel metadata updated
|
|
1125
|
-
channel.on('channelUpdated', ({ channelId, source }) => void)
|
|
560
|
+
## RoolChannel API
|
|
1126
561
|
|
|
1127
|
-
|
|
1128
|
-
channel.on('conversationUpdated', ({ conversationId, channelId, source }) => void)
|
|
562
|
+
Properties: `id` (space ID), `name` (space name), `role`, `linkAccess`, `userId`, `channelId`, `channelName`, `conversationId`, `isReadOnly`, `activeLeafId`.
|
|
1129
563
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
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 |
|
|
1133
574
|
|
|
1134
|
-
|
|
1135
|
-
channel.on('reset', ({ source }) => void)
|
|
575
|
+
Channel events:
|
|
1136
576
|
|
|
1137
|
-
|
|
1138
|
-
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);
|
|
1139
584
|
```
|
|
1140
585
|
|
|
1141
|
-
|
|
586
|
+
`channel.fetch(url, init?)` proxies external HTTP requests through the server to bypass browser CORS.
|
|
1142
587
|
|
|
1143
|
-
|
|
588
|
+
## Import/Export
|
|
1144
589
|
|
|
1145
590
|
```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
|
-
}
|
|
591
|
+
const archive = await space.exportArchive();
|
|
592
|
+
const imported = await client.importArchive('Imported Data', archive);
|
|
1155
593
|
```
|
|
1156
594
|
|
|
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.
|
|
595
|
+
Archives include objects, metadata, channels/conversations, and file storage.
|
|
1186
596
|
|
|
1187
597
|
## Data Types
|
|
1188
598
|
|
|
1189
|
-
### Schema Types
|
|
1190
|
-
|
|
1191
599
|
```typescript
|
|
1192
|
-
// Allowed field types
|
|
1193
600
|
type FieldType =
|
|
1194
601
|
| { kind: 'string' }
|
|
1195
602
|
| { kind: 'number' }
|
|
@@ -1207,144 +614,66 @@ interface FieldDef {
|
|
|
1207
614
|
|
|
1208
615
|
interface CollectionDef {
|
|
1209
616
|
fields: FieldDef[];
|
|
617
|
+
schemaOrgType?: string;
|
|
1210
618
|
}
|
|
1211
619
|
|
|
1212
|
-
// Full schema — collection names to definitions
|
|
1213
620
|
type SpaceSchema = Record<string, CollectionDef>;
|
|
1214
|
-
```
|
|
1215
|
-
|
|
1216
|
-
### Object Data
|
|
1217
621
|
|
|
1218
|
-
```typescript
|
|
1219
|
-
// An object addressed by location. References between objects are body
|
|
1220
|
-
// fields whose values are location strings.
|
|
1221
622
|
interface RoolObject {
|
|
1222
|
-
|
|
1223
|
-
collection: string;
|
|
1224
|
-
basename: string;
|
|
623
|
+
path: string;
|
|
1225
624
|
body: Record<string, unknown>;
|
|
1226
625
|
}
|
|
1227
626
|
|
|
1228
|
-
|
|
627
|
+
interface GetObjectsResult {
|
|
628
|
+
objects: RoolObject[];
|
|
629
|
+
missing: string[];
|
|
630
|
+
}
|
|
631
|
+
|
|
1229
632
|
interface RoolObjectStat {
|
|
1230
|
-
|
|
633
|
+
path: string;
|
|
1231
634
|
modifiedAt: number;
|
|
1232
635
|
modifiedBy: string;
|
|
1233
636
|
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)
|
|
637
|
+
modifiedInChannel: string;
|
|
638
|
+
modifiedInConversation: string | null;
|
|
639
|
+
modifiedInInteraction: string | null;
|
|
1250
640
|
}
|
|
1251
641
|
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
createdAt: number;
|
|
1258
|
-
createdBy: string;
|
|
1259
|
-
interactionCount: number;
|
|
1260
|
-
}
|
|
642
|
+
type PromptAttachment =
|
|
643
|
+
| File
|
|
644
|
+
| Blob
|
|
645
|
+
| { data: string; contentType: string; filename?: string }
|
|
646
|
+
| string;
|
|
1261
647
|
|
|
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
|
-
}
|
|
648
|
+
type PromptEffort = 'QUICK' | 'STANDARD' | 'REASONING' | 'RESEARCH';
|
|
1270
649
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
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;
|
|
1279
659
|
}
|
|
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
660
|
|
|
1302
661
|
type InteractionStatus = 'pending' | 'streaming' | 'done' | 'error';
|
|
1303
662
|
|
|
1304
663
|
interface Interaction {
|
|
1305
|
-
id: string;
|
|
1306
|
-
parentId: string | null;
|
|
664
|
+
id: string;
|
|
665
|
+
parentId: string | null;
|
|
1307
666
|
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
|
|
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[];
|
|
1348
677
|
}
|
|
1349
678
|
```
|
|
1350
679
|
|