@rool-dev/sdk 0.9.0-dev.a397f4d → 0.9.0-dev.bab1e95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,13 +4,14 @@ The TypeScript SDK for Rool, a persistent and collaborative environment for orga
4
4
 
5
5
  > **Building a new Rool extension?** Start with [`@rool-dev/extension`](/extension/) — it handles hosting, dev server, and gives you a reactive Svelte channel out of the box. This SDK is for advanced use cases: integrating Rool into an existing application, building Node.js scripts, or working outside the extension sandbox.
6
6
 
7
- The SDK manages authentication, real-time synchronization, and media storage. Core primitives:
7
+ The SDK manages authentication, real-time synchronization, and per-space file storage. Core primitives:
8
8
 
9
- - **Spaces** — Containers for objects, schema, metadata, and channels
9
+ - **Spaces** — Containers for objects, schema, metadata, channels, and files
10
10
  - **Channels** — Named contexts within a space. All object and AI operations go through a channel.
11
11
  - **Conversations** — Independent interaction histories within a channel.
12
- - **Objects** — Key-value records with any fields you define. References between objects are data fields whose values are object IDs.
12
+ - **Objects** — Records addressed by a **location** path (`/space/<collection>/<basename>.json`). The body holds user-defined fields. References between objects are body fields whose values are location strings.
13
13
  - **AI operations** — Create, update, or query objects using natural language and `{{placeholders}}`
14
+ - **File storage** — Every space has WebDAV file storage
14
15
 
15
16
  ## Installation
16
17
 
@@ -42,24 +43,20 @@ await channel.createCollection('body', [
42
43
  { name: 'orbits', type: { kind: 'maybe', inner: { kind: 'ref' } } },
43
44
  ]);
44
45
 
