@rool-dev/sdk 0.9.0-dev.9cb655b → 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
@@ -9,7 +9,7 @@ The SDK manages authentication, real-time synchronization, and per-space file st
9
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
14
  - **File storage** — Every space has WebDAV file storage
15
15
 
@@ -43,24 +43,20 @@ await channel.createCollection('body', [
43
43
  { name: 'orbits', type: { kind: 'maybe', inner: { kind: 'ref' } } },
44
44
  ]);
45
45
 
46
- // Create objects with AI-generated content using {{placeholders}}
47
- const { object: sun } = await channel.createObject({
48
- data: {
49
- type: 'body', // Must match a collection name
50
- name: 'Sun',
51
- mass: '{{mass in solar masses}}',
52
- radius: '{{radius in km}}'
53
- }
54
- });
55
-
56
- const { object: earth } = await channel.createObject({
57
- data: {
58
- type: 'body',
59
- name: 'Earth',
60
- mass: '{{mass in Earth masses}}',
61
- radius: '{{radius in km}}',
62
- orbits: sun.id // Reference to the sun object
63
- }
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
64
60
  });
65
61
 
66
62
  // Use the AI agent to work with your data
@@ -68,7 +64,7 @@ const { message, objects } = await channel.prompt(
68
64
  'Add the other planets in our solar system, each referencing the Sun'
69
65
  );
70
66
  console.log(message); // AI explains what it did
71
- console.log(`Created ${objects.length} objects`);
67
+ console.log(`Modified ${objects.length} objects`);
72
68
 
73
69
  // Query with natural language
