@rool-dev/sdk 0.10.1 → 0.10.2-dev.0bf8edb

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.
Files changed (58) hide show
  1. package/README.md +79 -179
  2. package/dist/channel.d.ts +41 -129
  3. package/dist/channel.d.ts.map +1 -1
  4. package/dist/channel.js +220 -394
  5. package/dist/channel.js.map +1 -1
  6. package/dist/client.d.ts +3 -55
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +7 -93
  9. package/dist/client.js.map +1 -1
  10. package/dist/graphql.d.ts +4 -46
  11. package/dist/graphql.d.ts.map +1 -1
  12. package/dist/graphql.js +28 -233
  13. package/dist/graphql.js.map +1 -1
  14. package/dist/index.d.ts +3 -6
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -4
  17. package/dist/index.js.map +1 -1
  18. package/dist/path.d.ts +6 -0
  19. package/dist/path.d.ts.map +1 -0
  20. package/dist/path.js +47 -0
  21. package/dist/path.js.map +1 -0
  22. package/dist/reroute.d.ts +22 -0
  23. package/dist/reroute.d.ts.map +1 -0
  24. package/dist/reroute.js +61 -0
  25. package/dist/reroute.js.map +1 -0
  26. package/dist/rest.d.ts +9 -0
  27. package/dist/rest.d.ts.map +1 -1
  28. package/dist/rest.js +33 -1
  29. package/dist/rest.js.map +1 -1
  30. package/dist/router.d.ts.map +1 -1
  31. package/dist/router.js +25 -10
  32. package/dist/router.js.map +1 -1
  33. package/dist/space.d.ts +10 -17
  34. package/dist/space.d.ts.map +1 -1
  35. package/dist/space.js +79 -55
  36. package/dist/space.js.map +1 -1
  37. package/dist/subscription.d.ts.map +1 -1
  38. package/dist/subscription.js +40 -32
  39. package/dist/subscription.js.map +1 -1
  40. package/dist/types.d.ts +52 -217
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/webdav.d.ts +44 -21
  43. package/dist/webdav.d.ts.map +1 -1
  44. package/dist/webdav.js +94 -57
  45. package/dist/webdav.js.map +1 -1
  46. package/package.json +2 -1
  47. package/dist/apps.d.ts +0 -30
  48. package/dist/apps.d.ts.map +0 -1
  49. package/dist/apps.js +0 -81
  50. package/dist/apps.js.map +0 -1
  51. package/dist/locations.d.ts +0 -34
  52. package/dist/locations.d.ts.map +0 -1
  53. package/dist/locations.js +0 -90
  54. package/dist/locations.js.map +0 -1
  55. package/dist/machine.d.ts +0 -16
  56. package/dist/machine.d.ts.map +0 -1
  57. package/dist/machine.js +0 -51
  58. package/dist/machine.js.map +0 -1
package/README.md CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  The TypeScript SDK for Rool, a persistent and collaborative environment for organizing objects.
4
4
 
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
-
7
5
  The SDK manages authentication, real-time synchronization, and per-space file storage. Core primitives:
8
6
 
9
7
  - **Spaces** — Containers for objects, schema, metadata, channels, and files