45
- // Create objects with AI-generated content using {{placeholders}}
46
- const { object: sun } = await channel.createObject({
47
- data: {
48
- type: 'body', // Must match a collection name
49
- name: 'Sun',
50
- mass: '{{mass in solar masses}}',
51
- radius: '{{radius in km}}'
52
- }
53
- });
54
-
55
- const { object: earth } = await channel.createObject({
56
- data: {
57
- type: 'body',
58
- name: 'Earth',
59
- mass: '{{mass in Earth masses}}',
60
- radius: '{{radius in km}}',
61
- orbits: sun.id // Reference to the sun object
62
- }
46
+ // Create objects with AI-generated content using {{placeholders}}.
47
+ // First arg is the collection. Second is the body — no `id` or `type`,
48
+ // identity lives in the location.
49
+ const { object: sun } = await channel.createObject('body', {
50
+ name: 'Sun',
51
+ mass: '{{mass in solar masses}}',
52
+ radius: '{{radius in km}}',
53
+ }, { basename: 'sun' });
54
+
55
+ const { object: earth } = await channel.createObject('body', {
56
+ name: 'Earth',
57
+ mass: '{{mass in Earth masses}}',
58
+ radius: '{{radius in km}}',
59
+ orbits: sun.location, // Reference to the sun via its location
63
60
  });
64
61
 
65
62
  // Use the AI agent to work with your data
@@ -67,7 +64,7 @@ const { message, objects } = await channel.prompt(
67
64
  'Add the other planets in our solar system, each referencing the Sun'
68
65
  );
69
66
  console.log(message); // AI explains what it did
70
- console.log(`Created ${objects.length} objects`);
67
+ console.log(`Modified ${objects.length} objects`);
71
68
 
72
69
  // Query with natural language
73
70
  const { objects: innerPlanets } = await channel.findObjects({
@@ -82,11 +79,11 @@ channel.close();
82
79
 
83
80
  ### Spaces and Channels
84
81
 
85
- A **space** is a container that holds objects, schema, metadata, and channels. A **channel** is a named context within a space — it's the handle you use for all object and AI operations. Each channel contains one or more **conversations**, each with independent interaction history.
82
+ 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.
86
83
 
87
84
  There are two main handles:
88
- - **`RoolSpace`** — Live handle with SSE subscription for user management, link access, channel management, export, and channel lifecycle events. Extends `EventEmitter`.
89
- - **`RoolChannel`** — Full real-time handle for objects, AI prompts, media, schema, and undo/redo.
85
+ - **`RoolSpace`** — Live handle with SSE subscription for user management, link access, channel management, file storage, export, and channel lifecycle events. Extends `EventEmitter`.
86
+ - **`RoolChannel`** — Full real-time handle for objects, AI prompts, schema, and undo/redo.
90
87
 
91
88
  ```typescript
92
89
  // Open a space — live handle with SSE subscription
@@ -186,52 +183,91 @@ thread.getInteractions(); // Returns the blue branch (root → leaf)
186
183
  - `prompt()` with no `parentInteractionId` auto-continues from `activeLeafId`
187
184
  - `prompt()` with `parentInteractionId: null` starts a new root-level branch
188
185
 
189
- ### Objects & References
186
+ ### Objects, Locations, and References
190
187
 
191
- **Objects** are plain key-value records. `id` and `type` are reserved; everything else is application-defined. Every object must include a `type` field whose value names a collection in the schema that binds the object to that collection and determines how it's validated. Create the collection first (see [Collection Schema](#collection-schema)).
188
+ 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.
192
189
 
193
190
  ```typescript
194
- { id: 'abc123', type: 'article', title: 'Hello World', status: 'draft' }
191
+ {
192
+ location: '/space/article/welcome.json',
193
+ collection: 'article',
194
+ basename: 'welcome',
195
+ body: { title: 'Hello World', status: 'draft' },
196
+ }
195
197
  ```
196
198
 
197
- **References** between objects are data fields whose values are object IDs. The system detects these statistically any string field whose value matches an existing object ID is recognized as a reference.
199
+ **Body** is the user-defined data. It never contains `id` or `type`identity lives entirely in the location.
200
+
201
+ **References** between objects are body fields whose values are location strings:
198
202
 
199
203
  ```typescript
200
- // A planet references a star via the 'orbits' field
201
- { id: 'earth', type: 'body', name: 'Earth', orbits: 'sun-01' }
204
+ // A planet references a star
205
+ {
206
+ location: '/space/body/earth.json',
207
+ collection: 'body',
208
+ basename: 'earth',
209
+ body: { name: 'Earth', orbits: '/space/body/sun.json' },
210
+ }
202
211
 
203
212
  // An array of references
204
- { id: 'team-a', type: 'team', name: 'Alpha', members: ['user-1', 'user-2', 'user-3'] }
213
+ {
214
+ location: '/space/team/alpha.json',
215
+ collection: 'team',
216
+ basename: 'alpha',
217
+ body: {
218
+ name: 'Alpha',
219
+ members: [
220
+ '/space/user/alice.json',
221
+ '/space/user/bob.json',
222
+ '/space/user/carol.json',
223
+ ],
224
+ },
225
+ }
205
226
  ```
206
227
 
207
- References are just data — no special API is needed to create or remove them. Set a field to an object ID to create a reference; clear it to remove it.
228
+ References are just data — no special API is needed to create or remove them. Set a field to a location string to create a reference; clear it to remove it.
229
+
230
+ #### Location helpers
231
+
232
+ ```typescript
233
+ import { loc, parseLocation, normalizeLocation, generateBasename } from '@rool-dev/sdk';
234
+
235
+ loc('article', 'welcome'); // '/space/article/welcome.json'
236
+ parseLocation('/space/article/welcome.json'); // { collection: 'article', basename: 'welcome' }
237
+
238
+ // normalizeLocation accepts canonical or short form and returns canonical
239
+ normalizeLocation('article/welcome'); // '/space/article/welcome.json'
240
+ normalizeLocation('/space/article/welcome.json'); // unchanged
241
+
242
+ // 6-char random basename — same generator the SDK uses by default
243
+ generateBasename(); // e.g., 'X7kQ9p'
244
+ ```
245
+
246
+ 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.
208
247
 
209
248
  ### AI Placeholder Pattern
210
249
 
211
- Use `{{description}}` in field values to have AI generate content:
250
+ Use `{{description}}` in body field values to have AI generate content:
212
251
 
213
252
  ```typescript
214
253
  // Create with AI-generated content
215
- await channel.createObject({
216
- data: {
217
- type: 'article',
218
- headline: '{{catchy headline about coffee}}',
219
- body: '{{informative paragraph}}'
220
- }
254
+ await channel.createObject('article', {
255
+ headline: '{{catchy headline about coffee}}',
256
+ body: '{{informative paragraph}}',
221
257
  });
222
258
 
223
259
  // Update existing content with AI
224
- await channel.updateObject('abc123', {
260
+ await channel.updateObject('/space/article/welcome.json', {
225
261
  prompt: 'Make the body shorter and more casual'
226
262
  });
227
263
 
228
264
  // Add new AI-generated field to existing object
229
- await channel.updateObject('abc123', {
265
+ await channel.updateObject('/space/article/welcome.json', {
230
266
  data: { summary: '{{one-sentence summary}}' }
231
267
  });
232
268
  ```
233
269
 
234
- When resolving placeholders, the agent has access to the full object data and the surrounding space context (except for `_`-prefixed fields). Placeholders are instructions, not templates, and do not need to repeat information already present in other fields.
270
+ 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.
235
271
 
236
272
  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.
237
273
 
@@ -242,7 +278,7 @@ Undo/redo works on **checkpoints**, not individual operations. Call `checkpoint(
242
278
  ```typescript
243
279
  // Create a checkpoint before user action
244
280
  await channel.checkpoint('Delete object');
245
- await channel.deleteObjects([objectId]);
281
+ await channel.deleteObjects([location]);
246
282
 
247
283
  // User can now undo back to the checkpoint
248
284
  if (await channel.canUndo()) {
@@ -259,16 +295,13 @@ Checkpoints are **space-wide**: one shared stack across all channels and users.
259
295
 
260
296
  ### Hidden Fields
261
297
 
262
- Fields starting with `_` (e.g., `_ui`, `_cache`) are hidden from AI and ignored by the schema — you can add them to any object regardless of its collection definition. Otherwise they behave like normal fields: they sync in real-time, persist to the server, support undo/redo, and are visible to all users of the Space. Use them for UI state, positions, or other data the AI shouldn't see or modify:
298
+ Body fields starting with `_` (e.g., `_ui`, `_cache`) are hidden from AI and ignored by the schema — you can add them to any object regardless of its collection definition. Otherwise they behave like normal fields: they sync in real-time, persist to the server, support undo/redo, and are visible to all users of the space. Use them for UI state, positions, or other data the AI shouldn't see or modify:
263
299
 
264
300
  ```typescript
265
- await channel.createObject({
266
- data: {
267
- type: 'article',
268
- title: 'My Article',
269
- author: "John Doe",
270
- _ui: { x: 100, y: 200, collapsed: false }
271
- }
301
+ await channel.createObject('article', {
302
+ title: 'My Article',
303
+ author: 'John Doe',
304
+ _ui: { x: 100, y: 200, collapsed: false }
272
305
  });
273
306
  ```
274
307
 
@@ -283,42 +316,50 @@ Events fire for both local and remote changes. The `source` field indicates orig
283
316
 
284
317
  ```typescript
285
318
  // All UI updates happen in one place, regardless of change source
286
- channel.on('objectUpdated', ({ objectId, object, source }) => {
287
- renderObject(objectId, object);
319
+ channel.on('objectUpdated', ({ location, object, source }) => {
320
+ renderObject(location, object);
288
321
  if (source === 'remote_agent') {
289
322
  doLayout(); // AI might have added content
290
323
  }
291
324
  });
292
325
 
293
326
  // Caller just makes the change - event handler does the UI work
294
- channel.updateObject(objectId, { prompt: 'expand this' });
327
+ channel.updateObject(location, { prompt: 'expand this' });
295
328
  ```
296
329
 
297
- ### Custom Object IDs
330
+ ### Locations & Basenames
298
331
 
299
- By default, `createObject` generates a 6-character alphanumeric ID. Provide your own via `data.id`:
332
+ By default, `createObject` mints a 6-character alphanumeric basename. Provide your own via `options.basename` for meaningful identifiers:
300
333
 
301
334
  ```typescript
302
- await channel.createObject({ data: { id: 'article-42', type: 'article', title: 'The Meaning of Life' } });
335
+ await channel.createObject('article',
336
+ { title: 'The Meaning of Life' },
337
+ { basename: 'meaning-of-life' },
338
+ );
339
+ // → location: /space/article/meaning-of-life.json
303
340
  ```
304
341
 
305
- **Why use custom IDs?**
306
- - **Fire-and-forget creation** — Know the ID immediately without awaiting the response.
307
- - **Meaningful IDs** — Use domain-specific IDs like `user-123` or `doc-abc` for easier debugging and external references.
342
+ **Why pin a basename?**
343
+ - **Fire-and-forget creation** — Know the location immediately without awaiting the response.
344
+ - **Meaningful identifiers** — Use domain-specific names like `welcome` or `2026-budget` for easier debugging and external references.
308
345
 
309
346
  ```typescript
310
347
  // Fire-and-forget: create and reference without waiting
311
- const id = RoolClient.generateId();
312
- channel.createObject({ data: { id, type: 'note', text: '{{expand this idea}}' } });
313
- channel.updateObject(parentId, { data: { notes: [...existingNotes, id] } }); // Add reference immediately
348
+ const basename = RoolClient.generateBasename();
349
+ const location = loc('note', basename);
350
+
351
+ channel.createObject('note', { text: '{{expand this idea}}' }, { basename });
352
+ channel.updateObject(parentLocation, {
353
+ data: { notes: [...existingNotes, location] },
354
+ }); // Add reference immediately
314
355
  ```
315
356
 
316
- **Constraints:**
317
- - Must contain only alphanumeric characters, hyphens (`-`), and underscores (`_`)
318
- - Must be unique within the space (throws if ID exists)
319
- - Cannot be changed after creation (immutable)
357
+ **Basename constraints:**
358
+ - Must start with an alphanumeric character.
359
+ - Other characters may be alphanumeric, hyphens (`-`), or underscores (`_`).
360
+ - Must be unique within its collection (throws if the location already exists).
320
361
 
321
- Use `RoolClient.generateId()` when you need an ID before calling `createObject` but don't need it to be meaningful — it gives you a valid random ID without writing your own generator.
362
+ Use `moveObject` to rename an object or move it to a different collection see [Moving and Renaming](#moving-and-renaming).
322
363
 
323
364
  ## Authentication
324
365
 
@@ -365,7 +406,7 @@ if (!authenticated) {
365
406
 
366
407
  ## AI Agent
367
408
 
368
- The `prompt()` method is the primary way to invoke the AI agent. The agent has editor-level capabilities — it can create, modify, and delete objects — but cannot see or modify `_`-prefixed fields.
409
+ The `prompt()` method is the primary way to invoke the AI agent. The agent has editor-level capabilities — it can create, modify, move, and delete objects — but cannot see or modify `_`-prefixed fields.
369
410
 
370
411
  ```typescript
371
412
  const { message, objects } = await channel.prompt(
@@ -389,13 +430,13 @@ Returns a message (the AI's response) and the list of objects that were created
389
430
 
390
431
  | Option | Description |
391
432
  |--------|-------------|
392
- | `objectIds` | Focus the AI on specific objects (given primary attention in context) |
433
+ | `locations` | Focus the AI on specific objects, identified by location (given primary attention in context). |
393
434
  | `responseSchema` | Request structured JSON instead of text summary |
394
435
  | `effort` | Effort level: `'QUICK'`, `'STANDARD'` (default), `'REASONING'`, or `'RESEARCH'` |
395
436
  | `ephemeral` | If true, don't record in interaction history (useful for tab completion) |
396
- | `readOnly` | If true, disable mutation tools (create, update, delete). Use for questions. |
437
+ | `readOnly` | If true, disable mutation tools (create, update, move, delete). Use for questions. |
397
438
  | `parentInteractionId` | Parent interaction in the conversation tree. Omit to auto-continue from the active leaf. Pass `null` to start a new root-level branch. Pass a specific ID to branch from that point (edit/reroll). |
398
- | `attachments` | Files to attach (`File`, `Blob`, or `{ data, contentType }`). Uploaded to the media store via `uploadMedia()`. Resulting URLs are stored on the interaction's `attachments` field for UI rendering. The AI can interpret images (JPEG, PNG, GIF, WebP, SVG), PDFs, text-based files (plain text, Markdown, CSV, HTML, XML, JSON), and DOCX documents. Other file types are uploaded and stored but the AI cannot read their contents. |
439
+ | `attachments` | Files to attach (`File`, `Blob`, or `{ data, contentType }`). Stored as authenticated space files; resulting `rool-drive:/...` references are stored on the interaction's `attachments` field for UI rendering. The AI can interpret images (JPEG, PNG, GIF, WebP, SVG), PDFs, text-based files (plain text, Markdown, CSV, HTML, XML, JSON), and DOCX documents. Other file types are uploaded and stored but the AI cannot natively consume their contents, only use shell tools on them. |
399
440
  | `signal` | `AbortSignal` to stop the prompt mid-flight. When aborted, the agent loop halts and the streaming response closes. Note that any LLM turn already in flight on Vertex keeps generating server-side and is billed. |
400
441
 
401
442
  ### Effort Levels
@@ -418,7 +459,7 @@ const { objects } = await channel.prompt(
418
459
  // Work with specific objects
419
460
  const result = await channel.prompt(
420
461
  "Summarize these articles",
421
- { objectIds: ['article-1', 'article-2'] }
462
+ { locations: ['/space/article/intro.json', '/space/article/conclusion.json'] }
422
463
  );
423
464
 
424
465
  // Quick question without mutations (fast model + read-only)
@@ -455,7 +496,11 @@ Use `responseSchema` to get structured JSON instead of a text message:
455
496
 
456
497
  ```typescript
457
498
  const { message } = await channel.prompt("Categorize these items", {
458
- objectIds: ['item-1', 'item-2', 'item-3'],
499
+ locations: [
500
+ '/space/item/widget.json',
501
+ '/space/item/gadget.json',
502
+ '/space/item/gizmo.json',
503
+ ],
459
504
  responseSchema: {
460
505
  type: 'object',
461
506
  properties: {
@@ -477,7 +522,7 @@ console.log(result.categories, result.summary);
477
522
  AI operations automatically receive context:
478
523
  - **Interaction history** — Previous interactions and their results from this channel
479
524
  - **Recently modified objects** — Objects created or changed recently
480
- - **Selected objects** — Objects passed via `objectIds` are given primary focus
525
+ - **Selected objects** — Objects passed via `locations` are given primary focus
481
526
 
482
527
  This context flows automatically — no configuration needed. The AI sees enough history to maintain coherent interactions while respecting the `_`-prefixed field hiding rules.
483
528
 
@@ -505,7 +550,7 @@ await space.addUser(user.id, 'editor');
505
550
  |------|--------------|
506
551
  | `owner` | Full control, can delete space and manage all users |
507
552
  | `admin` | All editor capabilities, plus can manage users (except other admins/owners) |
508
- | `editor` | Can create, modify, and delete objects |
553
+ | `editor` | Can create, modify, move, and delete objects |
509
554
  | `viewer` | Read-only access (can query with `prompt` and `findObjects`) |
510
555
 
511
556
  ### Space Collaboration Methods
@@ -548,6 +593,7 @@ When a user accesses a space via URL, they're granted the corresponding role (`v
548
593
  | `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. |
549
594
  | `getCurrentUser(): Promise<CurrentUser>` | Fetch fresh user profile from server (id, email, name, photoUrl, slug, plan, creditsBalance, totalCreditsUsed, createdAt, lastActivity, processedAt, storage) |
550
595
  | `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. |
596
+ | `deleteCurrentUser(): Promise<void>` | Mark the current user's account for deletion (10-minute grace period before irreversible). Logs out the client. |
551
597
  | `searchUser(email): Promise<UserResult \| null>` | Find user by exact email address (no partial matching) |
552
598
 
553
599
  ### Real-time Collaboration
@@ -555,7 +601,7 @@ When a user accesses a space via URL, they're granted the corresponding role (`v
555
601
  When multiple users have a space open, changes sync in real-time. The `source` field in events tells you who made the change:
556
602
 
557
603
  ```typescript
558
- channel.on('objectUpdated', ({ objectId, object, source }) => {
604
+ channel.on('objectUpdated', ({ location, object, source }) => {
559
605
  if (source === 'remote_user') {
560
606
  // Another user made this change
561
607
  showCollaboratorActivity(object);
@@ -591,8 +637,11 @@ const client = new RoolClient({
591
637
  | `listSpaces(): Promise<RoolSpaceInfo[]>` | List available spaces |
592
638
  | `openSpace(spaceId): Promise<RoolSpace>` | Open a space with live SSE subscription. Caches and reuses open spaces. Call `space.openChannel(channelId)` to get a channel. |
593
639
  | `createSpace(name): Promise<RoolSpace>` | Create a new space, returns live handle with SSE subscription |
640
+ | `duplicateSpace(sourceSpaceId, name): Promise<RoolSpace>` | Duplicate an existing space. Returns a handle to the new space. |
594
641
  | `deleteSpace(id): Promise<void>` | Permanently delete a space (cannot be undone) |
595
642
  | `importArchive(name, archive): Promise<RoolSpace>` | Import from a zip archive, creating a new space |
643
+ | `webdav(spaceId): RoolWebDAV` | Open a WebDAV client for a space's file storage |
644
+ | `getSpaceStorageUsage(spaceId): Promise<SpaceFileStorageUsage>` | Get WebDAV quota usage for a space |
596
645
 
597
646
  ### Channel Management
598
647
 
@@ -676,7 +725,8 @@ Discover and install extensions published by other users.
676
725
 
677
726
  | Method | Description |
678
727
  |--------|-------------|
679
- | `RoolClient.generateId(): string` | Generate 6-char alphanumeric ID (static) |
728
+ | `RoolClient.generateBasename(): string` | Generate a 6-char alphanumeric basename for new object identities. |
729
+ | `RoolClient.generateId(): string` | Same as `generateBasename()`; retained for callers minting non-object IDs (interactions, conversations, channels). |
680
730
  | `destroy(): void` | Clean up resources |
681
731
 
682
732
  ### Client Events
@@ -710,7 +760,7 @@ client.on('spaceRenamed', (id, name) => {
710
760
 
711
761
  ## RoolSpace API
712
762
 
713
- A space handle with a live SSE subscription. Extends `EventEmitter`. Manages user access, link sharing, channels, and export. The `channels` property auto-updates via SSE, and channel lifecycle events fire in real-time.
763
+ 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.
714
764
 
715
765
  `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.
716
766
 
@@ -724,6 +774,7 @@ A space handle with a live SSE subscription. Extends `EventEmitter`. Manages use
724
774
  | `linkAccess: LinkAccess` | URL sharing level |
725
775
  | `memberCount: number` | Number of users with access to the space |
726
776
  | `channels: ChannelInfo[]` | Live channel list (auto-updates via SSE) |
777
+ | `webdav: RoolWebDAV` | WebDAV client for this space's file storage |
727
778
 
728
779
  ### Methods
729
780
 
@@ -742,6 +793,7 @@ A space handle with a live SSE subscription. Extends `EventEmitter`. Manages use
742
793
  | `deleteChannel(channelId): Promise<void>` | Delete a channel |
743
794
  | `installExtension(extensionId, channelId): Promise<string>` | Install an extension into a channel of this space. If you own it, wires it directly. If it's a marketplace extension, copies and builds a new extension in your library. Returns the channel ID. |
744
795
  | `exportArchive(): Promise<Blob>` | Export space as zip archive |
796
+ | `getStorageUsage(): Promise<SpaceFileStorageUsage>` | Get WebDAV quota usage for this space |
745
797
  | `refresh(): Promise<void>` | Refresh space data from server |
746
798
 
747
799
  ### Space Events
@@ -782,49 +834,126 @@ A channel is a named context within a space. All object operations, AI prompts,
782
834
 
783
835
  ### Object Operations
784
836
 
785
- Objects are plain key/value records. `id` and `type` are reserved; everything else is application-defined. References between objects are data fields whose values are object IDs. Every object must include a `type` field whose value names a collection in the schema (see [Collection Schema](#collection-schema)) that binds the object to that collection. Before introducing a new kind of object, create the matching collection.
837
+ 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 user-defined fields only; identity lives in the location.
838
+
839
+ All methods that accept a location accept either the canonical form or the short form (`collection/basename`).
786
840
 
787
841
  | Method | Description |
788
842
  |--------|-------------|
789
- | `getObject(objectId): Promise<RoolObject \| undefined>` | Get object data, or undefined if not found. |
790
- | `stat(objectId): RoolObjectStat \| undefined` | Get object stat (audit info: modifiedAt, modifiedBy, modifiedByName, and the channel/conversation/interaction where the last write happened), or undefined if not found. Sync read from local cache. |
791
- | `findObjects(options): Promise<{ objects, message }>` | Find objects using structured filters and natural language. Results sorted by modifiedAt (desc by default). |
792
- | `getObjectIds(options?): string[]` | Get all object IDs. Sorted by modifiedAt (desc by default). Options: `{ limit?, order? }`. |
793
- | `createObject(options): Promise<{ object, message }>` | Create a new object. Returns the object (with AI-filled content) and message. |
794
- | `updateObject(objectId, options): Promise<{ object, message }>` | Update an existing object. Returns the updated object and message. |
795
- | `deleteObjects(objectIds): Promise<void>` | Delete objects. Other objects referencing deleted objects retain stale ref values. |
843
+ | `getObject(location): Promise<RoolObject \| undefined>` | Get an object, or undefined if not found. |
844
+ | `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. |
845
+ | `findObjects(options): Promise<{ objects, message }>` | Find objects using structured filters and/or natural language. Results sorted by modifiedAt (desc by default). |
846
+ | `getObjectLocations(options?): string[]` | Get all object locations. Sorted by modifiedAt (desc by default). Options: `{ limit?, order? }`. |
847
+ | `createObject(collection, body, options?): Promise<{ object, message }>` | Create a new object in `collection`. The SDK mints a random basename unless you pass `options.basename`. |
848
+ | `updateObject(location, options): Promise<{ object, message }>` | Update an existing object's body. |
849
+ | `moveObject(from, to, options?): Promise<{ object, message }>` | Rename or relocate an object. See [Moving and Renaming](#moving-and-renaming). |
850
+ | `deleteObjects(locations): Promise<void>` | Delete objects by location. Other objects' refs become stale. |
851
+
852
+ #### createObject
796
853
 
797
- #### createObject Options
854
+ ```typescript
855
+ // Auto-generated basename
856
+ const { object } = await channel.createObject('article', {
857
+ title: 'Hello',
858
+ body: 'World',
859
+ });
860
+ // → object.location: '/space/article/X7kQ9p.json'
861
+
862
+ // Pinned basename
863
+ await channel.createObject('article',
864
+ { title: 'Welcome' },
865
+ { basename: 'welcome' },
866
+ );
867
+ // → location: '/space/article/welcome.json'
868
+
869
+ // AI placeholders
870
+ await channel.createObject('article', {
871
+ headline: '{{catchy headline}}',
872
+ body: '{{long-form intro}}',
873
+ });
874
+ ```
798
875
 
799
876
  | Option | Description |
800
877
  |--------|-------------|
801
- | `data` | Object data fields (required). Must include `type` naming an existing collection. Include `id` to use a custom ID. Use `{{placeholder}}` for AI-generated content. Fields prefixed with `_` are hidden from AI. |
802
- | `ephemeral` | If true, the operation won't be recorded in interaction history. Useful for transient operations. |
878
+ | `basename` | Specific basename to use. If omitted, the SDK generates a random 6-char one. |
879
+ | `ephemeral` | If true, the operation won't be recorded in interaction history. |
880
+ | `parentInteractionId` | Conversation tree parent. Omit to auto-continue; pass `null` for a new root. |
881
+
882
+ The body must not contain `id` or `type` — those names are reserved for identity. The SDK throws if either is present.
803
883
 
804
- #### updateObject Options
884
+ #### updateObject
885
+
886
+ ```typescript
887
+ // Add/update fields
888
+ await channel.updateObject('/space/article/welcome.json', {
889
+ data: { status: 'published' },
890
+ });
891
+
892
+ // Delete a field (pass null)
893
+ await channel.updateObject('/space/article/welcome.json', {
894
+ data: { draft: null },
895
+ });
896
+
897
+ // AI-driven rewrite
898
+ await channel.updateObject('/space/article/welcome.json', {
899
+ prompt: 'Tighten the intro by 30%.',
900
+ });
901
+ ```
805
902
 
806
903
  | Option | Description |
807
904
  |--------|-------------|
808
- | `data` | Fields to add or update. Pass `null`/`undefined` to delete a field. Use `{{placeholder}}` for AI-generated content. Setting a new `type` retypes the object — the merged result must conform to the new collection. Fields prefixed with `_` are hidden from AI. |
905
+ | `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. |
809
906
  | `prompt` | Natural language instruction for AI to modify content. |
810
- | `ephemeral` | If true, the operation won't be recorded in interaction history. Useful for transient operations. |
907
+ | `ephemeral` | If true, the operation won't be recorded in interaction history. |
908
+ | `parentInteractionId` | Conversation tree parent. Omit to auto-continue; pass `null` for a new root. |
909
+
910
+ `data` must not contain `id` or `type` — use `moveObject` to change identity.
911
+
912
+ #### Moving and Renaming
913
+
914
+ `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.
915
+
916
+ ```typescript
917
+ // Rename within the same collection
918
+ await channel.moveObject(
919
+ '/space/article/welcome.json',
920
+ '/space/article/hello-world.json',
921
+ );
922
+
923
+ // Move into a different collection
924
+ await channel.moveObject(
925
+ '/space/draft/post-42.json',
926
+ '/space/article/post-42.json',
927
+ );
928
+
929
+ // Move and replace body in one go
930
+ await channel.moveObject(from, to, {
931
+ body: { title: 'Hello, world', status: 'published' },
932
+ });
933
+ ```
934
+
935
+ | Option | Description |
936
+ |--------|-------------|
937
+ | `body` | Replace the body atomically as part of the move. If omitted, the body is preserved. Must not contain `id` or `type`. |
938
+ | `ephemeral` | If true, the operation won't be recorded in interaction history. |
939
+ | `parentInteractionId` | Conversation tree parent. Omit to auto-continue; pass `null` for a new root. |
811
940
 
812
- #### findObjects Options
941
+ #### findObjects
813
942
 
814
943
  Find objects using structured filters and/or natural language.
815
944
 
816
945
  - **`where` only** — exact-match filtering, no AI, no credits.
817
- - **`collection` only** — filter by collection name (matches objects whose `type` field equals the name), no AI, no credits.
946
+ - **`collection` only** — filter by collection name, no AI, no credits.
818
947
  - **`prompt` only** — AI-powered semantic query over all objects.
819
- - **`where` + `prompt`** — `where` (and `objectIds`) narrow the data set first, then the AI queries within the constrained set.
948
+ - **`where` + `prompt`** — `where` (and `locations`) narrow the data set first, then the AI queries within the constrained set.
820
949
 
821
950
  | Option | Description |
822
951
  |--------|-------------|
823
- | `where` | Exact-match field filter (e.g. `{ status: 'published' }`). Values must match literally — no operators or `{{placeholders}}`. When combined with `prompt`, constrains which objects the AI can see. |
824
- | `collection` | Filter by collection name. Returns objects whose `type` field equals the given name. |
952
+ | `where` | Exact-match body-field filter (e.g. `{ status: 'published' }`). Values must match literally — no operators or `{{placeholders}}`. When combined with `prompt`, constrains which objects the AI can see. |
953
+ | `collection` | Filter by collection name. |
825
954
  | `prompt` | Natural language query. Triggers AI evaluation (uses credits). |
826
955
  | `limit` | Maximum number of results. |
827
- | `objectIds` | Scope to specific object IDs. Constrains the candidate set in both structured and AI queries. |
956
+ | `locations` | Scope to specific object locations. Constrains the candidate set in both structured and AI queries. |
828
957
  | `order` | Sort order by modifiedAt: `'asc'` or `'desc'` (default: `'desc'`). |
829
958
  | `ephemeral` | If true, the query won't be recorded in interaction history. Useful for responsive search. |
830
959
 
@@ -860,7 +989,7 @@ const { objects } = await channel.findObjects({
860
989
  });
861
990
  ```
862
991
 
863
- When `where` or `objectIds` are provided with a `prompt`, the AI only sees the filtered subset — not the full space. The returned `message` explains the query result.
992
+ When `where` or `locations` are provided with a `prompt`, the AI only sees the filtered subset — not the full space. The returned `message` explains the query result.
864
993
 
865
994
  ### Undo/Redo
866
995
 
@@ -877,7 +1006,7 @@ See [Checkpoints & Undo/Redo](#checkpoints--undoredo) for semantics.
877
1006
 
878
1007
  ### Space Metadata
879
1008
 
880
- Store arbitrary data alongside the Space without it being part of the object data (e.g., viewport state, user preferences).
1009
+ Store arbitrary data alongside the space without it being part of an object's body (e.g., viewport state, user preferences).
881
1010
 
882
1011
  | Method | Description |
883
1012
  |--------|-------------|
@@ -885,35 +1014,73 @@ Store arbitrary data alongside the Space without it being part of the object dat
885
1014
  | `getMetadata(key): unknown` | Get metadata value, or undefined if key not set |
886
1015
  | `getAllMetadata(): Record<string, unknown>` | Get all metadata |
887
1016
 
888
- ### Media
1017
+ ### Space File Storage
889
1018
 
890
- Media URLs in object fields are visible to AI. Both uploaded and AI-generated media work the same way — use `fetchMedia` to retrieve them for display.
1019
+ Every space has authenticated file storage. WebDAV is the SDK surface for that storage: paths are relative to the space root, collection operations use WebDAV collection semantics, and `rool-drive:/...` references are the text-safe way to point at files.
891
1020
 
892
- | Method | Description |
893
- |--------|-------------|
894
- | `uploadMedia(file): Promise<string>` | Upload file, returns URL |
895
- | `fetchMedia(url, options?): Promise<MediaResponse>` | Fetch any URL, returns headers and blob() method (adds auth for backend URLs, works for external URLs too). Pass `{ forceProxy: true }` to skip the direct fetch and route through the server proxy immediately. |
896
- | `deleteMedia(url): Promise<void>` | Delete media file by URL |
897
- | `listMedia(): Promise<MediaInfo[]>` | List all media with metadata |
1021
+ Use `client.webdav(spaceId)` when you only have an ID, or `space.webdav` when you already have an open space.
898
1022
 
899
1023
  ```typescript
900
- // Upload an image
901
- const url = await channel.uploadMedia(file);
902
- await channel.createObject({ data: { type: 'photo', title: 'Photo', image: url } });
1024
+ const webdav = client.webdav('space-id');
903
1025
 
904
- // Or let AI generate one using a placeholder
905
- await channel.createObject({
906
- data: { type: 'photo', title: 'Mascot', image: '{{generate an image of a flying tortoise}}' }
1026
+ await webdav.mkcol('docs');
1027
+ await webdav.put('docs/readme.md', '# Hello', {
1028
+ contentType: 'text/markdown',
1029
+ ifNoneMatch: '*',
907
1030
  });
908
1031
 
909
- // Display media (handles auth automatically)
910
- const response = await channel.fetchMedia(object.image);
911
- if (response.contentType.startsWith('image/')) {
912
- const blob = await response.blob();
913
- img.src = URL.createObjectURL(blob);
914
- }
1032
+ const listing = await webdav.propfind('docs/', {
1033
+ depth: '1',
1034
+ props: ['displayname', 'getcontentlength', 'getcontenttype', 'getetag'],
1035
+ });
1036
+
1037
+ const file = await webdav.get('docs/readme.md');
1038
+ console.log(await file.text());
1039
+
1040
+ const ref = webdav.ref('docs/read me.md'); // "rool-drive:/docs/read%20me.md"
1041
+ const sameFile = await webdav.get(ref);
1042
+
1043
+ const usage = await space.getStorageUsage();
1044
+ console.log(usage.usedBytes);
1045
+ console.log(usage.availableBytes); // null means unlimited
1046
+ console.log(usage.limitBytes); // null means unlimited
915
1047
  ```
916
1048
 
1049
+ Paths are space-relative (`docs/readme.md`, not `/docs/readme.md`). `rool-drive:/...` references percent-encode path segments for use in text; `webdav.path('rool-drive:/docs/read%20me.md')` returns `docs/read me.md`. The WebDAV methods accept either paths or refs and normalize them to paths. `PUT` writes an exact path and does not create parent collections; create parents with `mkcol()` first. Helpers preserve WebDAV status semantics: non-success responses throw `WebDAVError` with `status`, `statusText`, and `body`.
1050
+
1051
+ | Method | Description |
1052
+ |--------|-------------|
1053
+ | `client.webdav(spaceId)` | Create a WebDAV client for a space |
1054
+ | `client.getSpaceStorageUsage(spaceId)` | Get WebDAV quota usage for a space |
1055
+ | `space.webdav` | WebDAV client for an open space |
1056
+ | `space.getStorageUsage()` | Get WebDAV quota usage for an open space |
1057
+ | `webdav.getStorageUsage()` | Get WebDAV quota usage through the WebDAV client |
1058
+ | `webdav.ref(path)` | Create a `rool-drive:/...` reference |
1059
+ | `webdav.path(pathOrRef)` | Normalize a path or `rool-drive:/...` reference to a path |
1060
+ | `webdav.propfind(pathOrRef, options)` | Read properties/list collections; explicit `depth` required |
1061
+ | `webdav.get(pathOrRef, options?)` / `webdav.head(pathOrRef)` | Read a file, including optional byte ranges for `get` |
1062
+ | `webdav.put(pathOrRef, body, options?)` | Write an exact file path; parents must already exist |
1063
+ | `webdav.mkcol(path)` | Create one collection |
1064
+ | `webdav.copy(source, destination, options?)` | Copy a file or collection within the same space |
1065
+ | `webdav.move(source, destination, options?)` | Move a file or collection within the same space |
1066
+ | `webdav.delete(pathOrRef, options?)` | Delete a file or collection |
1067
+ | `webdav.lock(pathOrRef, options)` / `webdav.refreshLock(pathOrRef, token)` / `webdav.unlock(token)` | WebDAV Class 2 write locks |
1068
+ | `webdav.request(method, path, init?)` | Raw authenticated WebDAV request escape hatch |
1069
+
1070
+ > **Note**: file storage `rool-drive:/...` and object locations `/space/...` are two different reference forms. `rool-drive:/...` always points at user-visible files in the space's WebDAV storage. Object locations identify records inside the space (and live at `/space/<collection>/<basename>.json`). They're not interchangeable — pass file refs to WebDAV methods and object locations to channel methods.
1071
+
1072
+ #### File references from AI responses
1073
+
1074
+ When an agent refers to a user-visible file, the SDK contract is `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-drive:/docs/read%20me.md`).
1075
+
1076
+ ```typescript
1077
+ const response = await space.webdav.get('rool-drive:/docs/readme.md');
1078
+ const blob = await response.blob();
1079
+ img.src = URL.createObjectURL(blob);
1080
+ ```
1081
+
1082
+ 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-drive:/docs/readme.md` so clients do not have to guess whether a string is a file. Prefer `webdav.ref(path)` rather than building refs by hand.
1083
+
917
1084
  ### Proxied Fetch
918
1085
 
919
1086
  Fetch external URLs via the server, bypassing CORS restrictions. Requires editor role or above. Private/internal IP ranges are blocked (SSRF protection).
@@ -937,7 +1104,7 @@ const response = await channel.fetch('https://api.example.com/submit', {
937
1104
 
938
1105
  ### Collection Schema
939
1106
 
940
- Collections are the types you use to group objects in a space. Every object must belong to a collection: the object's `data.type` field names the collection it belongs to, and the server validates the object's fields against that collection's definition. Renaming a collection cascades to the `type` field of every object bound to it; dropping a collection is blocked while any object is still bound to it.
1107
+ 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.
941
1108
 
942
1109
  Collections make up the schema and are stored in the space data, syncing in real time. The schema is visible to the AI agent so it knows which collections exist and what fields they contain, producing more consistent objects.
943
1110
 
@@ -982,7 +1149,7 @@ await channel.dropCollection('article');
982
1149
  | `string` | Text value | `{ kind: 'string' }` |
983
1150
  | `number` | Numeric value | `{ kind: 'number' }` |
984
1151
  | `boolean` | True/false | `{ kind: 'boolean' }` |
985
- | `ref` | Reference to another object | `{ kind: 'ref' }` |
1152
+ | `ref` | Reference to another object (location string) | `{ kind: 'ref' }` |
986
1153
  | `enum` | One of a set of values | `{ kind: 'enum', values: ['a', 'b'] }` |
987
1154
  | `literal` | Exact value | `{ kind: 'literal', value: 'fixed' }` |
988
1155
  | `array` | List of values | `{ kind: 'array', inner: { kind: 'string' } }` |
@@ -994,7 +1161,7 @@ Export and import space data as zip archives for backup, portability, or migrati
994
1161
 
995
1162
  | Method | Description |
996
1163
  |--------|-------------|
997
- | `space.exportArchive(): Promise<Blob>` | Export objects, metadata, channels, and media as a zip archive |
1164
+ | `space.exportArchive(): Promise<Blob>` | Export objects, metadata, channels, and files as a zip archive |
998
1165
  | `client.importArchive(name, archive): Promise<RoolSpace>` | Import from a zip archive, creating a new space |
999
1166
 
1000
1167
  **Export:**
@@ -1011,7 +1178,7 @@ const space = await client.importArchive('Imported Data', archiveBlob);
1011
1178
  const channel = await space.openChannel('main');
1012
1179
  ```
1013
1180
 
1014
- The archive format bundles `data.json` (with objects, metadata, and channels) and a `media/` folder containing all media files. Media URLs are rewritten to relative paths within the archive and restored on import.
1181
+ The archive bundles `data.json` (objects, metadata, and channels) together with the space file storage. File references are rewritten to relative paths within the archive and restored on import.
1015
1182
 
1016
1183
  ### Channel Events
1017
1184
 
@@ -1024,10 +1191,11 @@ Semantic events describe what changed. Events fire for both local changes and re
1024
1191
  // - 'remote_agent': AI agent made the change
1025
1192
  // - 'system': Resync after error
1026
1193
 
1027
- // Object events
1028
- channel.on('objectCreated', ({ objectId, object, source }) => void)
1029
- channel.on('objectUpdated', ({ objectId, object, source }) => void)
1030
- channel.on('objectDeleted', ({ objectId, source }) => void)
1194
+ // Object events — payload includes the full RoolObject
1195
+ channel.on('objectCreated', ({ location, object, source }) => void)
1196
+ channel.on('objectUpdated', ({ location, object, source }) => void)
1197
+ channel.on('objectDeleted', ({ location, source }) => void)
1198
+ channel.on('objectMoved', ({ from, to, object, source }) => void)
1031
1199
 
1032
1200
  // Space metadata
1033
1201
  channel.on('metadataUpdated', ({ metadata, source }) => void)
@@ -1054,7 +1222,7 @@ AI operations may fail due to rate limiting or other transient errors. Check `er
1054
1222
 
1055
1223
  ```typescript
1056
1224
  try {
1057
- await channel.updateObject(objectId, { prompt: 'expand this' });
1225
+ await channel.updateObject(location, { prompt: 'expand this' });
1058
1226
  } catch (error) {
1059
1227
  if (error.message.includes('temporarily unavailable')) {
1060
1228
  showToast('Service busy, please try again in a moment');
@@ -1088,7 +1256,7 @@ Channel management (listing, renaming, deleting channels) is done via the client
1088
1256
 
1089
1257
  The `ai` field in interactions distinguishes AI-generated responses from synthetic confirmations:
1090
1258
  - `ai: true` — AI processed this operation (prompt, or createObject/updateObject with placeholders)
1091
- - `ai: false` — System confirmation only (e.g., "Created object abc123")
1259
+ - `ai: false` — System confirmation only (e.g., "Created object /space/note/welcome.json")
1092
1260
 
1093
1261
  ### Tool Calls
1094
1262
 
@@ -1126,17 +1294,19 @@ type SpaceSchema = Record<string, CollectionDef>;
1126
1294
  ### Object Data
1127
1295
 
1128
1296
  ```typescript
1129
- // RoolObject represents the object data you work with
1130
- // Always contains `id`, plus any additional fields
1131
- // Fields prefixed with _ are hidden from AI
1132
- // References between objects are fields whose values are object IDs
1297
+ // An object — identity in the envelope, data in body. Body never contains
1298
+ // `id` or `type`; references between objects are body fields whose values
1299
+ // are location strings.
1133
1300
  interface RoolObject {
1134
- id: string;
1135
- [key: string]: unknown;
1301
+ location: string; // "/space/<collection>/<basename>.json"
1302
+ collection: string;
1303
+ basename: string;
1304
+ body: Record<string, unknown>;
1136
1305
  }
1137
1306
 
1138
- // Object stat - audit information returned by channel.stat()
1307
+ // Object stat audit information returned by channel.stat()
1139
1308
  interface RoolObjectStat {
1309
+ location: string;
1140
1310
  modifiedAt: number;
1141
1311
  modifiedBy: string;
1142
1312
  modifiedByName: string | null;
@@ -1208,19 +1378,19 @@ interface ToolCall {
1208
1378
  type InteractionStatus = 'pending' | 'streaming' | 'done' | 'error';
1209
1379
 
1210
1380
  interface Interaction {
1211
- id: string; // Unique ID for this interaction
1212
- parentId: string | null; // Parent in conversation tree (null = root)
1381
+ id: string; // Unique ID for this interaction
1382
+ parentId: string | null; // Parent in conversation tree (null = root)
1213
1383
  timestamp: number;
1214
- userId: string; // Who performed this interaction
1215
- userName?: string | null; // Display name at time of interaction
1216
- operation: 'prompt' | 'createObject' | 'updateObject' | 'deleteObjects';
1217
- input: string; // What the user did: prompt text or action description
1218
- output: string | null; // AI response or confirmation message (may be partial when streaming)
1219
- status: InteractionStatus; // Lifecycle status (pending → streaming → done/error)
1220
- ai: boolean; // Whether AI was invoked (vs synthetic confirmation)
1221
- modifiedObjectIds: string[]; // Objects affected by this interaction
1222
- toolCalls: ToolCall[]; // Tools called during this interaction (for AI prompts)
1223
- attachments?: string[]; // Media URLs attached by the user (images, documents, etc.)
1384
+ userId: string; // Who performed this interaction
1385
+ userName?: string | null; // Display name at time of interaction
1386
+ operation: 'prompt' | 'createObject' | 'updateObject' | 'moveObject' | 'deleteObjects';
1387
+ input: string; // What the user did: prompt text or action description
1388
+ output: string | null; // AI response or confirmation message (may be partial when streaming)
1389
+ status: InteractionStatus; // Lifecycle status (pending → streaming → done/error)
1390
+ ai: boolean; // Whether AI was invoked (vs synthetic confirmation)
1391
+ modifiedObjectLocations: string[]; // Locations of objects affected by this interaction
1392
+ toolCalls: ToolCall[]; // Tools called during this interaction (for AI prompts)
1393
+ attachments?: string[]; // rool-drive:/... file references attached by the user
1224
1394
  }
1225
1395
  ```
1226
1396
 
@@ -1234,8 +1404,6 @@ interface RoolSpaceInfo { id: string; name: string; role: RoolUserRole; ownerId:
1234
1404
  interface SpaceMember { id: string; email: string; role: RoolUserRole; photoUrl: string | null; }
1235
1405
  interface UserResult { id: string; email: string; name: string | null; photoUrl: string | null; }
1236
1406
  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>; }
1237
- interface MediaInfo { url: string; contentType: string; size: number; createdAt: string; }
1238
- interface MediaResponse { contentType: string; size: number | null; blob(): Promise<Blob>; }
1239
1407
  type ChangeSource = 'local_user' | 'remote_user' | 'remote_agent' | 'system';
1240
1408
  ```
1241
1409
 
@@ -1245,13 +1413,14 @@ type ChangeSource = 'local_user' | 'remote_user' | 'remote_agent' | 'system';
1245
1413
  type PromptEffort = 'QUICK' | 'STANDARD' | 'REASONING' | 'RESEARCH';
1246
1414
 
1247
1415
  interface PromptOptions {
1248
- objectIds?: string[]; // Scope to specific objects
1416
+ locations?: string[]; // Scope to specific objects
1249
1417
  responseSchema?: Record<string, unknown>;
1250
- effort?: PromptEffort; // Effort level (default: 'STANDARD')
1251
- ephemeral?: boolean; // Don't record in interaction history
1252
- readOnly?: boolean; // Disable mutation tools (default: false)
1253
- parentInteractionId?: string | null; // Branch from a specific interaction (omit to auto-continue)
1254
- attachments?: Array<File | Blob | { data: string; contentType: string }>; // Files to attach (uploaded to media store)
1418
+ effort?: PromptEffort; // Effort level (default: 'STANDARD')
1419
+ ephemeral?: boolean; // Don't record in interaction history
1420
+ readOnly?: boolean; // Disable mutation tools (default: false)
1421
+ parentInteractionId?: string | null; // Branch from a specific interaction (omit to auto-continue)
1422
+ attachments?: Array<File | Blob | { data: string; contentType: string }>; // Files to attach
1423
+ signal?: AbortSignal; // Cancel an in-flight prompt
1255
1424
  }
1256
1425
  ```
1257
1426