74
70
  const { objects: innerPlanets } = await channel.findObjects({
@@ -187,52 +183,91 @@ thread.getInteractions(); // Returns the blue branch (root → leaf)
187
183
  - `prompt()` with no `parentInteractionId` auto-continues from `activeLeafId`
188
184
  - `prompt()` with `parentInteractionId: null` starts a new root-level branch
189
185
 
190
- ### Objects & References
186
+ ### Objects, Locations, and References
191
187
 
192
- **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.
193
189
 
194
190
  ```typescript
195
- { 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
+ }
196
197
  ```
197
198
 
198
- **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:
199
202
 
200
203
  ```typescript
201
- // A planet references a star via the 'orbits' field
202
- { 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
+ }
203
211
 
204
212
  // An array of references
205
- { 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
+ }
226
+ ```
227
+
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'
206
244
  ```
207
245
 
208
- 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.
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.
209
247
 
210
248
  ### AI Placeholder Pattern
211
249
 
212
- Use `{{description}}` in field values to have AI generate content:
250
+ Use `{{description}}` in body field values to have AI generate content:
213
251
 
214
252
  ```typescript
215
253
  // Create with AI-generated content
216
- await channel.createObject({
217
- data: {
218
- type: 'article',
219
- headline: '{{catchy headline about coffee}}',
220
- body: '{{informative paragraph}}'
221
- }
254
+ await channel.createObject('article', {
255
+ headline: '{{catchy headline about coffee}}',
256
+ body: '{{informative paragraph}}',
222
257
  });
223
258
 
224
259
  // Update existing content with AI
225
- await channel.updateObject('abc123', {
260
+ await channel.updateObject('/space/article/welcome.json', {
226
261
  prompt: 'Make the body shorter and more casual'
227
262
  });
228
263
 
229
264
  // Add new AI-generated field to existing object
230
- await channel.updateObject('abc123', {
265
+ await channel.updateObject('/space/article/welcome.json', {
231
266
  data: { summary: '{{one-sentence summary}}' }
232
267
  });
233
268
  ```
234
269
 
235
- 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.
236
271
 
237
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.
238
273
 
@@ -243,7 +278,7 @@ Undo/redo works on **checkpoints**, not individual operations. Call `checkpoint(
243
278
  ```typescript
244
279
  // Create a checkpoint before user action
245
280
  await channel.checkpoint('Delete object');
246
- await channel.deleteObjects([objectId]);
281
+ await channel.deleteObjects([location]);
247
282
 
248
283
  // User can now undo back to the checkpoint
249
284
  if (await channel.canUndo()) {
@@ -260,16 +295,13 @@ Checkpoints are **space-wide**: one shared stack across all channels and users.
260
295
 
261
296
  ### Hidden Fields
262
297
 
263
- 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:
264
299
 
265
300
  ```typescript
266
- await channel.createObject({
267
- data: {
268
- type: 'article',
269
- title: 'My Article',
270
- author: "John Doe",
271
- _ui: { x: 100, y: 200, collapsed: false }
272
- }
301
+ await channel.createObject('article', {
302
+ title: 'My Article',
303
+ author: 'John Doe',
304
+ _ui: { x: 100, y: 200, collapsed: false }
273
305
  });
274
306
  ```
275
307
 
@@ -284,42 +316,50 @@ Events fire for both local and remote changes. The `source` field indicates orig
284
316
 
285
317
  ```typescript
286
318
  // All UI updates happen in one place, regardless of change source
287
- channel.on('objectUpdated', ({ objectId, object, source }) => {
288
- renderObject(objectId, object);
319
+ channel.on('objectUpdated', ({ location, object, source }) => {
320
+ renderObject(location, object);
289
321
  if (source === 'remote_agent') {
290
322
  doLayout(); // AI might have added content
291
323
  }
292
324
  });
293
325
 
294
326
  // Caller just makes the change - event handler does the UI work
295
- channel.updateObject(objectId, { prompt: 'expand this' });
327
+ channel.updateObject(location, { prompt: 'expand this' });
296
328
  ```
297
329
 
298
- ### Custom Object IDs
330
+ ### Locations & Basenames
299
331
 
300
- 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:
301
333
 
302
334
  ```typescript
303
- 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
304
340
  ```
305
341
 
306
- **Why use custom IDs?**
307
- - **Fire-and-forget creation** — Know the ID immediately without awaiting the response.
308
- - **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.
309
345
 
310
346
  ```typescript
311
347
  // Fire-and-forget: create and reference without waiting
312
- const id = RoolClient.generateId();
313
- channel.createObject({ data: { id, type: 'note', text: '{{expand this idea}}' } });
314
- 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
315
355
  ```
316
356
 
317
- **Constraints:**
318
- - Must contain only alphanumeric characters, hyphens (`-`), and underscores (`_`)
319
- - Must be unique within the space (throws if ID exists)
320
- - 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).
321
361
 
322
- 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).
323
363
 
324
364
  ## Authentication
325
365
 
@@ -366,7 +406,7 @@ if (!authenticated) {
366
406
 
367
407
  ## AI Agent
368
408
 
369
- 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.
370
410
 
371
411
  ```typescript
372
412
  const { message, objects } = await channel.prompt(
@@ -390,11 +430,11 @@ Returns a message (the AI's response) and the list of objects that were created
390
430
 
391
431
  | Option | Description |
392
432
  |--------|-------------|
393
- | `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). |
394
434
  | `responseSchema` | Request structured JSON instead of text summary |
395
435
  | `effort` | Effort level: `'QUICK'`, `'STANDARD'` (default), `'REASONING'`, or `'RESEARCH'` |
396
436
  | `ephemeral` | If true, don't record in interaction history (useful for tab completion) |
397
- | `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. |
398
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). |
399
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. |
400
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. |
@@ -419,7 +459,7 @@ const { objects } = await channel.prompt(
419
459
  // Work with specific objects
420
460
  const result = await channel.prompt(
421
461
  "Summarize these articles",
422
- { objectIds: ['article-1', 'article-2'] }
462
+ { locations: ['/space/article/intro.json', '/space/article/conclusion.json'] }
423
463
  );
424
464
 
425
465
  // Quick question without mutations (fast model + read-only)
@@ -456,7 +496,11 @@ Use `responseSchema` to get structured JSON instead of a text message:
456
496
 
457
497
  ```typescript
458
498
  const { message } = await channel.prompt("Categorize these items", {
459
- 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
+ ],
460
504
  responseSchema: {
461
505
  type: 'object',
462
506
  properties: {
@@ -478,7 +522,7 @@ console.log(result.categories, result.summary);
478
522
  AI operations automatically receive context:
479
523
  - **Interaction history** — Previous interactions and their results from this channel
480
524
  - **Recently modified objects** — Objects created or changed recently
481
- - **Selected objects** — Objects passed via `objectIds` are given primary focus
525
+ - **Selected objects** — Objects passed via `locations` are given primary focus
482
526
 
483
527
  This context flows automatically — no configuration needed. The AI sees enough history to maintain coherent interactions while respecting the `_`-prefixed field hiding rules.
484
528
 
@@ -506,7 +550,7 @@ await space.addUser(user.id, 'editor');
506
550
  |------|--------------|
507
551
  | `owner` | Full control, can delete space and manage all users |
508
552
  | `admin` | All editor capabilities, plus can manage users (except other admins/owners) |
509
- | `editor` | Can create, modify, and delete objects |
553
+ | `editor` | Can create, modify, move, and delete objects |
510
554
  | `viewer` | Read-only access (can query with `prompt` and `findObjects`) |
511
555
 
512
556
  ### Space Collaboration Methods
@@ -549,6 +593,7 @@ When a user accesses a space via URL, they're granted the corresponding role (`v
549
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. |
550
594
  | `getCurrentUser(): Promise<CurrentUser>` | Fetch fresh user profile from server (id, email, name, photoUrl, slug, plan, creditsBalance, totalCreditsUsed, createdAt, lastActivity, processedAt, storage) |
551
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. |
552
597
  | `searchUser(email): Promise<UserResult \| null>` | Find user by exact email address (no partial matching) |
553
598
 
554
599
  ### Real-time Collaboration
@@ -556,7 +601,7 @@ When a user accesses a space via URL, they're granted the corresponding role (`v
556
601
  When multiple users have a space open, changes sync in real-time. The `source` field in events tells you who made the change:
557
602
 
558
603
  ```typescript
559
- channel.on('objectUpdated', ({ objectId, object, source }) => {
604
+ channel.on('objectUpdated', ({ location, object, source }) => {
560
605
  if (source === 'remote_user') {
561
606
  // Another user made this change
562
607
  showCollaboratorActivity(object);
@@ -592,6 +637,7 @@ const client = new RoolClient({
592
637
  | `listSpaces(): Promise<RoolSpaceInfo[]>` | List available spaces |
593
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. |
594
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. |
595
641
  | `deleteSpace(id): Promise<void>` | Permanently delete a space (cannot be undone) |
596
642
  | `importArchive(name, archive): Promise<RoolSpace>` | Import from a zip archive, creating a new space |
597
643
  | `webdav(spaceId): RoolWebDAV` | Open a WebDAV client for a space's file storage |
@@ -679,7 +725,8 @@ Discover and install extensions published by other users.
679
725
 
680
726
  | Method | Description |
681
727
  |--------|-------------|
682
- | `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). |
683
730
  | `destroy(): void` | Clean up resources |
684
731
 
685
732
  ### Client Events
@@ -787,49 +834,126 @@ A channel is a named context within a space. All object operations, AI prompts,
787
834
 
788
835
  ### Object Operations
789
836
 
790
- 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`).
791
840
 
792
841
  | Method | Description |
793
842
  |--------|-------------|
794
- | `getObject(objectId): Promise<RoolObject \| undefined>` | Get object data, or undefined if not found. |
795
- | `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. |
796
- | `findObjects(options): Promise<{ objects, message }>` | Find objects using structured filters and natural language. Results sorted by modifiedAt (desc by default). |
797
- | `getObjectIds(options?): string[]` | Get all object IDs. Sorted by modifiedAt (desc by default). Options: `{ limit?, order? }`. |
798
- | `createObject(options): Promise<{ object, message }>` | Create a new object. Returns the object (with AI-filled content) and message. |
799
- | `updateObject(objectId, options): Promise<{ object, message }>` | Update an existing object. Returns the updated object and message. |
800
- | `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
801
853
 
802
- #### 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
+ ```
803
875
 
804
876
  | Option | Description |
805
877
  |--------|-------------|
806
- | `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. |
807
- | `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.
883
+
884
+ #### updateObject
885
+
886
+ ```typescript
887
+ // Add/update fields
888
+ await channel.updateObject('/space/article/welcome.json', {
889
+ data: { status: 'published' },
890
+ });
808
891
 
809
- #### updateObject Options
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
+ ```
810
902
 
811
903
  | Option | Description |
812
904
  |--------|-------------|
813
- | `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. |
814
906
  | `prompt` | Natural language instruction for AI to modify content. |
815
- | `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. |
816
909
 
817
- #### findObjects Options
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. |
940
+
941
+ #### findObjects
818
942
 
819
943
  Find objects using structured filters and/or natural language.
820
944
 
821
945
  - **`where` only** — exact-match filtering, no AI, no credits.
822
- - **`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.
823
947
  - **`prompt` only** — AI-powered semantic query over all objects.
824
- - **`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.
825
949
 
826
950
  | Option | Description |
827
951
  |--------|-------------|
828
- | `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. |
829
- | `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. |
830
954
  | `prompt` | Natural language query. Triggers AI evaluation (uses credits). |
831
955
  | `limit` | Maximum number of results. |
832
- | `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. |
833
957
  | `order` | Sort order by modifiedAt: `'asc'` or `'desc'` (default: `'desc'`). |
834
958
  | `ephemeral` | If true, the query won't be recorded in interaction history. Useful for responsive search. |
835
959
 
@@ -865,7 +989,7 @@ const { objects } = await channel.findObjects({
865
989
  });
866
990
  ```
867
991
 
868
- 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.
869
993
 
870
994
  ### Undo/Redo
871
995
 
@@ -882,7 +1006,7 @@ See [Checkpoints & Undo/Redo](#checkpoints--undoredo) for semantics.
882
1006
 
883
1007
  ### Space Metadata
884
1008
 
885
- 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).
886
1010
 
887
1011
  | Method | Description |
888
1012
  |--------|-------------|
@@ -943,6 +1067,8 @@ Paths are space-relative (`docs/readme.md`, not `/docs/readme.md`). `rool-drive:
943
1067
  | `webdav.lock(pathOrRef, options)` / `webdav.refreshLock(pathOrRef, token)` / `webdav.unlock(token)` | WebDAV Class 2 write locks |
944
1068
  | `webdav.request(method, path, init?)` | Raw authenticated WebDAV request escape hatch |
945
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
+
946
1072
  #### File references from AI responses
947
1073
 
948
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`).
@@ -978,7 +1104,7 @@ const response = await channel.fetch('https://api.example.com/submit', {
978
1104
 
979
1105
  ### Collection Schema
980
1106
 
981
- 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.
982
1108
 
983
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.
984
1110
 
@@ -1023,7 +1149,7 @@ await channel.dropCollection('article');
1023
1149
  | `string` | Text value | `{ kind: 'string' }` |
1024
1150
  | `number` | Numeric value | `{ kind: 'number' }` |
1025
1151
  | `boolean` | True/false | `{ kind: 'boolean' }` |
1026
- | `ref` | Reference to another object | `{ kind: 'ref' }` |
1152
+ | `ref` | Reference to another object (location string) | `{ kind: 'ref' }` |
1027
1153
  | `enum` | One of a set of values | `{ kind: 'enum', values: ['a', 'b'] }` |
1028
1154
  | `literal` | Exact value | `{ kind: 'literal', value: 'fixed' }` |
1029
1155
  | `array` | List of values | `{ kind: 'array', inner: { kind: 'string' } }` |
@@ -1065,10 +1191,11 @@ Semantic events describe what changed. Events fire for both local changes and re
1065
1191
  // - 'remote_agent': AI agent made the change
1066
1192
  // - 'system': Resync after error
1067
1193
 
1068
- // Object events
1069
- channel.on('objectCreated', ({ objectId, object, source }) => void)
1070
- channel.on('objectUpdated', ({ objectId, object, source }) => void)
1071
- 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)
1072
1199
 
1073
1200
  // Space metadata
1074
1201
  channel.on('metadataUpdated', ({ metadata, source }) => void)
@@ -1095,7 +1222,7 @@ AI operations may fail due to rate limiting or other transient errors. Check `er
1095
1222
 
1096
1223
  ```typescript
1097
1224
  try {
1098
- await channel.updateObject(objectId, { prompt: 'expand this' });
1225
+ await channel.updateObject(location, { prompt: 'expand this' });
1099
1226
  } catch (error) {
1100
1227
  if (error.message.includes('temporarily unavailable')) {
1101
1228
  showToast('Service busy, please try again in a moment');
@@ -1129,7 +1256,7 @@ Channel management (listing, renaming, deleting channels) is done via the client
1129
1256
 
1130
1257
  The `ai` field in interactions distinguishes AI-generated responses from synthetic confirmations:
1131
1258
  - `ai: true` — AI processed this operation (prompt, or createObject/updateObject with placeholders)
1132
- - `ai: false` — System confirmation only (e.g., "Created object abc123")
1259
+ - `ai: false` — System confirmation only (e.g., "Created object /space/note/welcome.json")
1133
1260
 
1134
1261
  ### Tool Calls
1135
1262
 
@@ -1167,17 +1294,19 @@ type SpaceSchema = Record<string, CollectionDef>;
1167
1294
  ### Object Data
1168
1295
 
1169
1296
  ```typescript
1170
- // RoolObject represents the object data you work with
1171
- // Always contains `id`, plus any additional fields
1172
- // Fields prefixed with _ are hidden from AI
1173
- // 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.
1174
1300
  interface RoolObject {
1175
- id: string;
1176
- [key: string]: unknown;
1301
+ location: string; // "/space/<collection>/<basename>.json"
1302
+ collection: string;
1303
+ basename: string;
1304
+ body: Record<string, unknown>;
1177
1305
  }
1178
1306
 
1179
- // Object stat - audit information returned by channel.stat()
1307
+ // Object stat audit information returned by channel.stat()
1180
1308
  interface RoolObjectStat {
1309
+ location: string;
1181
1310
  modifiedAt: number;
1182
1311
  modifiedBy: string;
1183
1312
  modifiedByName: string | null;
@@ -1249,19 +1378,19 @@ interface ToolCall {
1249
1378
  type InteractionStatus = 'pending' | 'streaming' | 'done' | 'error';
1250
1379
 
1251
1380
  interface Interaction {
1252
- id: string; // Unique ID for this interaction
1253
- 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)
1254
1383
  timestamp: number;
1255
- userId: string; // Who performed this interaction
1256
- userName?: string | null; // Display name at time of interaction
1257
- operation: 'prompt' | 'createObject' | 'updateObject' | 'deleteObjects';
1258
- input: string; // What the user did: prompt text or action description
1259
- output: string | null; // AI response or confirmation message (may be partial when streaming)
1260
- status: InteractionStatus; // Lifecycle status (pending → streaming → done/error)
1261
- ai: boolean; // Whether AI was invoked (vs synthetic confirmation)
1262
- modifiedObjectIds: string[]; // Objects affected by this interaction
1263
- toolCalls: ToolCall[]; // Tools called during this interaction (for AI prompts)
1264
- attachments?: string[]; // rool-drive:/... file references attached by the user
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
1265
1394
  }
1266
1395
  ```
1267
1396
 
@@ -1284,13 +1413,14 @@ type ChangeSource = 'local_user' | 'remote_user' | 'remote_agent' | 'system';
1284
1413
  type PromptEffort = 'QUICK' | 'STANDARD' | 'REASONING' | 'RESEARCH';
1285
1414
 
1286
1415
  interface PromptOptions {
1287
- objectIds?: string[]; // Scope to specific objects
1416
+ locations?: string[]; // Scope to specific objects
1288
1417
  responseSchema?: Record<string, unknown>;
1289
- effort?: PromptEffort; // Effort level (default: 'STANDARD')
1290
- ephemeral?: boolean; // Don't record in interaction history
1291
- readOnly?: boolean; // Disable mutation tools (default: false)
1292
- parentInteractionId?: string | null; // Branch from a specific interaction (omit to auto-continue)
1293
- attachments?: Array<File | Blob | { data: string; contentType: string }>; // Files to attach
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
1294
1424
  }
1295
1425
  ```
1296
1426