@@ -65,10 +63,9 @@ const { message, objects } = await channel.prompt(
65
63
  console.log(message); // AI explains what it did
66
64
  console.log(`Modified ${objects.length} objects`);
67
65
 
68
- // Query with natural language
69
- const { objects: innerPlanets } = await channel.findObjects({
70
- prompt: 'planets closer to the sun than Earth'
71
- });
66
+ // Read an object by location
67
+ const loadedEarth = await channel.getObject(earth.location);
68
+ console.log(loadedEarth?.body.name);
72
69
 
73
70
  // Clean up
74
71
  channel.close();
@@ -229,7 +226,7 @@ References are just data — no special API is needed to create or remove them.
229
226
  #### Location helpers
230
227
 
231
228
  ```typescript
232
- import { loc, parseLocation, normalizeLocation, generateBasename } from '@rool-dev/sdk';
229
+ import { loc, parseLocation, normalizeLocation } from '@rool-dev/sdk';
233
230
 
234
231
  loc('article', 'welcome'); // '/space/article/welcome.json'
235
232
  parseLocation('/space/article/welcome.json'); // { collection: 'article', basename: 'welcome' }
@@ -237,9 +234,6 @@ parseLocation('/space/article/welcome.json'); // { collection: 'article', basena
237
234
  // normalizeLocation accepts canonical or short form and returns canonical
238
235
  normalizeLocation('article/welcome'); // '/space/article/welcome.json'
239
236
  normalizeLocation('/space/article/welcome.json'); // unchanged
240
-
241
- // 6-char random basename — same generator the SDK uses by default
242
- generateBasename(); // e.g., 'X7kQ9p'
243
237
  ```
244
238
 
245
239
  SDK methods that accept a location (`getObject`, `updateObject`, `deleteObjects`, `moveObject`, etc.) accept either form and normalize internally. SDK return values always use the canonical full form.
@@ -320,24 +314,24 @@ await channel.createObject('article', {
320
314
 
321
315
  ### Real-time Sync
322
316
 
323
- Events fire for both local and remote changes. The `source` field indicates origin:
324
-
325
- - `local_user` — This client made the change
326
- - `remote_user` — Another user/client made the change
327
- - `remote_agent` — AI agent made the change
328
- - `system` — Resync after error
317
+ Object and file reactivity is WebDAV-based. Listen for space-level file change notifications, then reconcile with `webdav.syncCollection()` using your sync token. This covers both object files under `/space` and user files under `/rool-drive`.
329
318
 
330
319
  ```typescript
331
- // All UI updates happen in one place, regardless of change source
332
- channel.on('objectUpdated', ({ location, object, source }) => {
333
- renderObject(location, object);
334
- if (source === 'remote_agent') {
335
- doLayout(); // AI might have added content
336
- }
337
- });
320
+ let token: string | null = null;
321
+
322
+ async function syncFiles() {
323
+ const result = await space.webdav.syncCollection('/', {
324
+ token,
325
+ level: 'infinite',
326
+ props: ['displayname', 'getetag', 'getlastmodified', 'resourcetype'],
327
+ });
328
+ token = result.token;
329
+ updateFileTree(result.responses);
330
+ }
338
331
 
339
- // Caller just makes the change - event handler does the UI work
340
- channel.updateObject(location, { prompt: 'expand this' });
332
+ space.on('filesChanged', syncFiles);
333
+ space.on('filesReset', () => { token = null; syncFiles(); });
334
+ await syncFiles();
341
335
  ```
342
336
 
343
337
  ### Locations & Basenames
@@ -358,7 +352,7 @@ await channel.createObject('article',
358
352
 
359
353
  ```typescript
360
354
  // Fire-and-forget: create and reference without waiting
361
- const basename = RoolClient.generateBasename();
355
+ const basename = 'idea-seed';
362
356
  const location = loc('note', basename);
363
357
 
364
358
  channel.createObject('note', { text: '{{expand this idea}}' }, { basename });
@@ -443,13 +437,12 @@ Returns a message (the AI's response) and the list of objects that were created
443
437
 
444
438
  | Option | Description |
445
439
  |--------|-------------|
446
- | `locations` | Focus the AI on specific objects, identified by location (given primary attention in context). |
447
440
  | `responseSchema` | Request structured JSON instead of text summary |
448
441
  | `effort` | Effort level: `'QUICK'`, `'STANDARD'` (default), `'REASONING'`, or `'RESEARCH'` |
449
442
  | `ephemeral` | If true, don't record in interaction history (useful for tab completion) |
450
443
  | `readOnly` | If true, disable mutation tools (create, update, move, delete). Use for questions. |
451
444
  | `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). |
452
- | `attachments` | Files to attach (`File`, `Blob`, or `{ data, contentType }`). Stored as authenticated space files; resulting `rool-machine:/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. |
445
+ | `attachments` | Machine resources to focus the AI on, plus local files to upload (`File`, `Blob`, or `{ data, contentType, filename? }`). Pass object resources (`/space/...`) for object context and file resources (`/rool-drive/...`) for existing WebDAV files/folders. Local files are uploaded to authenticated space file storage first. The interaction stores canonical `rool-machine:/...` refs 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 stored but the AI cannot natively consume their contents, only use shell tools on them. |
453
446
  | `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. |
454
447
 
455
448
  ### Effort Levels
@@ -470,9 +463,12 @@ const { objects } = await channel.prompt(
470
463
  );
471
464
 
472
465
  // Work with specific objects
466
+ const intro = resolveMachineResource('/space/article/intro.json');
467
+ const conclusion = resolveMachineResource('/space/article/conclusion.json');
468
+ if (!intro || !conclusion) throw new Error('invalid resource');
473
469
  const result = await channel.prompt(
474
470
  "Summarize these articles",
475
- { locations: ['/space/article/intro.json', '/space/article/conclusion.json'] }
471
+ { attachments: [intro, conclusion] }
476
472
  );
477
473
 
478
474
  // Quick question without mutations (fast model + read-only)
@@ -487,11 +483,13 @@ await channel.prompt(
487
483
  { effort: 'REASONING' }
488
484
  );
489
485
 
490
- // Attach files for the AI to see (File from <input>, Blob, or base64)
486
+ // Attach existing WebDAV files/folders or local uploads
487
+ const report = resolveMachineResource('/rool-drive/docs/report.pdf');
488
+ if (!report) throw new Error('invalid resource');
491
489
  const file = fileInput.files[0]; // from <input type="file">
492
490
  await channel.prompt(
493
- "Describe what's in this photo and create an object for it",
494
- { attachments: [file] }
491
+ "Compare this report with the uploaded photo",
492
+ { attachments: [report, file] }
495
493
  );
496
494
 
497
495
  // Cancel a long-running prompt
@@ -508,12 +506,18 @@ await channel.prompt("Do a deep analysis...", {
508
506
  Use `responseSchema` to get structured JSON instead of a text message:
509
507
 
510
508
  ```typescript
509
+ const resources = [
510
+ '/space/item/widget.json',
511
+ '/space/item/gadget.json',
512
+ '/space/item/gizmo.json',
513
+ ].map((path) => {
514
+ const resource = resolveMachineResource(path);
515
+ if (!resource) throw new Error(`invalid resource: ${path}`);
516
+ return resource;
517
+ });
518
+
511
519
  const { message } = await channel.prompt("Categorize these items", {
512
- locations: [
513
- '/space/item/widget.json',
514
- '/space/item/gadget.json',
515
- '/space/item/gizmo.json',
516
- ],
520
+ attachments: resources,
517
521
  responseSchema: {
518
522
  type: 'object',
519
523
  properties: {
@@ -535,7 +539,7 @@ console.log(result.categories, result.summary);
535
539
  AI operations automatically receive context:
536
540
  - **Interaction history** — Previous interactions and their results from this channel
537
541
  - **Recently modified objects** — Objects created or changed recently
538
- - **Selected objects** — Objects passed via `locations` are given primary focus
542
+ - **Attached resources** — Object resources passed via `attachments` are given primary focus; file resources are surfaced as `/rool-drive/...` paths
539
543
 
540
544
  This context flows automatically — no configuration needed. The AI sees enough history to maintain coherent interactions while respecting the `_`-prefixed field hiding rules.
541
545
 
@@ -564,7 +568,7 @@ await space.addUser(user.id, 'editor');
564
568
  | `owner` | Full control, can delete space and manage all users |
565
569
  | `admin` | All editor capabilities, plus can manage users (except other admins/owners) |
566
570
  | `editor` | Can create, modify, move, and delete objects |
567
- | `viewer` | Read-only access (can query with `prompt` and `findObjects`) |
571
+ | `viewer` | Read-only access (can query with `prompt` and read objects/files) |
568
572
 
569
573
  ### Space Collaboration Methods
570
574
 
@@ -611,18 +615,9 @@ When a user accesses a space via URL, they're granted the corresponding role (`v
611
615
 
612
616
  ### Real-time Collaboration
613
617
 
614
- When multiple users have a space open, changes sync in real-time. The `source` field in events tells you who made the change:
618
+ When multiple users have a space open, object and file changes are announced by `space.on('filesChanged')` and reconciled through WebDAV `syncCollection()`. Channel/conversation state still emits channel events; filesystem state does not use channel object events.
615
619
 
616
- ```typescript
617
- channel.on('objectUpdated', ({ location, object, source }) => {
618
- if (source === 'remote_user') {
619
- // Another user made this change
620
- showCollaboratorActivity(object);
621
- }
622
- });
623
- ```
624
-
625
- See [Real-time Sync](#real-time-sync) for more on event sources.
620
+ See [Real-time Sync](#real-time-sync) for a WebDAV sync-token example.
626
621
 
627
622
  ## RoolClient API
628
623
 
@@ -653,8 +648,6 @@ const client = new RoolClient({
653
648
  | `duplicateSpace(sourceSpaceId, name): Promise<RoolSpace>` | Duplicate an existing space. Returns a handle to the new space. |
654
649
  | `deleteSpace(id): Promise<void>` | Permanently delete a space (cannot be undone) |
655
650
  | `importArchive(name, archive): Promise<RoolSpace>` | Import from a zip archive, creating a new space |
656
- | `webdav(spaceId): RoolWebDAV` | Open a WebDAV client for a space's file storage |
657
- | `getSpaceStorageUsage(spaceId): Promise<SpaceFileStorageUsage>` | Get WebDAV quota usage for a space |
658
651
 
659
652
  ### Channel Management
660
653
 
@@ -663,7 +656,6 @@ Manage channels on the `RoolSpace` handle:
663
656
  | Method | Description |
664
657
  |--------|-------------|
665
658
  | `space.channels: ChannelInfo[]` | Live channel list (auto-updates via SSE) |
666
- | `space.getChannels(): ChannelInfo[]` | List channels (deprecated — use `space.channels` instead) |
667
659
  | `space.renameChannel(channelId, name): Promise<void>` | Rename a channel |
668
660
  | `space.deleteChannel(channelId): Promise<void>` | Delete a channel and its interaction history |
669
661
  | `channel.rename(name): Promise<void>` | Rename the current open channel |
@@ -707,39 +699,11 @@ client.on('userStorageChanged', ({ key, value, source }) => {
707
699
  });
708
700
  ```
709
701
 
710
- ### Extensions
711
-
712
- Manage and publish extensions. See [`@rool-dev/extension`](/extension/) for building extensions.
713
-
714
- There are two distinct domains: your **personal library** (extensions you've created or installed) and the **published extensions** (extensions discoverable by all users). Each has its own return type.
715
-
716
- #### Your Library (`ExtensionInfo`)
717
-
718
- Manage extensions you own. Each `ExtensionInfo` includes `published` (whether it's listed in the marketplace) and `marketplaceExtensionId` (non-null if you installed it from someone else's listing, null if you authored it).
719
-
720
- | Method | Description |
721
- |--------|-------------|
722
- | `uploadExtension(extensionId, options): Promise<ExtensionInfo>` | Upload or update an extension (`options.bundle`: zip with `index.html` and `manifest.json`) |
723
- | `listExtensions(): Promise<ExtensionInfo[]>` | List your extensions |
724
- | `getExtensionInfo(extensionId): Promise<ExtensionInfo \| null>` | Get info for a specific extension |
725
- | `deleteExtension(extensionId): Promise<void>` | Delete an extension permanently (removes files and DB row) |
726
-
727
- #### Marketplace (`PublishedExtensionInfo`)
728
-
729
- Discover and install extensions published by other users.
730
-
731
- | Method | Description |
732
- |--------|-------------|
733
- | `findExtensions(options?): Promise<PublishedExtensionInfo[]>` | Search the marketplace. Options: `query` (semantic search string), `limit` (default 20, max 100). Omit `query` to browse all. |
734
- | `publishToPublic(extensionId): Promise<void>` | Publish one of your extensions to the marketplace |
735
- | `unpublishFromPublic(extensionId): Promise<void>` | Remove from the marketplace (keeps the extension in your library) |
736
-
737
702
  ### Utilities
738
703
 
739
704
  | Method | Description |
740
705
  |--------|-------------|
741
- | `RoolClient.generateBasename(): string` | Generate a 6-char alphanumeric basename for new object identities. |
742
- | `RoolClient.generateId(): string` | Same as `generateBasename()`; retained for callers minting non-object IDs (interactions, conversations, channels). |
706
+ | `RoolClient.generateId(): string` | Generate a unique 6-character alphanumeric ID. |
743
707
  | `destroy(): void` | Clean up resources |
744
708
 
745
709
  ### Client Events
@@ -801,10 +765,8 @@ A space handle with a live SSE subscription. Extends `EventEmitter`. Manages use
801
765
  | `addUser(userId, role): Promise<void>` | Add user to space |
802
766
  | `removeUser(userId): Promise<void>` | Remove user from space |
803
767
  | `setLinkAccess(linkAccess): Promise<void>` | Set URL sharing level |
804
- | `getChannels(): ChannelInfo[]` | List channels (deprecated — use `channels` property instead) |
805
768
  | `renameChannel(channelId, name): Promise<void>` | Rename a channel |
806
769
  | `deleteChannel(channelId): Promise<void>` | Delete a channel |
807
- | `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. |
808
770
  | `exportArchive(): Promise<Blob>` | Export space as zip archive |
809
771
  | `getStorageUsage(): Promise<SpaceFileStorageUsage>` | Get WebDAV quota usage for this space |
810
772
  | `fetchMachineResource(resource): Promise<Response>` | Fetch a resolved file `MachineResource` through this space |
@@ -814,8 +776,9 @@ A space handle with a live SSE subscription. Extends `EventEmitter`. Manages use
814
776
 
815
777
  ```typescript
816
778
  space.on('channelCreated', (channel: ChannelInfo) => void) // New channel added
817
- space.on('channelUpdated', (channel: ChannelInfo) => void) // Channel metadata changed (name, extension, manifest)
779
+ space.on('channelUpdated', (channel: ChannelInfo) => void) // Channel metadata changed
818
780
  space.on('channelDeleted', (channelId: string) => void) // Channel removed
781
+ space.on('filesChanged', ({ source, timestamp }) => void) // WebDAV file storage changed; call webdav.syncCollection()
819
782
  space.on('connectionStateChanged', (state: 'connected' | 'disconnected' | 'reconnecting') => void)
820
783
  ```
821
784
 
@@ -834,9 +797,6 @@ A channel is a named context within a space. All object operations, AI prompts,
834
797
  | `userId: string` | Current user's ID |
835
798
  | `channelId: string` | Channel ID (read-only, fixed at open time) |
836
799
  | `isReadOnly: boolean` | True if viewer role |
837
- | `extensionUrl: string \| null` | URL of the installed extension, or null if this is a plain channel |
838
- | `extensionId: string \| null` | ID of the installed extension, or null if this is a plain channel |
839
- | `manifest: ExtensionManifest \| null` | Extension manifest snapshot (name, icon, collections, etc.), or null |
840
800
 
841
801
  ### Lifecycle
842
802
 
@@ -856,8 +816,6 @@ All methods that accept a location accept either the canonical form or the short
856
816
  |--------|-------------|
857
817
  | `getObject(location): Promise<RoolObject \| undefined>` | Get an object, or undefined if not found. |
858
818
  | `stat(location): RoolObjectStat \| undefined` | Get audit info for an object: when it was last modified, by whom, and where (channel/conversation/interaction). Sync read from local cache. |
859
- | `findObjects(options): Promise<{ objects, message }>` | Find objects using structured filters and/or natural language. Results sorted by modifiedAt (desc by default). |
860
- | `getObjectLocations(options?): string[]` | Get all object locations. Sorted by modifiedAt (desc by default). Options: `{ limit?, order? }`. |
861
819
  | `createObject(collection, body, options?): Promise<{ object, message }>` | Create a new object in `collection`. The SDK mints a random basename unless you pass `options.basename`. |
862
820
  | `updateObject(location, options): Promise<{ object, message }>` | Update an existing object's body. |
863
821
  | `moveObject(from, to, options?): Promise<{ object, message }>` | Rename or relocate an object. See [Moving and Renaming](#moving-and-renaming). |
@@ -950,59 +908,6 @@ await channel.moveObject(from, to, {
950
908
  | `ephemeral` | If true, the operation won't be recorded in interaction history. |
951
909
  | `parentInteractionId` | Conversation tree parent. Omit to auto-continue; pass `null` for a new root. |
952
910
 
953
- #### findObjects
954
-
955
- Find objects using structured filters and/or natural language.
956
-
957
- - **`where` only** — exact-match filtering, no AI, no credits.
958
- - **`collection` only** — filter by collection name, no AI, no credits.
959
- - **`prompt` only** — AI-powered semantic query over all objects.
960
- - **`where` + `prompt`** — `where` (and `locations`) narrow the data set first, then the AI queries within the constrained set.
961
-
962
- | Option | Description |
963
- |--------|-------------|
964
- | `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. |
965
- | `collection` | Filter by collection name. |
966
- | `prompt` | Natural language query. Triggers AI evaluation (uses credits). |
967
- | `limit` | Maximum number of results. |
968
- | `locations` | Scope to specific object locations. Constrains the candidate set in both structured and AI queries. |
969
- | `order` | Sort order by modifiedAt: `'asc'` or `'desc'` (default: `'desc'`). |
970
- | `ephemeral` | If true, the query won't be recorded in interaction history. Useful for responsive search. |
971
-
972
- **Examples:**
973
-
974
- ```typescript
975
- // Filter by collection (no AI, no credits)
976
- const { objects } = await channel.findObjects({
977
- collection: 'article'
978
- });
979
-
980
- // Exact field matching (no AI, no credits)
981
- const { objects } = await channel.findObjects({
982
- where: { status: 'published' }
983
- });
984
-
985
- // Combine collection and field filters
986
- const { objects } = await channel.findObjects({
987
- collection: 'article',
988
- where: { status: 'published' }
989
- });
990
-
991
- // Pure natural language query (AI interprets)
992
- const { objects, message } = await channel.findObjects({
993
- prompt: 'articles about space exploration published this year'
994
- });
995
-
996
- // Combined: collection + where narrow the data, prompt queries within it
997
- const { objects } = await channel.findObjects({
998
- collection: 'article',
999
- prompt: 'that discuss climate solutions positively',
1000
- limit: 10
1001
- });
1002
- ```
1003
-
1004
- 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.
1005
-
1006
911
  ### Undo/Redo
1007
912
 
1008
913
  | Method | Description |
@@ -1030,12 +935,13 @@ Store arbitrary data alongside the space without it being part of an object's bo
1030
935
 
1031
936
  Every space has authenticated file storage. WebDAV is the SDK surface for that storage: paths are relative to the space root and collection operations use WebDAV collection semantics. Human/AI file links use `rool-machine:/rool-drive/...`; resolve those links with `resolveMachineResource()` and fetch file resources with `space.fetchMachineResource(resource)`.
1032
937
 
1033
- Use `client.webdav(spaceId)` when you only have an ID, or `space.webdav` when you already have an open space.
938
+ Open a space, then use `space.webdav`.
1034
939
 
1035
940
  ```typescript
1036
941
  import { resolveMachineResource } from '@rool-dev/sdk';
1037
942
 
1038
- const webdav = client.webdav('space-id');
943
+ const space = await client.openSpace('space-id');
944
+ const webdav = space.webdav;
1039
945
 
1040
946
  await webdav.mkcol('docs');
1041
947
  await webdav.put('docs/readme.md', '# Hello', {
@@ -1059,19 +965,33 @@ const usage = await space.getStorageUsage();
1059
965
  console.log(usage.usedBytes);
1060
966
  console.log(usage.availableBytes); // null means unlimited
1061
967
  console.log(usage.limitBytes); // null means unlimited
968
+
969
+ const rootProps = await webdav.propfind('', {
970
+ depth: '0',
971
+ props: ['sync-token', 'supported-report-set'],
972
+ });
973
+ let syncToken = rootProps.responses[0]?.props.syncToken ?? null;
974
+
975
+ space.on('filesChanged', async () => {
976
+ const delta = await space.webdav.syncCollection('', {
977
+ token: syncToken,
978
+ level: 'infinite',
979
+ });
980
+ syncToken = delta.token;
981
+ console.log('Changed file responses:', delta.responses);
982
+ });
1062
983
  ```
1063
984
 
1064
985
  Paths are space-relative (`docs/readme.md`, not `/docs/readme.md`). WebDAV methods accept WebDAV paths only. User-facing file links should use `rool-machine:/rool-drive/...`; resolve either that URI or a bare `/rool-drive/...` machine path with `resolveMachineResource()` and fetch the resulting file resource with `space.fetchMachineResource(resource)`. `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`.
1065
986
 
1066
987
  | Method | Description |
1067
988
  |--------|-------------|
1068
- | `client.webdav(spaceId)` | Create a WebDAV client for a space |
1069
- | `client.getSpaceStorageUsage(spaceId)` | Get WebDAV quota usage for a space |
1070
989
  | `space.webdav` | WebDAV client for an open space |
1071
990
  | `space.getStorageUsage()` | Get WebDAV quota usage for an open space |
1072
991
  | `webdav.getStorageUsage()` | Get WebDAV quota usage through the WebDAV client |
1073
992
  | `webdav.path(path)` | Normalize a WebDAV path |
1074
- | `webdav.propfind(path, options)` | Read properties/list collections; explicit `depth` required |
993
+ | `webdav.propfind(path, options)` | Read properties/list collections; explicit `depth` required. Supports `sync-token` and `supported-report-set` props. |
994
+ | `webdav.syncCollection(path, options)` | Reconcile WebDAV changes with `REPORT sync-collection`. Pass the previous `token` (or `null`), `level: '1' \| 'infinite'`, optional `props`/`limit`; returns changed responses plus the next `token`. |
1075
995
  | `webdav.get(path, options?)` / `webdav.head(path)` | Read a file, including optional byte ranges for `get` |
1076
996
  | `webdav.put(path, body, options?)` | Write an exact file path; parents must already exist |
1077
997
  | `webdav.mkcol(path)` | Create one collection |
@@ -1198,37 +1118,23 @@ The archive bundles `data.json` (objects, metadata, and channels) together with
1198
1118
 
1199
1119
  ### Channel Events
1200
1120
 
1201
- Semantic events describe what changed. Events fire for both local changes and remote changes.
1121
+ Channel events are for channel/conversation state. Object and file reactivity goes through `space.on('filesChanged' | 'filesReset')` plus WebDAV `syncCollection()`.
1202
1122
 
1203
1123
  ```typescript
1204
- // source indicates origin:
1205
- // - 'local_user': This client made the change
1206
- // - 'remote_user': Another user/client made the change
1207
- // - 'remote_agent': AI agent made the change
1208
- // - 'system': Resync after error
1209
-
1210
- // Object events — payload includes the full RoolObject
1211
- channel.on('objectCreated', ({ location, object, source }) => void)
1212
- channel.on('objectUpdated', ({ location, object, source }) => void)
1213
- channel.on('objectDeleted', ({ location, source }) => void)
1214
- channel.on('objectMoved', ({ from, to, object, source }) => void)
1215
-
1216
- // Space metadata
1217
- channel.on('metadataUpdated', ({ metadata, source }) => void)
1218
-
1219
- // Collection schema changed
1220
- channel.on('schemaUpdated', ({ schema, source }) => void)
1221
-
1222
- // Channel metadata updated (name, extensionUrl)
1124
+ // Channel metadata updated
1223
1125
  channel.on('channelUpdated', ({ channelId, source }) => void)
1224
1126
 
1225
1127
  // Conversation interaction history updated
1226
1128
  channel.on('conversationUpdated', ({ conversationId, channelId, source }) => void)
1227
1129
 
1130
+ // Space metadata / schema compatibility events
1131
+ channel.on('metadataUpdated', ({ metadata, source }) => void)
1132
+ channel.on('schemaUpdated', ({ schema, source }) => void)
1133
+
1228
1134
  // Full state replacement (undo/redo, resync after error)
1229
1135
  channel.on('reset', ({ source }) => void)
1230
1136
 
1231
- // Sync error occurred, channel resynced from server
1137
+ // Sync error occurred
1232
1138
  channel.on('syncError', (error: Error) => void)
1233
1139
  ```
1234
1140
 
@@ -1359,13 +1265,10 @@ interface Channel {
1359
1265
  createdAt: number; // Timestamp when channel was created
1360
1266
  createdBy: string; // User ID who created the channel
1361
1267
  createdByName?: string; // Display name at time of creation
1362
- extensionUrl?: string; // URL of installed extension (set by installExtension)
1363
- extensionId?: string; // ID of installed extension (user_extensions.extension_id)
1364
- manifest?: ExtensionManifest; // Extension manifest snapshot (set when extension is wired)
1365
1268
  conversations: Record<string, Conversation>; // Keyed by conversation ID
1366
1269
  }
1367
1270
 
1368
- // Channel summary info (returned by client.getChannels)
1271
+ // Channel summary info (used by space.channels)
1369
1272
  interface ChannelInfo {
1370
1273
  id: string;
1371
1274
  name: string | null;
@@ -1373,9 +1276,6 @@ interface ChannelInfo {
1373
1276
  createdBy: string;
1374
1277
  createdByName: string | null;
1375
1278
  interactionCount: number;
1376
- extensionUrl: string | null; // URL of installed extension, or null
1377
- extensionId: string | null; // ID of installed extension, or null
1378
- manifest: ExtensionManifest | null; // Extension manifest snapshot, or null
1379
1279
  }
1380
1280
  ```
1381
1281
 
@@ -1414,7 +1314,7 @@ interface Interaction {
1414
1314
  ai: boolean; // Whether AI was invoked (vs synthetic confirmation)
1415
1315
  modifiedObjectLocations: string[]; // Locations of objects affected by this interaction
1416
1316
  toolCalls: ToolCall[]; // Tools called during this interaction (for AI prompts)
1417
- attachments?: string[]; // rool-machine:/rool-drive/... file references attached by the user
1317
+ attachments?: string[]; // canonical rool-machine:/... resource refs attached by the user
1418
1318
  }
1419
1319
  ```
1420
1320
 
@@ -1435,15 +1335,15 @@ type ChangeSource = 'local_user' | 'remote_user' | 'remote_agent' | 'system';
1435
1335
 
1436
1336
  ```typescript
1437
1337
  type PromptEffort = 'QUICK' | 'STANDARD' | 'REASONING' | 'RESEARCH';
1338
+ type PromptAttachment = File | Blob | { data: string; contentType: string; filename?: string } | MachineResource;
1438
1339
 
1439
1340
  interface PromptOptions {
1440
- locations?: string[]; // Scope to specific objects
1441
1341
  responseSchema?: Record<string, unknown>;
1442
1342
  effort?: PromptEffort; // Effort level (default: 'STANDARD')
1443
1343
  ephemeral?: boolean; // Don't record in interaction history
1444
1344
  readOnly?: boolean; // Disable mutation tools (default: false)
1445
1345
  parentInteractionId?: string | null; // Branch from a specific interaction (omit to auto-continue)
1446
- attachments?: Array<File | Blob | { data: string; contentType: string }>; // Files to attach
1346
+ attachments?: PromptAttachment[]; // Machine resources or local files to upload
1447
1347
  signal?: AbortSignal; // Cancel an in-flight prompt
1448
1348
  }
1449
1349
  ```